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