Skip to content

Commit 8f1c4b9

Browse files
committed
(MODULES-8886) Revert removal of deepmerge function
Reverting this change as the deepmerge function in stdlib does not work the same as the mysql function. The mysql config is generated with incorrect duplicate entries in which '-' and '_' are mixed up when using the stdlib function.
1 parent 0b8f65c commit 8f1c4b9

File tree

4 files changed

+162
-2
lines changed

4 files changed

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

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

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

0 commit comments

Comments
 (0)