Skip to content

Commit

Permalink
Merge pull request puppetlabs#1181 from eimlav/modules-8886
Browse files Browse the repository at this point in the history
(MODULES-8886) Revert removal of deepmerge function
  • Loading branch information
david22swan authored Apr 12, 2019
2 parents 4ed5cba + 6b7d88b commit 630522c
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 2 deletions.
67 changes: 67 additions & 0 deletions lib/puppet/functions/mysql/normalise_and_deepmerge.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# @summary Recursively merges two or more hashes together, normalises keys with differing use of dashesh and underscores,
# then returns the resulting hash.
#
# @example
# $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }
# $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } }
# $merged_hash = mysql::normalise_and_deepmerge($hash1, $hash2)
# # The resulting hash is equivalent to:
# # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } }
#
# - When there is a duplicate key that is a hash, they are recursively merged.
# - When there is a duplicate key that is not a hash, the key in the rightmost hash will "win."
# - When there are conficting uses of dashes and underscores in two keys (which mysql would otherwise equate), the rightmost style will win.
#
Puppet::Functions.create_function(:'mysql::normalise_and_deepmerge') do
def normalise_and_deepmerge(*args)
if args.length < 2
raise Puppet::ParseError, _('mysql::normalise_and_deepmerge(): wrong number of arguments (%{args_length}; must be at least 2)') % { args_length: args.length }
end

result = {}
args.each do |arg|
next if arg.is_a?(String) && arg.empty? # empty string is synonym for puppet's undef
# If the argument was not a hash, skip it.
unless arg.is_a?(Hash)
raise Puppet::ParseError, _('mysql::normalise_and_deepmerge: unexpected argument type %{arg_class}, only expects hash arguments.') % { args_class: args.class }
end

# We need to make a copy of the hash since it is frozen by puppet
current = deep_copy(arg)

# Now we have to traverse our hash assigning our non-hash values
# to the matching keys in our result while following our hash values
# and repeating the process.
overlay(result, current)
end
result
end

def normalized?(hash, key)
return true if hash.key?(key)
return false unless key =~ %r{-|_}
other_key = key.include?('-') ? key.tr('-', '_') : key.tr('_', '-')
return false unless hash.key?(other_key)
hash[key] = hash.delete(other_key)
true
end

def overlay(hash1, hash2)
hash2.each do |key, value|
if normalized?(hash1, key) && value.is_a?(Hash) && hash1[key].is_a?(Hash)
overlay(hash1[key], value)
else
hash1[key] = value
end
end
end

def deep_copy(inputhash)
return inputhash unless inputhash.is_a? Hash
hash = {}
inputhash.each do |k, v|
hash.store(k, deep_copy(v))
end
hash
end
end
2 changes: 1 addition & 1 deletion manifests/backup/mysqlbackup.pp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
'password' => $backuppassword,
}
}
$options = $default_options.deep_merge($mysql::server::override_options)
$options = mysql::normalise_and_deepmerge($default_options, $mysql::server::override_options)

file { 'mysqlbackup-config-file':
path => '/etc/mysql/conf.d/meb.cnf',
Expand Down
2 changes: 1 addition & 1 deletion manifests/server.pp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
}

# Create a merged together set of options. Rightmost hashes win over left.
$options = $mysql::params::default_options.deep_merge($override_options)
$options = mysql::normalise_and_deepmerge($mysql::params::default_options, $override_options)

Class['mysql::server::root_password'] -> Mysql::Db <| |>

Expand Down
92 changes: 92 additions & 0 deletions spec/functions/mysql_normalise_and_deepmerge_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
require 'spec_helper'

describe 'mysql::normalise_and_deepmerge' do
it 'exists' do
is_expected.not_to eq(nil)
end

it 'throws error with no arguments' do
is_expected.to run.with_params.and_raise_error(Puppet::ParseError)
end

it 'throws error with only one argument' do
is_expected.to run.with_params('one' => 1).and_raise_error(Puppet::ParseError)
end

it 'accepts empty strings as puppet undef' do
is_expected.to run.with_params({}, '')
end

# rubocop:disable RSpec/NamedSubject
index_values = ['one', 'two', 'three']
expected_values_one = ['1', '2', '2']
it 'merge two hashes' do
new_hash = subject.execute({ 'one' => '1', 'two' => '1' }, 'two' => '2', 'three' => '2')
index_values.each_with_index do |index, expected|
expect(new_hash[index]).to eq(expected_values_one[expected])
end
end

it 'merges multiple hashes' do
hash = subject.execute({ 'one' => 1 }, { 'one' => '2' }, 'one' => '3')
expect(hash['one']).to eq('3')
end

it 'accepts empty hashes' do
is_expected.to run.with_params({}, {}, {}).and_return({})
end

expected_values_two = [1, 2, 'four' => 4]
it 'merges subhashes' do
hash = subject.execute({ 'one' => 1 }, 'two' => 2, 'three' => { 'four' => 4 })
index_values.each_with_index do |index, expected|
expect(hash[index]).to eq(expected_values_two[expected])
end
end

it 'appends to subhashes' do
hash = subject.execute({ 'one' => { 'two' => 2 } }, 'one' => { 'three' => 3 })
expect(hash['one']).to eq('two' => 2, 'three' => 3)
end

expected_values_three = [1, 'dos', { 'four' => 4, 'five' => 5 }]
it 'appends to subhashes 2' do
hash = subject.execute({ 'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, 'two' => 'dos', 'three' => { 'five' => 5 })
index_values.each_with_index do |index, expected|
expect(hash[index]).to eq(expected_values_three[expected])
end
end

index_values_two = ['key1', 'key2']
expected_values_four = [{ 'a' => 1, 'b' => 99 }, 'c' => 3]
it 'appends to subhashes 3' do
hash = subject.execute({ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, 'key1' => { 'b' => 99 })
index_values_two.each_with_index do |index, expected|
expect(hash[index]).to eq(expected_values_four[expected])
end
end

it 'equates keys mod dash and underscore #value' do
hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10)
expect(hash['a_b_c']).to eq(10)
end
it 'equates keys mod dash and underscore #not' do
hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10)
expect(hash).not_to have_key('a-b-c')
end

index_values_three = ['a_b_c', 'b-c-d']
expected_values_five = [10, { 'e-f-g' => 3, 'c_d_e' => 12 }]
index_values_error = ['a-b-c', 'b_c_d']
index_values_three.each_with_index do |index, expected|
it 'keeps style of the last when keys are equal mod dash and underscore #value' do
hash = subject.execute({ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 } }, 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 })
expect(hash[index]).to eq(expected_values_five[expected])
end
it 'keeps style of the last when keys are equal mod dash and underscore #not' do
hash = subject.execute({ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 } }, 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 })
expect(hash).not_to have_key(index_values_error[expected])
end
end
# rubocop:enable RSpec/NamedSubject
end

0 comments on commit 630522c

Please sign in to comment.