Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
bmhughes committed Dec 20, 2024
1 parent cedd302 commit c668a07
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 121 deletions.
4 changes: 3 additions & 1 deletion libraries/chef_auto_accumulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@
# limitations under the License.
#

require_relative 'chef_auto_accumulator/version'
require_relative 'chef_auto_accumulator/_utils'
require_relative 'chef_auto_accumulator/version'
require_relative 'chef_auto_accumulator/error'

require_relative 'chef_auto_accumulator/config'
require_relative 'chef_auto_accumulator/file'
require_relative 'chef_auto_accumulator/resource'
require_relative 'chef_auto_accumulator/state'

# Base namespace to include for automatic accumulator functionality
module ChefAutoAccumulator
include ChefAutoAccumulator::Config
include ChefAutoAccumulator::File
include ChefAutoAccumulator::Resource
include ChefAutoAccumulator::State
include ChefAutoAccumulator::Utils
end
24 changes: 5 additions & 19 deletions libraries/chef_auto_accumulator/_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,6 @@ def action_class?
instance_variable_defined?(:@new_resource)
end

# Return the resource declared name
#
# @return [String]
#
def resource_declared_name
instance_variable_defined?(:@new_resource) ? new_resource.name : name
end

# Return the resource declared type name
#
# @return [String]
#
def resource_type_name
instance_variable_defined?(:@new_resource) ? new_resource.declared_type.to_s : resource_name.to_s
end

# Return the formatted class name and value (if not Nil) of a variable for debug output
#
# @return [String] The formatted debug output
Expand All @@ -82,6 +66,8 @@ def debug_var_output(var, inspect = true)
var.to_s
end if var && inspect

output << "-- (Inspect disabled)" unless inspect

output.strip
end

Expand All @@ -102,12 +88,12 @@ def multi_is_a?(object, *classes)
# @param value [Any] Value to test against
# @return [true, false]
#
def kv_test_log(object, key, value)
def kv_test(object, key, value, log: true)
return false unless object.respond_to?(:fetch)

log_chef(:trace) { "Testing key #{debug_var_output(key)} and value #{debug_var_output(value)} against object #{debug_var_output(object)}" }
log_chef(:trace) { "Testing key #{debug_var_output(key)} and value #{debug_var_output(value)} against object #{debug_var_output(object)}" } if log
result = object.fetch(key, nil).eql?(value)
log_chef(:debug) { "Matched key #{debug_var_output(key)} and value #{debug_var_output(value)} against object #{debug_var_output(object)}" } if result
log_chef(:debug) { "Matched key #{debug_var_output(key)} and value #{debug_var_output(value)} against object #{debug_var_output(object)}" } if log && result

result
end
Expand Down
3 changes: 2 additions & 1 deletion libraries/chef_auto_accumulator/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@

require_relative 'config/accumulator'
require_relative 'config/file'
require_relative 'config/load_current_value'
require_relative 'config/path'

module ChefAutoAccumulator
# Config base module namespace
module Config
include ChefAutoAccumulator::Config::Accumulator
include ChefAutoAccumulator::Config::File
include ChefAutoAccumulator::Config::LoadCurrentValue
include ChefAutoAccumulator::Config::Path
end
end
8 changes: 6 additions & 2 deletions libraries/chef_auto_accumulator/config/accumulator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,18 @@ module Accumulator
def accumulator_config_path_contained_nested?
path_tuple = [ option_config_path_match_key, option_config_path_match_value, option_config_path_contained_key ]

log_chef(:debug) { "Path tuple option_config_path_match_key: #{debug_var_output(option_config_path_match_key)}"}
log_chef(:debug) { "Path tuple option_config_path_match_value: #{debug_var_output(option_config_path_match_value)}"}
log_chef(:debug) { "Path tuple option_config_path_contained_key: #{debug_var_output(option_config_path_contained_key)}"}

unless path_tuple.any? { |v| v.is_a?(Array) }
log_chef(:debug) { 'Config not nested' }
return false
end

# Verify all options are type Array
%w(option_config_path_match_key option_config_path_match_value option_config_path_contained_key).each do |opt|
opt_val = send(opt.to_sym)
%i(option_config_path_match_key option_config_path_match_value option_config_path_contained_key).each do |opt|
opt_val = send(opt)
next if opt_val.is_a?(Array)

raise ChefAutoAccumulator::Resource::Options::ResourceOptionMalformedError.new(resource_type_name, opt, opt_val, 'Array')
Expand Down
92 changes: 49 additions & 43 deletions libraries/chef_auto_accumulator/config/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,50 +19,56 @@

require_relative '../_utils'
require_relative '../file'
require_relative '../config'
require_relative '../resource'

require_relative '../resource/property'
require_relative '../resource/property_translation'

