From 90c1cdab7e75fca68475083224634f42989651f2 Mon Sep 17 00:00:00 2001 From: "Eric D. Helms" Date: Mon, 19 Jul 2021 08:31:14 -0400 Subject: [PATCH] Introduce a class to represent the answer file Refactors code into an AnswerFile class for handling the loading of and details of the parts of the answer file. This abstraction will allow easier introduction of newer versions of the answer file. --- README.md | 25 +++++++++ lib/kafo/answer_file.rb | 56 +++++++++++++++++++ lib/kafo/configuration.rb | 32 ++++------- test/fixtures/answer_files/basic-answers.yaml | 6 ++ .../answer_files/invalid-answers.yaml | 4 ++ test/kafo/answer_file_test.rb | 38 +++++++++++++ 6 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 lib/kafo/answer_file.rb create mode 100644 test/fixtures/answer_files/basic-answers.yaml create mode 100644 test/fixtures/answer_files/invalid-answers.yaml create mode 100644 test/kafo/answer_file_test.rb diff --git a/README.md b/README.md index bd33c8db..13345e64 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,31 @@ As you may have noticed there are several ways how to specify arguments. Here's * values specified on CLI * interactive mode arguments +## Answer File Schema + +The answer file schema can be described using Puppet types as such: + +``` +Hash[ +String $puppet_class, + Enum[true, false, Hash[String, Variant[String, Boolean, Integer, Array, Hash]]] $parameters +] +``` + +An example of each available option: + +``` +class_a: true +class_b: false +class_c: {} +class_d: + key: value + key2: 'value' + key3: false + key4: 1 + key5: ['a', 'b'] +``` + ## Requirements Kafo is supported with Puppet versions 4.9+, 5 and 6. Puppet may be installed diff --git a/lib/kafo/answer_file.rb b/lib/kafo/answer_file.rb new file mode 100644 index 00000000..fba4a481 --- /dev/null +++ b/lib/kafo/answer_file.rb @@ -0,0 +1,56 @@ +require 'yaml' + +module Kafo + class AnswerFile + + attr_reader :answers, :filename, :version + + def initialize(answer_filename, version: 1) + @filename = answer_filename + @version = version.nil? ? 1 : version + + begin + @answers = YAML.load_file(@filename) + rescue Errno::ENOENT + KafoConfigure.exit(:no_answer_filename) do + puts "No answer file found at #{@filename}" + end + end + + validate + end + + def filename + @filename + end + + def puppet_classes + @answers.keys.sort + end + + def parameters_for_class(puppet_class) + params = @answers[puppet_class] + params.is_a?(Hash) ? params : {} + end + + def class_enabled?(puppet_class) + value = @answers[puppet_class.is_a?(String) ? puppet_class : puppet_class.identifier] + !!value || value.is_a?(Hash) + end + + private + + def validate + invalid = @answers.reject do |puppet_class, value| + value.is_a?(Hash) || [true, false].include?(value) + end + + unless invalid.empty? + KafoConfigure.exit(:invalid_values) do + KafoConfigure.logger.error("Answer file at #{@filename} has invalid values for #{invalid}. Please ensure they are either a hash or true/false.") + end + end + end + + end +end diff --git a/lib/kafo/configuration.rb b/lib/kafo/configuration.rb index d873b7bb..18da2a2b 100644 --- a/lib/kafo/configuration.rb +++ b/lib/kafo/configuration.rb @@ -6,6 +6,7 @@ require 'kafo/data_type_parser' require 'kafo/execution_environment' require 'kafo/scenario_option' +require 'kafo/answer_file' module Kafo class Configuration @@ -55,13 +56,8 @@ def initialize(file, persist = true) configure_application @logger = KafoConfigure.logger - @answer_file = app[:answer_file] - begin - @data = load_yaml_file(@answer_file) - rescue Errno::ENOENT - puts "No answer file at #{@answer_file} found, can not continue" - KafoConfigure.exit(:no_answer_file) - end + @answer_file = AnswerFile.new(app[:answer_file], version: app[:answer_file_version]) + @answers = @answer_file.answers @config_dir = File.dirname(@config_file) @scenario_id = Configuration.get_scenario_id(@config_file) @@ -130,7 +126,7 @@ def has_custom_fact?(key) def modules @modules ||= begin register_data_types - @data.keys.map { |mod| PuppetModule.new(mod, configuration: self).parse }.sort + @answer_file.puppet_classes.map { |mod| PuppetModule.new(mod, configuration: self).parse }.sort end end @@ -231,14 +227,12 @@ class { '::kafo_configure::dump_values': # if a value is a true we return empty hash because we have no specific options for a # particular puppet module - def [](key) - value = @data[key] - value.is_a?(Hash) ? value : {} + def [](puppet_class) + @answer_file.parameters_for_class(puppet_class) end - def module_enabled?(mod) - value = @data[mod.is_a?(String) ? mod : mod.identifier] - !!value || value.is_a?(Hash) + def module_enabled?(puppet_class) + @answer_file.class_enabled?(puppet_class) end def config_header @@ -248,7 +242,7 @@ def config_header end def store(data, file = nil) - filename = file || answer_file + filename = file || @answer_file.filename FileUtils.touch filename File.chmod 0600, filename File.open(filename, 'w') { |f| f.write(config_header + format(YAML.dump(data))) } @@ -316,17 +310,13 @@ def log_exists? log_files.any? { |f| File.size(f) > 0 } end - def answers - @data - end - def run_migrations migrations = Kafo::Migrations.new(migrations_dir) - @app, @data = migrations.run(app, answers) + @app, @answers = migrations.run(app, @answers) if migrations.migrations.count > 0 @modules = nil # force the lazy loaded modules to reload next time they are used save_configuration(app) - store(answers) + store(@answers) migrations.store_applied @logger.notice("#{migrations.migrations.count} migration/s were applied. Updated configuration was saved.") end diff --git a/test/fixtures/answer_files/basic-answers.yaml b/test/fixtures/answer_files/basic-answers.yaml new file mode 100644 index 00000000..8f31128f --- /dev/null +++ b/test/fixtures/answer_files/basic-answers.yaml @@ -0,0 +1,6 @@ +--- +class_a: true +class_b: + key: value +class_c: {} +class_d: false diff --git a/test/fixtures/answer_files/invalid-answers.yaml b/test/fixtures/answer_files/invalid-answers.yaml new file mode 100644 index 00000000..d0a41c42 --- /dev/null +++ b/test/fixtures/answer_files/invalid-answers.yaml @@ -0,0 +1,4 @@ +--- +class_a: 'true' +class_b: +class_c: 1 diff --git a/test/kafo/answer_file_test.rb b/test/kafo/answer_file_test.rb new file mode 100644 index 00000000..1b9366be --- /dev/null +++ b/test/kafo/answer_file_test.rb @@ -0,0 +1,38 @@ +require 'test_helper' + +describe 'Kafo::AnswerFile' do + + describe 'valid answer file' do + let(:answer_file_path) { 'test/fixtures/answer_files/basic-answers.yaml' } + let(:answer_file) { Kafo::AnswerFile.new(answer_file_path) } + + it 'returns the sorted puppet classes' do + _(answer_file.puppet_classes).must_equal(['class_a', 'class_b', 'class_c', 'class_d']) + end + + it 'returns the parameters for a class' do + _(answer_file.parameters_for_class('class_b')).must_equal({'key' => 'value'}) + end + + it 'returns true for a class with a hash' do + _(answer_file.class_enabled?('class_c')).must_equal(true) + end + + it 'returns true for a class set to true' do + _(answer_file.class_enabled?('class_a')).must_equal(true) + end + + it 'returns false for a class set to false' do + _(answer_file.class_enabled?('class_d')).must_equal(false) + end + end + + describe 'invalid answer file' do + let(:answer_file_path) { 'test/fixtures/answer_files/invalid-answers.yaml' } + + it 'exits with invalid_answer_file' do + must_exit_with_code(21) { Kafo::AnswerFile.new(answer_file_path) } + end + end + +end