Skip to content

Commit

Permalink
Refactor config item matching (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmhughes authored Dec 20, 2024
1 parent d68b4ec commit f0ba82c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ This file is used to list changes made in each version of the chef_auto_accumula

## Unreleased

- Refactor config item matching

## 0.6.1 - *2024-07-09*

- Bump gem dependency versions
Expand Down
43 changes: 35 additions & 8 deletions libraries/chef_auto_accumulator/config/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,23 @@ def load_config_file_section_item(config_file)
log_chef(:debug) { "Resultant path\n#{debug_var_output(search_object)}" }
search_object
else
config.select { |cs| match.any? { |mk, mv| kv_test_log(cs, mk, mv) } }
index = config_item_index_match(config, match)
nil_or_empty?(index) ? nil : config.values_at(*index)
end

log_chef(:debug) { "Filtered items\n#{debug_var_output(item)}" }

return if item.nil?

raise unless item.one? || item.empty?
item = item.first
raise "Expected one or no items to be filtered, got #{item.count}" unless item.one? || item.empty?

if item
log_chef(:info) { "#{config_file} got Match for Filter\n#{debug_var_output(match)}\n\nResult\n\n#{debug_var_output(item)}" }
else
log_chef(:info) { "#{config_file} got No Match for Filter\n#{debug_var_output(match)}" }
log_chef(:warn) { "#{config_file} got No Match for Filter\n#{debug_var_output(match)}" }
end

item
item.first
rescue KeyError
nil
end
Expand All @@ -147,9 +147,9 @@ def load_config_file_section_contained_item(config_file)
end

match = option_config_match
log_chef(:trace) { "Filtering against K/V pairs #{debug_var_output(match)}" }

item = outer_key_config.filter { |object| match.any? { |k, v| kv_test_log(object, k, v) } }
log_chef(:debug) { "Filtering against K/V pairs #{debug_var_output(match)}" }
index = config_item_index_match(outer_key_config, match)
item = nil_or_empty?(index) ? nil : outer_key_config.values_at(*index)

if nil_or_empty?(item)
log_chef(:info) { "#{config_file} got No Match for Filter\n#{debug_var_output(match)}" }
Expand Down Expand Up @@ -184,6 +184,33 @@ def config_file_config_present?
!config.nil?
end

# Match a configuration item against multiple conditions, return the highest matching element
#
# @param config [Array] The configuration set to search
# @param match [Hash] The match criteria
# @return [Any] Matched result
#
def config_item_index_match(config, match)
return if nil_or_empty?(config)
raise FileConfigPathFilterError, 'Empty resource match filter set' if nil_or_empty?(match)

# Find the config items that match at least one of the match filters, then sort to bring the high matches to the top
matched_items = config.each_with_index.map { |c, i| [i, match.map { |k, v| kv_test_log(c, k, v) }.count(true)] }.filter { |_, c| c.positive? }
matched_items.sort_by! { |_, count| -count }

return unless matched_items.count.positive?

# Get the number of matches for the 'best' match then find out how many items matched at this level
best_match = matched_items.first.last
index = matched_items.filter { |_, c| c.eql?(best_match) }.map(&:first)

unless index.one?
log_chef(:warn) { "Expected either one or zero filtered configuration items, got #{index.count}. Data:\n#{debug_var_output(index)}" }
end

index
end

# Error to raise when failing to filter a single containing resource from a parent path
class FileConfigPathFilterError < FilterError; end
end
Expand Down
44 changes: 23 additions & 21 deletions libraries/chef_auto_accumulator/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -255,27 +255,29 @@ def accumulator_config_array_index
match = option_config_match

# Find the Array index for the configuration object that matches the resource definition
index = case option_config_path_type
when :array
log_chef(:debug) { "Testing :array for #{debug_var_output(match)}" }

array_path = accumulator_config_path_init(action, *path)
array_path.each_with_index.select { |obj, _| match.any? { |k, v| kv_test_log(obj, k, v) } }.map(&:last)
when :array_contained
ck = accumulator_config_path_containing_key

log_chef(:debug) { "Searching :array_contained #{debug_var_output(ck)} against #{debug_var_output(match)}" }

array_cpath = accumulator_config_containing_path_init(action: action, path: path)
return unless array_cpath

# Fetch the containing key and filter for any objects that match the filter
array_cpath.fetch(ck, []).each_with_index.select { |obj, _| match.any? { |k, v| kv_test_log(obj, k, v) } }.map(&:last)
else
raise ArgumentError "Unknown config path type #{debug_var_output(option_config_path_type)}"
end

index.reverse! # We need the indexes in reverse order so we delete correctly, otherwise the shift will result in left over objects we intended to delete
array_path = case option_config_path_type
when :array
log_chef(:debug) { "Testing :array for #{debug_var_output(match)}" }
accumulator_config_path_init(action, *path)
when :array_contained
ck = accumulator_config_path_containing_key

log_chef(:debug) { "Searching :array_contained #{debug_var_output(ck)} against #{debug_var_output(match)}" }

array_path = accumulator_config_containing_path_init(action: action, path: path)
return unless array_path

# Fetch the containing key and filter for any objects that match the filter
array_path = array_path.fetch(ck, [])
log_chef(:debug) { "Path: #{debug_var_output(array_path)}" }
array_path
else
raise ArgumentError "Unknown config path type #{debug_var_output(option_config_path_type)}"
end

index = config_item_index_match(array_path, match)
# We need the indexes in reverse order so we delete correctly, otherwise the shift will result in left over objects we intended to delete
index.reverse! unless nil_or_empty?(index)
log_chef(:debug) { "Result #{debug_var_output(index)}" }

index
Expand Down

0 comments on commit f0ba82c

Please sign in to comment.