module ChefAutoAccumulator
module Config

# On disk configuration state access, required for load_current_value support
module File
private
class File
include ChefAutoAccumulator::File
include Config
include Resource

# Load the on disk configuration file
#
# @param config_file [String] The configuration file to load
# @return [Hash] Configuration file contents
#
def load_config_file(config_file, cache = true)
return unless ::File.exist?(config_file)
include ChefAutoAccumulator::Resource::Property
include ChefAutoAccumulator::Resource::PropertyTranslation

node.run_state['caa'] ||= {}
node.run_state['caa']['load_config_file'] ||= {}
attr_reader :contents, :file, :filetype
attr_accessor :new_resource

if node.run_state['caa']['load_config_file'].key?(config_file) && cache
log_chef(:debug) { "Returning #{config_file} from cache" }
log_chef(:trace) { "File #{config_file} data\n#{debug_var_output(node.run_state['caa']['load_config_file'][config_file])}" }
return node.run_state['caa']['load_config_file'][config_file]
end
def initialize(filetype: config_file_type, file: config_file, new_resource:)
raise FileError, "Configuration file #{file} does not exist" unless ::File.exist?(file)

@file = file
@filetype = filetype
@contents = nil
@new_resource = new_resource

true
end

node.run_state['caa']['load_config_file'][config_file] = load_file(config_file)
config = node.run_state['caa']['load_config_file'][config_file]
log_chef(:debug) { "Loading #{config_file} Count - #{config.count}" }
log_chef(:trace) { "Loading #{config_file}\n#{debug_var_output(config)}" }
def load!
@contents = load_file(file, filetype)

config
!nil_or_empty?(@contents)
end

# private

# Load a section from the on disk configuration file
#
# @param config_file [String] The configuration file to load
# @return [Hash] Configuration file contents
#
def load_config_file_section(config_file)
config = load_config_file(config_file)

return if nil_or_empty?(config)
def file_section
return if nil_or_empty?(@contents)

path = resource_config_path
section_config = config.dig(*path)
section_config = @contents.dig(*path)
log_chef(:debug) { "#{config_file} section #{path.join(' -> ')}\n#{debug_var_output(section_config, false)}" }
log_chef(:trace) { "#{config_file} section #{path.join(' -> ')} data\n#{debug_var_output(section_config)}" }

Expand All @@ -74,20 +80,17 @@ def load_config_file_section(config_file)
# @param config_file [String] The configuration file to load
# @return [Hash] Configuration item contents
#
def load_config_file_section_item(config_file)
config = load_config_file_section(config_file)

return if nil_or_empty?(config)
def file_section_item
return if nil_or_empty?(file_section)

match = option_config_match
log_chef(:debug) { "Filtering\n#{debug_var_output(match)}\n\nagainst\n\n#{debug_var_output(config, false)}" }
log_chef(:trace) { "Filter data\n#{debug_var_output(config)}" }
log_chef(:trace) { "Filter data\n#{debug_var_output(file_section)}" }

item = if accumulator_config_path_contained_nested?
filter_tuple = option_config_path_match_key.zip(option_config_path_match_value, option_config_path_contained_key.slice(0...-1))
log_chef(:trace) { "Zipped pairs #{debug_var_output(filter_tuple)}" }

search_object = config
search_object = file_section
log_chef(:trace) { "Initial search path set to #{debug_var_output(search_object)}" }

while (k, v, ck = filter_tuple.shift)
Expand All @@ -105,7 +108,9 @@ def load_config_file_section_item(config_file)
log_chef(:debug) { "Resultant path\n#{debug_var_output(search_object)}" }
search_object
else
index = config_item_index_match(config, match)
log_chef(:debug) { "Filtering\n#{debug_var_output(match)}\n\nagainst\n\n#{debug_var_output(file_section, true)}" }

index = config_item_index_match(file_section, match)
nil_or_empty?(index) ? nil : config.values_at(*index)
end

Expand All @@ -116,9 +121,9 @@ def load_config_file_section_item(config_file)
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)}" }
log_chef(:info) { "#{@file} got match for Filter\n#{debug_var_output(match)}\n\nResult\n\n#{debug_var_output(item)}" }
else
log_chef(:warn) { "#{config_file} got No Match for Filter\n#{debug_var_output(match)}" }
log_chef(:info) { "#{@file} got no match for Filter\n#{debug_var_output(match)}" }
end

item.first
Expand All @@ -131,16 +136,14 @@ def load_config_file_section_item(config_file)
# @param config_file [String] The configuration file to load
# @return [Hash] Contained configuration item contents
#
def load_config_file_section_contained_item(config_file)
config = load_config_file_section_item(config_file)

if nil_or_empty?(config)
def file_section_contained_item
if nil_or_empty?(file_section_item)
log_chef(:info) { 'Nil or empty config, returning' }
return
end

