From cc0454e4c37dfac73d15b589e0a828f1b88c0a3c Mon Sep 17 00:00:00 2001 From: Alexander Fisher Date: Wed, 26 Apr 2023 13:02:48 +0100 Subject: [PATCH] Modernise `fqdn_rotate` function Convert the function to the modern function API as a namespaced function and use the `networking` fact instead of legacy facts. A non-namespaced shim is also created (but marked deprecated), to preserve compatibility. Fixes #1381 --- lib/puppet/functions/fqdn_rotate.rb | 14 +++++ lib/puppet/functions/stdlib/fqdn_rotate.rb | 66 ++++++++++++++++++++++ lib/puppet/parser/functions/fqdn_rotate.rb | 59 ------------------- spec/functions/fqdn_rotate_spec.rb | 12 ++-- 4 files changed, 85 insertions(+), 66 deletions(-) create mode 100644 lib/puppet/functions/fqdn_rotate.rb create mode 100644 lib/puppet/functions/stdlib/fqdn_rotate.rb delete mode 100644 lib/puppet/parser/functions/fqdn_rotate.rb diff --git a/lib/puppet/functions/fqdn_rotate.rb b/lib/puppet/functions/fqdn_rotate.rb new file mode 100644 index 000000000..2f067fe43 --- /dev/null +++ b/lib/puppet/functions/fqdn_rotate.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# THIS FILE WAS GENERATED BY `rake regenerate_unamespaced_shims` + +# @summary DEPRECATED. Use the namespaced function [`stdlib::fqdn_rotate`](#stdlibfqdn_rotate) instead. +Puppet::Functions.create_function(:fqdn_rotate) do + dispatch :deprecation_gen do + repeated_param 'Any', :args + end + def deprecation_gen(*args) + call_function('deprecation', 'fqdn_rotate', 'This function is deprecated, please use stdlib::fqdn_rotate instead.', false) + call_function('stdlib::fqdn_rotate', *args) + end +end diff --git a/lib/puppet/functions/stdlib/fqdn_rotate.rb b/lib/puppet/functions/stdlib/fqdn_rotate.rb new file mode 100644 index 000000000..5d7121b28 --- /dev/null +++ b/lib/puppet/functions/stdlib/fqdn_rotate.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# @summary Rotates an array or string a random number of times, combining the `fqdn` fact and an optional seed for repeatable randomness. +Puppet::Functions.create_function(:'stdlib::fqdn_rotate') do + # @param input + # The String you want rotated a random number of times + # @param seeds + # One of more values to use as a custom seed. These will be combined with the host's FQDN + # + # @return [String] Returns the rotated String + # + # @example Rotating a String + # stdlib::fqdn_rotate('abcd') + # @example Using a custom seed + # stdlib::fqdn_rotate('abcd', 'custom seed') + dispatch :fqdn_rotate_string do + param 'String', :input + optional_repeated_param 'Variant[Integer,String]', :seeds + return_type 'String' + end + + # @param input + # The Array you want rotated a random number of times + # @param seeds + # One of more values to use as a custom seed. These will be combined with the host's FQDN + # + # @return [String] Returns the rotated Array + # + # @example Rotating an Array + # stdlib::fqdn_rotate(['a', 'b', 'c', 'd']) + # @example Using custom seeds + # stdlib::fqdn_rotate([1, 2, 3], 'custom', 'seed', 1) + dispatch :fqdn_rotate_array do + param 'Array', :input + optional_repeated_param 'Variant[Integer,String]', :seeds + return_type 'Array' + end + + def fqdn_rotate_array(input, *seeds) + # Check whether it makes sense to rotate ... + return input if input.size <= 1 + + result = input.clone + + require 'digest/md5' + seed = Digest::MD5.hexdigest([fqdn_fact, seeds].join(':')).hex + + offset = Puppet::Util.deterministic_rand(seed, result.size).to_i + + offset.times do + result.push result.shift + end + + result + end + + def fqdn_rotate_string(input, *seeds) + fqdn_rotate_array(input.chars, seeds).join + end + + private + + def fqdn_fact + closure_scope['facts']['networking']['fqdn'] + end +end diff --git a/lib/puppet/parser/functions/fqdn_rotate.rb b/lib/puppet/parser/functions/fqdn_rotate.rb deleted file mode 100644 index 1437caa38..000000000 --- a/lib/puppet/parser/functions/fqdn_rotate.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -# -# fqdn_rotate.rb -# -Puppet::Parser::Functions.newfunction(:fqdn_rotate, type: :rvalue, doc: <<-DOC - @summary - Rotates an array or string a random number of times, combining the `$fqdn` fact - and an optional seed for repeatable randomness. - - @return - rotated array or string - - @example Example Usage: - fqdn_rotate(['a', 'b', 'c', 'd']) - fqdn_rotate('abcd') - fqdn_rotate([1, 2, 3], 'custom seed') -DOC -) do |args| - raise(Puppet::ParseError, "fqdn_rotate(): Wrong number of arguments given (#{args.size} for 1)") if args.empty? - - value = args.shift - require 'digest/md5' - - raise(Puppet::ParseError, 'fqdn_rotate(): Requires either array or string to work with') unless value.is_a?(Array) || value.is_a?(String) - - result = value.clone - - string = value.is_a?(String) - - # Check whether it makes sense to rotate ... - return result if result.size <= 1 - - # We turn any string value into an array to be able to rotate ... - result = string ? result.chars : result - - elements = result.size - - seed = Digest::MD5.hexdigest([lookupvar('facts'), args].join(':')).hex - # deterministic_rand() was added in Puppet 3.2.0; reimplement if necessary - if Puppet::Util.respond_to?(:deterministic_rand) - offset = Puppet::Util.deterministic_rand(seed, elements).to_i - else - return offset = Random.new(seed).rand(elements) if defined?(Random) == 'constant' && Random.instance_of?(Class) - - old_seed = srand(seed) - offset = rand(elements) - srand(old_seed) - end - offset.times do - result.push result.shift - end - - result = string ? result.join : result - - return result -end - -# vim: set ts=2 sw=2 et : diff --git a/spec/functions/fqdn_rotate_spec.rb b/spec/functions/fqdn_rotate_spec.rb index ea0e2ddd5..7906c42c2 100644 --- a/spec/functions/fqdn_rotate_spec.rb +++ b/spec/functions/fqdn_rotate_spec.rb @@ -4,9 +4,9 @@ describe 'fqdn_rotate' do it { is_expected.not_to be_nil } - it { is_expected.to run.with_params.and_raise_error(Puppet::ParseError, %r{wrong number of arguments}i) } - it { is_expected.to run.with_params(0).and_raise_error(Puppet::ParseError, %r{Requires either array or string to work with}) } - it { is_expected.to run.with_params({}).and_raise_error(Puppet::ParseError, %r{Requires either array or string to work with}) } + it { is_expected.to run.with_params.and_raise_error(ArgumentError, %r{expects at least 1 argument, got none}) } + it { is_expected.to run.with_params(0).and_raise_error(ArgumentError, %r{parameter 'input' expects a value of type String or Array, got Integer}) } + it { is_expected.to run.with_params({}).and_raise_error(ArgumentError, %r{parameter 'input' expects a value of type String or Array, got Hash}) } it { is_expected.to run.with_params('').and_return('') } it { is_expected.to run.with_params('a').and_return('a') } it { is_expected.to run.with_params('ã').and_return('ã') } @@ -48,8 +48,6 @@ end it 'uses the Puppet::Util.deterministic_rand function' do - skip 'Puppet::Util#deterministic_rand not available' unless Puppet::Util.respond_to?(:deterministic_rand) - expect(Puppet::Util).to receive(:deterministic_rand).with(44_489_829_212_339_698_569_024_999_901_561_968_770, 4) fqdn_rotate('asdf') end @@ -68,9 +66,9 @@ def fqdn_rotate(value, args = {}) # workaround not being able to use let(:facts) because some tests need # multiple different hostnames in one context - allow(scope).to receive(:lookupvar).with('facts').and_return(host) + allow(subject.func.closure_scope).to receive(:[]).with('facts').and_return({ 'networking' => { 'fqdn' => host } }) function_args = [value] + extra - scope.function_fqdn_rotate(function_args) + scope.call_function('fqdn_rotate', function_args) end end