Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(MODULES-8886) Revert removal of deepmerge function #1181

Merged
merged 2 commits into from
Apr 12, 2019
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
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