Skip to content

Commit 8fba5c0

Browse files
committed
Merge pull request #405 from elyscape/feature/fqdn_rand_strings
(MODULES-1715) Add FQDN-based random string generator
2 parents 487e8d4 + a82266c commit 8fba5c0

5 files changed

Lines changed: 262 additions & 0 deletions

File tree

README.markdown

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,23 @@ This function flattens any deeply nested arrays and returns a single flat array
244244
Returns the largest integer less than or equal to the argument.
245245
Takes a single numeric value as an argument. *Type*: rvalue
246246

247+
#### `fqdn_rand_string`
248+
249+
Generates a random alphanumeric string using an optionally-specified character set (default is alphanumeric), combining the `$fqdn` fact and an optional seed for repeatable randomness.
250+
251+
*Usage:*
252+
```
253+
fqdn_rand_string(LENGTH, [CHARSET], [SEED])
254+
```
255+
*Examples:*
256+
```
257+
fqdn_rand_string(10)
258+
fqdn_rand_string(10, 'ABCDEF!@#$%^')
259+
fqdn_rand_string(10, '', 'custom seed')
260+
```
261+
262+
*Type*: rvalue
263+
247264
#### `fqdn_rotate`
248265

249266
Rotates an array a random number of times based on a node's fqdn. *Type*: rvalue
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Puppet::Parser::Functions::newfunction(
2+
:fqdn_rand_string,
3+
:arity => -2,
4+
:type => :rvalue,
5+
:doc => "Usage: `fqdn_rand_string(LENGTH, [CHARSET], [SEED])`. LENGTH is
6+
required and must be a positive integer. CHARSET is optional and may be
7+
`undef` or a string. SEED is optional and may be any number or string.
8+
9+
Generates a random string LENGTH characters long using the character set
10+
provided by CHARSET, combining the `$fqdn` fact and the value of SEED for
11+
repeatable randomness. (That is, each node will get a different random
12+
string from this function, but a given node's result will be the same every
13+
time unless its hostname changes.) Adding a SEED can be useful if you need
14+
more than one unrelated string. CHARSET will default to alphanumeric if
15+
`undef` or an empty string.") do |args|
16+
raise(ArgumentError, "fqdn_rand_string(): wrong number of arguments (0 for 1)") if args.size == 0
17+
Puppet::Parser::Functions.function('is_integer')
18+
raise(ArgumentError, "fqdn_rand_base64(): first argument must be a positive integer") unless function_is_integer([args[0]]) and args[0].to_i > 0
19+
raise(ArgumentError, "fqdn_rand_base64(): second argument must be undef or a string") unless args[1].nil? or args[1].is_a? String
20+
21+
Puppet::Parser::Functions.function('fqdn_rand')
22+
23+
length = args.shift.to_i
24+
charset = args.shift.to_s.chars.to_a
25+
26+
charset = (0..9).map { |i| i.to_s } + ('A'..'Z').to_a + ('a'..'z').to_a if charset.empty?
27+
28+
rand_string = ''
29+
for current in 1..length
30+
rand_string << charset[function_fqdn_rand([charset.size, (args + [current.to_s]).join(':')]).to_i]
31+
end
32+
33+
rand_string
34+
end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#! /usr/bin/env ruby -S rspec
2+
require 'spec_helper_acceptance'
3+
4+
describe 'fqdn_rand_base64 function', :unless => unsupported_platforms.include?(fact('operatingsystem')) do
5+
describe 'success' do
6+
let(:facts_d) do
7+
if fact('is_pe', '--puppet') == "true"
8+
if fact('osfamily') =~ /windows/i
9+
if fact('kernelmajversion').to_f < 6.0
10+
'c:/documents and settings/all users/application data/puppetlabs/facter/facts.d'
11+
else
12+
'c:/programdata/puppetlabs/facter/facts.d'
13+
end
14+
else
15+
'/etc/puppetlabs/facter/facts.d'
16+
end
17+
else
18+
'/etc/facter/facts.d'
19+
end
20+
end
21+
after :each do
22+
shell("if [ -f '#{facts_d}/fqdn.txt' ] ; then rm '#{facts_d}/fqdn.txt' ; fi")
23+
end
24+
before :each do
25+
#no need to create on windows, pe creates by default
26+
if fact('osfamily') !~ /windows/i
27+
shell("mkdir -p '#{facts_d}'")
28+
end
29+
end
30+
it 'generates random base64 strings' do
31+
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
32+
pp = <<-eos
33+
$l = 10
34+
$o = fqdn_rand_base64($l)
35+
notice(inline_template('fqdn_rand_base64 is <%= @o.inspect %>'))
36+
eos
37+
38+
apply_manifest(pp, :catch_failures => true) do |r|
39+
expect(r.stdout).to match(/fqdn_rand_base64 is "8ySYp0dq0B"/)
40+
end
41+
end
42+
it 'generates random base64 strings with custom seeds' do
43+
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
44+
pp = <<-eos
45+
$l = 10
46+
$s = 'seed'
47+
$o = fqdn_rand_base64($l, $s)
48+
notice(inline_template('fqdn_rand_base64 is <%= @o.inspect %>'))
49+
eos
50+
51+
apply_manifest(pp, :catch_failures => true) do |r|
52+
expect(r.stdout).to match(/fqdn_rand_base64 is "6J2c4oMRUJ"/)
53+
end
54+
end
55+
end
56+
describe 'failure' do
57+
it 'handles improper argument counts'
58+
it 'handles non-numbers for length argument'
59+
end
60+
end
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#! /usr/bin/env ruby -S rspec
2+
require 'spec_helper_acceptance'
3+
4+
describe 'fqdn_rand_string function', :unless => unsupported_platforms.include?(fact('operatingsystem')) do
5+
describe 'success' do
6+
let(:facts_d) do
7+
if fact('is_pe', '--puppet') == "true"
8+
if fact('osfamily') =~ /windows/i
9+
if fact('kernelmajversion').to_f < 6.0
10+
'c:/documents and settings/all users/application data/puppetlabs/facter/facts.d'
11+
else
12+
'c:/programdata/puppetlabs/facter/facts.d'
13+
end
14+
else
15+
'/etc/puppetlabs/facter/facts.d'
16+
end
17+
else
18+
'/etc/facter/facts.d'
19+
end
20+
end
21+
after :each do
22+
shell("if [ -f '#{facts_d}/fqdn.txt' ] ; then rm '#{facts_d}/fqdn.txt' ; fi")
23+
end
24+
before :each do
25+
#no need to create on windows, pe creates by default
26+
if fact('osfamily') !~ /windows/i
27+
shell("mkdir -p '#{facts_d}'")
28+
end
29+
end
30+
it 'generates random alphanumeric strings' do
31+
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
32+
pp = <<-eos
33+
$l = 10
34+
$o = fqdn_rand_string($l)
35+
notice(inline_template('fqdn_rand_string is <%= @o.inspect %>'))
36+
eos
37+
38+
apply_manifest(pp, :catch_failures => true) do |r|
39+
expect(r.stdout).to match(/fqdn_rand_string is "7oDp0KOr1b"/)
40+
end
41+
end
42+
it 'generates random alphanumeric strings with custom seeds' do
43+
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
44+
pp = <<-eos
45+
$l = 10
46+
$s = 'seed'
47+
$o = fqdn_rand_string($l, $s)
48+
notice(inline_template('fqdn_rand_string is <%= @o.inspect %>'))
49+
eos
50+
51+
apply_manifest(pp, :catch_failures => true) do |r|
52+
expect(r.stdout).to match(/fqdn_rand_string is "3HS4mbuI3E"/)
53+
end
54+
end
55+
end
56+
describe 'failure' do
57+
it 'handles improper argument counts'
58+
it 'handles non-numbers for length argument'
59+
end
60+
end
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#! /usr/bin/env ruby -S rspec
2+
require 'spec_helper'
3+
4+
describe "the fqdn_rand_string function" do
5+
let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
6+
7+
it "should exist" do
8+
expect(Puppet::Parser::Functions.function("fqdn_rand_string")).to eq("function_fqdn_rand_string")
9+
end
10+
11+
it "should raise an ArgumentError if there is less than 1 argument" do
12+
expect { fqdn_rand_string() }.to( raise_error(ArgumentError, /wrong number of arguments/))
13+
end
14+
15+
it "should raise an ArgumentError if argument 1 isn't a positive integer" do
16+
expect { fqdn_rand_string(0) }.to( raise_error(ArgumentError, /first argument must be a positive integer/))
17+
expect { fqdn_rand_string(-1) }.to( raise_error(ArgumentError, /first argument must be a positive integer/))
18+
expect { fqdn_rand_string(0.5) }.to( raise_error(ArgumentError, /first argument must be a positive integer/))
19+
end
20+
21+
it "provides a valid alphanumeric string when no character set is provided" do
22+
length = 100
23+
string = %r{\A[a-zA-Z0-9]{#{length}}\z}
24+
expect(fqdn_rand_string(length).match(string)).not_to eq(nil)
25+
end
26+
27+
it "provides a valid alphanumeric string when an undef character set is provided" do
28+
length = 100
29+
string = %r{\A[a-zA-Z0-9]{#{length}}\z}
30+
expect(fqdn_rand_string(length, :charset => nil).match(string)).not_to eq(nil)
31+
end
32+
33+
it "provides a valid alphanumeric string when an empty character set is provided" do
34+
length = 100
35+
string = %r{\A[a-zA-Z0-9]{#{length}}\z}
36+
expect(fqdn_rand_string(length, :charset => '').match(string)).not_to eq(nil)
37+
end
38+
39+
it "uses a provided character set" do
40+
length = 100
41+
charset = '!@#$%^&*()-_=+'
42+
string = %r{\A[#{charset}]{#{length}}\z}
43+
expect(fqdn_rand_string(length, :charset => charset).match(string)).not_to eq(nil)
44+
end
45+
46+
it "provides a random string exactly as long as the given length" do
47+
expect(fqdn_rand_string(10).size).to eql(10)
48+
end
49+
50+
it "provides the same 'random' value on subsequent calls for the same host" do
51+
expect(fqdn_rand_string(10)).to eql(fqdn_rand_string(10))
52+
end
53+
54+
it "considers the same host and same extra arguments to have the same random sequence" do
55+
first_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"])
56+
second_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"])
57+
58+
expect(first_random).to eql(second_random)
59+
end
60+
61+
it "allows extra arguments to control the random value on a single host" do
62+
first_random = fqdn_rand_string(10, :extra_identifier => [1, "different", "host"])
63+
second_different_random = fqdn_rand_string(10, :extra_identifier => [2, "different", "host"])
64+
65+
expect(first_random).not_to eql(second_different_random)
66+
end
67+
68+
it "should return different strings for different hosts" do
69+
val1 = fqdn_rand_string(10, :host => "first.host.com")
70+
val2 = fqdn_rand_string(10, :host => "second.host.com")
71+
72+
expect(val1).not_to eql(val2)
73+
end
74+
75+
def fqdn_rand_string(max, args = {})
76+
host = args[:host] || '127.0.0.1'
77+
charset = args[:charset]
78+
extra = args[:extra_identifier] || []
79+
80+
scope = PuppetlabsSpec::PuppetInternals.scope
81+
scope.stubs(:[]).with("::fqdn").returns(host)
82+
scope.stubs(:lookupvar).with("::fqdn").returns(host)
83+
84+
function_args = [max]
85+
if args.has_key?(:charset) or !extra.empty?
86+
function_args << charset
87+
end
88+
function_args += extra
89+
scope.function_fqdn_rand_string(function_args)
90+
end
91+
end

0 commit comments

Comments
 (0)