Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,23 @@ This function flattens any deeply nested arrays and returns a single flat array
Returns the largest integer less than or equal to the argument.
Takes a single numeric value as an argument. *Type*: rvalue

#### `fqdn_rand_string`

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.

*Usage:*
```
fqdn_rand_string(LENGTH, [CHARSET], [SEED])
```
*Examples:*
```
fqdn_rand_string(10)
fqdn_rand_string(10, 'ABCDEF!@#$%^')
fqdn_rand_string(10, '', 'custom seed')
```

*Type*: rvalue

#### `fqdn_rotate`

Rotates an array a random number of times based on a node's fqdn. *Type*: rvalue
Expand Down
34 changes: 34 additions & 0 deletions lib/puppet/parser/functions/fqdn_rand_string.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Puppet::Parser::Functions::newfunction(
:fqdn_rand_string,
:arity => -2,
:type => :rvalue,
:doc => "Usage: `fqdn_rand_string(LENGTH, [CHARSET], [SEED])`. LENGTH is
required and must be a positive integer. CHARSET is optional and may be
`undef` or a string. SEED is optional and may be any number or string.

Generates a random string LENGTH characters long using the character set
provided by CHARSET, combining the `$fqdn` fact and the value of SEED for
repeatable randomness. (That is, each node will get a different random
string from this function, but a given node's result will be the same every
time unless its hostname changes.) Adding a SEED can be useful if you need
more than one unrelated string. CHARSET will default to alphanumeric if
`undef` or an empty string.") do |args|
raise(ArgumentError, "fqdn_rand_string(): wrong number of arguments (0 for 1)") if args.size == 0
Puppet::Parser::Functions.function('is_integer')
raise(ArgumentError, "fqdn_rand_base64(): first argument must be a positive integer") unless function_is_integer([args[0]]) and args[0].to_i > 0
raise(ArgumentError, "fqdn_rand_base64(): second argument must be undef or a string") unless args[1].nil? or args[1].is_a? String

Puppet::Parser::Functions.function('fqdn_rand')

length = args.shift.to_i
charset = args.shift.to_s.chars.to_a

charset = (0..9).map { |i| i.to_s } + ('A'..'Z').to_a + ('a'..'z').to_a if charset.empty?

rand_string = ''
for current in 1..length
rand_string << charset[function_fqdn_rand([charset.size, (args + [current.to_s]).join(':')]).to_i]
end

rand_string
end
60 changes: 60 additions & 0 deletions spec/acceptance/fqdn_rand_base64_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#! /usr/bin/env ruby -S rspec
require 'spec_helper_acceptance'

describe 'fqdn_rand_base64 function', :unless => unsupported_platforms.include?(fact('operatingsystem')) do
describe 'success' do
let(:facts_d) do
if fact('is_pe', '--puppet') == "true"
if fact('osfamily') =~ /windows/i
if fact('kernelmajversion').to_f < 6.0
'c:/documents and settings/all users/application data/puppetlabs/facter/facts.d'
else
'c:/programdata/puppetlabs/facter/facts.d'
end
else
'/etc/puppetlabs/facter/facts.d'
end
else
'/etc/facter/facts.d'
end
end
after :each do
shell("if [ -f '#{facts_d}/fqdn.txt' ] ; then rm '#{facts_d}/fqdn.txt' ; fi")
end
before :each do
#no need to create on windows, pe creates by default
if fact('osfamily') !~ /windows/i
shell("mkdir -p '#{facts_d}'")
end
end
it 'generates random base64 strings' do
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
pp = <<-eos
$l = 10
$o = fqdn_rand_base64($l)
notice(inline_template('fqdn_rand_base64 is <%= @o.inspect %>'))
eos

apply_manifest(pp, :catch_failures => true) do |r|
expect(r.stdout).to match(/fqdn_rand_base64 is "8ySYp0dq0B"/)
end
end
it 'generates random base64 strings with custom seeds' do
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
pp = <<-eos
$l = 10
$s = 'seed'
$o = fqdn_rand_base64($l, $s)
notice(inline_template('fqdn_rand_base64 is <%= @o.inspect %>'))
eos

apply_manifest(pp, :catch_failures => true) do |r|
expect(r.stdout).to match(/fqdn_rand_base64 is "6J2c4oMRUJ"/)
end
end
end
describe 'failure' do
it 'handles improper argument counts'
it 'handles non-numbers for length argument'
end
end
60 changes: 60 additions & 0 deletions spec/acceptance/fqdn_rand_string_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#! /usr/bin/env ruby -S rspec
require 'spec_helper_acceptance'

describe 'fqdn_rand_string function', :unless => unsupported_platforms.include?(fact('operatingsystem')) do
describe 'success' do
let(:facts_d) do
if fact('is_pe', '--puppet') == "true"
if fact('osfamily') =~ /windows/i
if fact('kernelmajversion').to_f < 6.0
'c:/documents and settings/all users/application data/puppetlabs/facter/facts.d'
else
'c:/programdata/puppetlabs/facter/facts.d'
end
else
'/etc/puppetlabs/facter/facts.d'
end
else
'/etc/facter/facts.d'
end
end
after :each do
shell("if [ -f '#{facts_d}/fqdn.txt' ] ; then rm '#{facts_d}/fqdn.txt' ; fi")
end
before :each do
#no need to create on windows, pe creates by default
if fact('osfamily') !~ /windows/i
shell("mkdir -p '#{facts_d}'")
end
end
it 'generates random alphanumeric strings' do
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
pp = <<-eos
$l = 10
$o = fqdn_rand_string($l)
notice(inline_template('fqdn_rand_string is <%= @o.inspect %>'))
eos

apply_manifest(pp, :catch_failures => true) do |r|
expect(r.stdout).to match(/fqdn_rand_string is "7oDp0KOr1b"/)
end
end
it 'generates random alphanumeric strings with custom seeds' do
shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
pp = <<-eos
$l = 10
$s = 'seed'
$o = fqdn_rand_string($l, $s)
notice(inline_template('fqdn_rand_string is <%= @o.inspect %>'))
eos

apply_manifest(pp, :catch_failures => true) do |r|
expect(r.stdout).to match(/fqdn_rand_string is "3HS4mbuI3E"/)
end
end
end
describe 'failure' do
it 'handles improper argument counts'
it 'handles non-numbers for length argument'
end
end
91 changes: 91 additions & 0 deletions spec/functions/fqdn_rand_string_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#! /usr/bin/env ruby -S rspec
require 'spec_helper'

describe "the fqdn_rand_string function" do
let(:scope) { PuppetlabsSpec::PuppetInternals.scope }

it "should exist" do
expect(Puppet::Parser::Functions.function("fqdn_rand_string")).to eq("function_fqdn_rand_string")
end

it "should raise an ArgumentError if there is less than 1 argument" do
expect { fqdn_rand_string() }.to( raise_error(ArgumentError, /wrong number of arguments/))
end

it "should raise an ArgumentError if argument 1 isn't a positive integer" do
expect { fqdn_rand_string(0) }.to( raise_error(ArgumentError, /first argument must be a positive integer/))
expect { fqdn_rand_string(-1) }.to( raise_error(ArgumentError, /first argument must be a positive integer/))
expect { fqdn_rand_string(0.5) }.to( raise_error(ArgumentError, /first argument must be a positive integer/))
end

it "provides a valid alphanumeric string when no character set is provided" do
length = 100
string = %r{\A[a-zA-Z0-9]{#{length}}\z}
expect(fqdn_rand_string(length).match(string)).not_to eq(nil)
end

it "provides a valid alphanumeric string when an undef character set is provided" do
length = 100
string = %r{\A[a-zA-Z0-9]{#{length}}\z}
expect(fqdn_rand_string(length, :charset => nil).match(string)).not_to eq(nil)
end

it "provides a valid alphanumeric string when an empty character set is provided" do
length = 100
string = %r{\A[a-zA-Z0-9]{#{length}}\z}
expect(fqdn_rand_string(length, :charset => '').match(string)).not_to eq(nil)
end

it "uses a provided character set" do
length = 100
charset = '!@#$%^&*()-_=+'
string = %r{\A[#{charset}]{#{length}}\z}
expect(fqdn_rand_string(length, :charset => charset).match(string)).not_to eq(nil)
end

it "provides a random string exactly as long as the given length" do
expect(fqdn_rand_string(10).size).to eql(10)
end

it "provides the same 'random' value on subsequent calls for the same host" do
expect(fqdn_rand_string(10)).to eql(fqdn_rand_string(10))
end

it "considers the same host and same extra arguments to have the same random sequence" do
first_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"])
second_random = fqdn_rand_string(10, :extra_identifier => [1, "same", "host"])

expect(first_random).to eql(second_random)
end

it "allows extra arguments to control the random value on a single host" do
first_random = fqdn_rand_string(10, :extra_identifier => [1, "different", "host"])
second_different_random = fqdn_rand_string(10, :extra_identifier => [2, "different", "host"])

expect(first_random).not_to eql(second_different_random)
end

it "should return different strings for different hosts" do
val1 = fqdn_rand_string(10, :host => "first.host.com")
val2 = fqdn_rand_string(10, :host => "second.host.com")

expect(val1).not_to eql(val2)
end

def fqdn_rand_string(max, args = {})
host = args[:host] || '127.0.0.1'
charset = args[:charset]
extra = args[:extra_identifier] || []

scope = PuppetlabsSpec::PuppetInternals.scope
scope.stubs(:[]).with("::fqdn").returns(host)
scope.stubs(:lookupvar).with("::fqdn").returns(host)

function_args = [max]
if args.has_key?(:charset) or !extra.empty?
function_args << charset
end
function_args += extra
scope.function_fqdn_rand_string(function_args)
end
end