ck = accumulator_config_path_containing_key
outer_key_config = config.fetch(ck, nil)
outer_key_config = file_section_item.fetch(ck, nil)
if nil_or_empty?(outer_key_config)
log_chef(:info) { 'Nil or empty outer_key_config, returning' }
return
Expand All @@ -152,10 +155,10 @@ def load_config_file_section_contained_item(config_file)
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)}" }
log_chef(:info) { "#{@file} got No Match for Filter\n#{debug_var_output(match)}" }
return
else
log_chef(:info) { "#{config_file} got Match for Filter\n#{debug_var_output(match)}\nResult\n#{debug_var_output(item)}" }
log_chef(:info) { "#{@file} got Match for Filter\n#{debug_var_output(match)}\nResult\n#{debug_var_output(item)}" }
end

log_chef(:warn) { "Expected either one or zero filtered configuration items, got #{item.count}. Data:\n#{debug_var_output(item)}" } unless item.one?
Expand Down Expand Up @@ -214,5 +217,8 @@ def config_item_index_match(config, match)
# Error to raise when failing to filter a single containing resource from a parent path
class FileConfigPathFilterError < FilterError; end
end

# Error to raise when failing to filter a single containing resource from a parent path
class ConfigFilePathFilterError < FilterError; end
end
end
97 changes: 97 additions & 0 deletions libraries/chef_auto_accumulator/config/load_current_value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#
# Cookbook:: chef_auto_accumulator
# Library:: config_file
#
# Copyright:: Ben Hughes <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require_relative '../_utils'
require_relative '../file'

module ChefAutoAccumulator
module Config
# Provides load_current_value support
module LoadCurrentValue
private

def config_file_current_data(filetype: config_file_type, config_file: option_config_file, cache: true)
return unless ::File.exist?(config_file)

if cache && (run_state_cache(:file).key?(config_file) && run_state_cache(:file)[config_file].is_a?(ChefAutoAccumulator::Config::File))
log_chef(:debug) { "Returning file object from cache for: #{config_file}" }
log_chef(:trace) { "File #{config_file} data\n#{debug_var_output(run_state_cache(:file).fetch(config_file).contents)}" }

return run_state_cache(:file).fetch(config_file).contents
end

nil
end

# Return a resources property values from disk (if it exists)
#
# @param filetype [Symbol] The configuration file type
# @param config_file [String] The configuration file to load
# @param cache [TrueClass, FalseClass] Control data caching
# @param new_resource [Chef::Resource] New resource data
# @return [Hash] Resource property data
#
def config_file_current_resource_data(filetype: config_file_type, config_file: option_config_file, cache: true, new_resource:)
return unless ::File.exist?(config_file)

if cache && (run_state_cache(:file).key?(config_file) && run_state_cache(:file)[config_file].is_a?(ChefAutoAccumulator::Config::File))
log_chef(:debug) { "Returning file object from cache for: #{config_file}" }
log_chef(:trace) { "File #{config_file} data\n#{debug_var_output(run_state_cache(:file).fetch(config_file).contents)}" }
else
log_chef(:debug) { "Creating new file object for: #{config_file}" }
run_state_cache(:file)[config_file] = ChefAutoAccumulator::Config::File.new(filetype: filetype, file: config_file, new_resource: new_resource)
run_state_cache(:file)[config_file].load!
end

current_config = run_state_cache(:file).fetch(config_file)
current_config.new_resource = new_resource

log_chef(:debug) { "Resource path type: #{option_config_path_type}" }
config = case option_config_path_type
when :array
current_config.file_section_item
when :array_contained
current_config.file_section_contained_item
when :array_contained_hash
section = current_config.file_section_item
section.fetch(option_config_path_contained_key, nil) if section.is_a?(Hash)
when :hash
current_config.file_section
when :hash_contained
section = current_config.file_section
section.fetch(option_config_path_contained_key, nil) if section.is_a?(Hash)
end.dup

log_chef(:debug) { "Resource config: #{debug_var_output(config)}" }
config
end

# Test if a resources configuration is present on disk
#
# @return [true, false] Test result
#
def config_file_current_resource_data?(filetype: config_file_type, config_file: option_config_file, cache: true, new_resource:)
result = !config_file_current_resource_data(filetype: filetype, config_file: config_file, cache: cache, new_resource: new_resource).nil?
log_chef(:info) { "Result: #{debug_var_output(result)}" }

result
end
end
end
end
3 changes: 3 additions & 0 deletions libraries/chef_auto_accumulator/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ def initialize(fkey, fvalue, path, result)
].join("\n\n"))
end
end

class FileError < BaseError
end
end
Loading

0 comments on commit c668a07

Please sign in to comment.