Skip to content

Commit 630522c

Browse files
authored
Merge pull request #1181 from eimlav/modules-8886
(MODULES-8886) Revert removal of deepmerge function
2 parents 4ed5cba + 6b7d88b commit 630522c

File tree

4 files changed

+161
-2
lines changed

4 files changed

+161
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# @summary Recursively merges two or more hashes together, normalises keys with differing use of dashesh and underscores,
2+
# then returns the resulting hash.
3+
#
4+
# @example
5+
# $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }
6+
# $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } }
7+
# $merged_hash = mysql::normalise_and_deepmerge($hash1, $hash2)
8+
# # The resulting hash is equivalent to:
9+
# # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } }
10+
#
11+
# - When there is a duplicate key that is a hash, they are recursively merged.
12+
# - When there is a duplicate key that is not a hash, the key in the rightmost hash will "win."
13+
# - When there are conficting uses of dashes and underscores in two keys (which mysql would otherwise equate), the rightmost style will win.
14+
#
15+
Puppet::Functions.create_function(:'mysql::normalise_and_deepmerge') do
16+
def normalise_and_deepmerge(*args)
17+
if args.length < 2
18+
raise Puppet::ParseError, _('mysql::normalise_and_deepmerge(): wrong number of arguments (%{args_length}; must be at least 2)') % { args_length: args.length }
19+
end
20+
21+
result = {}
22+
args.each do |arg|
23+
next if arg.is_a?(String) && arg.empty? # empty string is synonym for puppet's undef
24+
# If the argument was not a hash, skip it.
25+
unless arg.is_a?(Hash)
26+
raise Puppet::ParseError, _('mysql::normalise_and_deepmerge: unexpected argument type %{arg_class}, only expects hash arguments.') % { args_class: args.class }
27+
end
28+
29+
# We need to make a copy of the hash since it is frozen by puppet
30+
current = deep_copy(arg)
31+
32+
# Now we have to traverse our hash assigning our non-hash values
33+
# to the matching keys in our result while following our hash values
34+
# and repeating the process.
35+
overlay(result, current)
36+
end
37+
result
38+
end
39+
40+
def normalized?(hash, key)
41+
return true if hash.key?(key)
42+
return false unless key =~ %r{-|_}
43+
other_key = key.include?('-') ? key.tr('-', '_') : key.tr('_', '-')
44+
return false unless hash.key?(other_key)
45+
hash[key] = hash.delete(other_key)
46+
true
47+
end
48+
49+
def overlay(hash1, hash2)
50+
hash2.each do |key, value|
51+
if normalized?(hash1, key) && value.is_a?(Hash) && hash1[key].is_a?(Hash)
52+
overlay(hash1[key], value)
53+
else
54+
hash1[key] = value
55+
end
56+
end
57+
end
58+
59+
def deep_copy(inputhash)
60+
return inputhash unless inputhash.is_a? Hash
61+
hash = {}
62+
inputhash.each do |k, v|
63+
hash.store(k, deep_copy(v))
64+
end
65+
hash
66+
end
67+
end

manifests/backup/mysqlbackup.pp

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
'password' => $backuppassword,
9494
}
9595
}
96-
$options = $default_options.deep_merge($mysql::server::override_options)
96+
$options = mysql::normalise_and_deepmerge($default_options, $mysql::server::override_options)
9797

9898
file { 'mysqlbackup-config-file':
9999
path => '/etc/mysql/conf.d/meb.cnf',

manifests/server.pp

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
}
117117

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

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
require 'spec_helper'
2+
3+
describe 'mysql::normalise_and_deepmerge' do
4+
it 'exists' do
5+
is_expected.not_to eq(nil)
6+
end
7+
8+
it 'throws error with no arguments' do
9+
is_expected.to run.with_params.and_raise_error(Puppet::ParseError)
10+
end
11+
12+
it 'throws error with only one argument' do
13+
is_expected.to run.with_params('one' => 1).and_raise_error(Puppet::ParseError)
14+
end
15+
16+
it 'accepts empty strings as puppet undef' do
17+
is_expected.to run.with_params({}, '')
18+
end
19+
20+
# rubocop:disable RSpec/NamedSubject
21+
index_values = ['one', 'two', 'three']
22+
expected_values_one = ['1', '2', '2']
23+
it 'merge two hashes' do
24+
new_hash = subject.execute({ 'one' => '1', 'two' => '1' }, 'two' => '2', 'three' => '2')
25+
index_values.each_with_index do |index, expected|
26+
expect(new_hash[index]).to eq(expected_values_one[expected])
27+
end
28+
end
29+
30+
it 'merges multiple hashes' do
31+
hash = subject.execute({ 'one' => 1 }, { 'one' => '2' }, 'one' => '3')
32+
expect(hash['one']).to eq('3')
33+
end
34+
35+
it 'accepts empty hashes' do
36+
is_expected.to run.with_params({}, {}, {}).and_return({})
37+
end
38+
39+
expected_values_two = [1, 2, 'four' => 4]
40+
it 'merges subhashes' do
41+
hash = subject.execute({ 'one' => 1 }, 'two' => 2, 'three' => { 'four' => 4 })
42+
index_values.each_with_index do |index, expected|
43+
expect(hash[index]).to eq(expected_values_two[expected])
44+
end
45+
end
46+
47+
it 'appends to subhashes' do
48+
hash = subject.execute({ 'one' => { 'two' => 2 } }, 'one' => { 'three' => 3 })
49+
expect(hash['one']).to eq('two' => 2, 'three' => 3)
50+
end
51+
52+
expected_values_three = [1, 'dos', { 'four' => 4, 'five' => 5 }]
53+
it 'appends to subhashes 2' do
54+
hash = subject.execute({ 'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, 'two' => 'dos', 'three' => { 'five' => 5 })
55+
index_values.each_with_index do |index, expected|
56+
expect(hash[index]).to eq(expected_values_three[expected])
57+
end
58+
end
59+
60+
index_values_two = ['key1', 'key2']
61+
expected_values_four = [{ 'a' => 1, 'b' => 99 }, 'c' => 3]
62+
it 'appends to subhashes 3' do
63+
hash = subject.execute({ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, 'key1' => { 'b' => 99 })
64+
index_values_two.each_with_index do |index, expected|
65+
expect(hash[index]).to eq(expected_values_four[expected])
66+
end
67+
end
68+
69+
it 'equates keys mod dash and underscore #value' do
70+
hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10)
71+
expect(hash['a_b_c']).to eq(10)
72+
end
73+
it 'equates keys mod dash and underscore #not' do
74+
hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10)
75+
expect(hash).not_to have_key('a-b-c')
76+
end
77+
78+
index_values_three = ['a_b_c', 'b-c-d']
79+
expected_values_five = [10, { 'e-f-g' => 3, 'c_d_e' => 12 }]
80+
index_values_error = ['a-b-c', 'b_c_d']
81+
index_values_three.each_with_index do |index, expected|
82+
it 'keeps style of the last when keys are equal mod dash and underscore #value' do
83+
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 })
84+
expect(hash[index]).to eq(expected_values_five[expected])
85+
end
86+
it 'keeps style of the last when keys are equal mod dash and underscore #not' do
87+
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 })
88+
expect(hash).not_to have_key(index_values_error[expected])
89+
end
90+
end
91+
# rubocop:enable RSpec/NamedSubject
92+
end

0 commit comments

Comments
 (0)