class Kafo::KafoConfigure

Attributes

check_dirs[RW]
config[RW]
config_file[RW]
exit_handler[RW]
gem_root[RW]
hooking[W]
kafo_modules_dir[RW]
logger[RW]
module_dirs[RW]
root_dir[RW]
scenario_manager[RW]
store[RW]
verbose[RW]
puppet_report[RW]

Public Class Methods

exit(code, &block) click to toggle source
# File lib/kafo/kafo_configure.rb, line 59
def exit(code, &block)
  exit_handler.exit(code, &block)
end
exit_code() click to toggle source
# File lib/kafo/kafo_configure.rb, line 63
def exit_code
  self.exit_handler.exit_code
end
help(*args) click to toggle source
Calls superclass method
# File lib/kafo/kafo_configure.rb, line 71
def help(*args)
  kafo          = args.pop
  builder_class = kafo.full_help? ? HelpBuilders::Advanced : HelpBuilders::Basic
  args.push builder_class.new(kafo.params)
  super(*args)
end
hooking() click to toggle source
# File lib/kafo/kafo_configure.rb, line 49
def hooking
  @hooking ||= Hooking.new
end
in_help_mode?() click to toggle source
# File lib/kafo/kafo_configure.rb, line 67
def in_help_mode?
  ARGV.include?('--help') || ARGV.include?('--full-help') || ARGV.include?('-h')
end
new(*args) click to toggle source
Calls superclass method
# File lib/kafo/kafo_configure.rb, line 101
def initialize(*args)
  self.class.preset_color_scheme
  self.class.logger           = Logger.new
  self.class.exit_handler     = ExitHandler.new
  @progress_bar               = nil
  @config_reload_requested    = false

  scenario_manager = setup_scenario_manager
  self.class.scenario_manager = scenario_manager

  # Handle --list-scenarios before we need them
  scenario_manager.list_available_scenarios if ARGV.include?('--list-scenarios')
  scenario_manager.check_enable_scenario
  scenario_manager.check_disable_scenario
  setup_config(config_file)

  self.class.hooking.execute(:pre_migrations)
  reload_config
  applied_total = self.class.config.run_migrations
  request_config_reload if applied_total > 0

  if ARGV.include?('--migrations-only')
    verbose = ARGV.include?('--verbose') || ARGV.include?('-v')
    Logging.setup(verbose: verbose)
    self.class.logger.notice('Log buffers flushed')
    self.class.exit(0)
  end

  reload_config

  if scenario_manager.configured?
    scenario_manager.check_scenario_change(self.class.config_file)
    if scenario_manager.scenario_changed?(self.class.config_file) && !self.class.in_help_mode?
      prev_config = scenario_manager.load_configuration(scenario_manager.previous_scenario)
      prev_config.run_migrations
      self.class.config.migrate_configuration(prev_config, :skip => [:log_name])
      setup_config(self.class.config_file)
      self.class.logger.notice("Due to scenario change the configuration (#{self.class.config_file}) was updated with #{scenario_manager.previous_scenario} and reloaded.")
    end
  end

  super

  self.class.hooking.execute(:boot)
  set_app_options # define args for installer
  # we need to parse app config params using clamp even before run method does it
  # so we limit parsing only to app config options (because of --help and later defined params)
  parse clamp_app_arguments
  parse_app_arguments # set values from ARGS to config.app

  if ARGV.any? { |option| ['--help', '--full-help'].include? option }
    Logging.setup_verbose(level: :error)
  else
    Logging.setup(verbose: config.app[:verbose])
  end

  logger.notice("Loading installer configuration. This will take some time.")
  self.class.set_color_scheme

  self.class.hooking.execute(:init)
  set_parameters # here the params gets parsed and we need app config populated
  set_options
end
preset_color_scheme() click to toggle source
# File lib/kafo/kafo_configure.rb, line 88
def preset_color_scheme
  match = ARGV.join(' ').match(/--color-of-background[ =](\w+)/)
  background = match && match[1]
  ColorScheme.new(:background => background, :colors => use_colors?).setup
end
run() click to toggle source
Calls superclass method
# File lib/kafo/kafo_configure.rb, line 53
def run
  return super
rescue SystemExit
  self.exit_handler.exit(self.exit_code) # fail in initialize
end
set_color_scheme() click to toggle source
# File lib/kafo/kafo_configure.rb, line 94
def set_color_scheme
  ColorScheme.new(
    :background => config.app[:color_of_background],
    :colors => use_colors?).setup
end
use_colors?() click to toggle source
# File lib/kafo/kafo_configure.rb, line 78
def use_colors?
  if config
    config.app[:colors]
  elsif ARGV.include?('--no-colors')
    false
  elsif ARGV.include?('--colors')
    true
  end
end

Public Instance Methods

add_module(name) click to toggle source
# File lib/kafo/kafo_configure.rb, line 255
def add_module(name)
  config.add_module(name)
  reset_params_cache
  self.module(name)
end
config() click to toggle source
# File lib/kafo/kafo_configure.rb, line 165
def config
  self.class.config
end
enabled_params() click to toggle source
# File lib/kafo/kafo_configure.rb, line 246
def enabled_params
  params.select { |p| p.module.enabled? }
end
execute() click to toggle source
# File lib/kafo/kafo_configure.rb, line 189
def execute
  parse_cli_arguments

  if !config.app[:verbose]
    @progress_bar = config.app[:colors] ? ProgressBars::Colored.new : ProgressBars::BlackWhite.new
  end

  if checks_only? || !skip_checks_i_know_better?
    logger = Logger.new('checks')
    if SystemChecker.check
      logger.notice("System checks passed")
    else
      logger.error("Your system does not meet configuration criteria")
      self.class.exit(:invalid_system)
    end
  end

  self.class.exit(0) if checks_only?

  self.class.hooking.execute(:pre_validations)
  if interactive?
    wizard = Wizard.new(self)
    wizard.run
  else
    unless validate_all
      puts "Error during configuration, exiting"
      self.class.exit(:invalid_values)
    end
  end

  self.class.hooking.execute(:pre_commit)
  unless dont_save_answers? || noop?
    config.configure_application
    store_params
    self.class.scenario_manager.link_last_scenario(self.class.config_file) if self.class.scenario_manager.configured?
  end
  run_installation
  return self
rescue SystemExit
  return self
end
exit_code() click to toggle source
# File lib/kafo/kafo_configure.rb, line 231
def exit_code
  self.class.exit_code
end
help() click to toggle source
# File lib/kafo/kafo_configure.rb, line 235
def help
  self.class.help(invocation_path, self)
end
logger() click to toggle source
# File lib/kafo/kafo_configure.rb, line 173
def logger
  self.class.logger
end
module(name) click to toggle source
# File lib/kafo/kafo_configure.rb, line 265
def module(name)
  modules.detect { |m| m.name == name }
end
modules() click to toggle source
# File lib/kafo/kafo_configure.rb, line 261
def modules
  config.modules.sort
end
param(mod, name) click to toggle source
# File lib/kafo/kafo_configure.rb, line 269
def param(mod, name)
  config.param(mod, name)
end
params() click to toggle source
# File lib/kafo/kafo_configure.rb, line 239
def params
  @params ||= modules.map(&:params).flatten
rescue KafoParsers::ModuleName => e
  puts e
  self.class.exit(:unknown_module)
end
request_config_reload() click to toggle source
# File lib/kafo/kafo_configure.rb, line 273
def request_config_reload
  @config_reload_requested = true
end
reset_params_cache() click to toggle source
# File lib/kafo/kafo_configure.rb, line 250
def reset_params_cache
  @params = nil
  params
end
run(*args) click to toggle source
Calls superclass method
# File lib/kafo/kafo_configure.rb, line 177
def run(*args)
  started_at = Time.now
  logger.debug("Running installer with args #{args.inspect}")
  if config.app[:verbose]
    logger.notice("Running installer with log based terminal output at level #{config.app[:verbose_log_level].upcase}.")
    logger.notice("Use -l to set the terminal output log level to ERROR, WARN, NOTICE, INFO, or DEBUG. See --full-help for definitions.")
  end
  super
ensure
  logger.debug("Installer finished in #{Time.now - started_at} seconds")
end
store() click to toggle source
# File lib/kafo/kafo_configure.rb, line 169
def store
  self.class.store
end

Private Instance Methods

app_option(*args, &block) click to toggle source
# File lib/kafo/kafo_configure.rb, line 330
def app_option(*args, &block)
  self.class.app_option(*args, &block)
end
argument_missing?(value) click to toggle source
# File lib/kafo/kafo_configure.rb, line 474
def argument_missing?(value)
  !!self.class.declared_options.find { |opt| opt.handles?(value) }
end
build_yes_no_variants(s) click to toggle source
# File lib/kafo/kafo_configure.rb, line 434
def build_yes_no_variants(s)
  [ s.sub('[no-]', ''), s.sub('[no-]', 'no-') ]
end
clamp_app_arguments() click to toggle source

ARGV can contain values for attributes e.g. ['-l', 'info'] so we accept either allowed args or those that does not start with '-' and are right after accepted argument

# File lib/kafo/kafo_configure.rb, line 418
def clamp_app_arguments
  @allowed_clamp_app_arguments = self.class.declared_options.map do |option|
    option.switches.map { |s| is_yes_no_flag?(s) ? build_yes_no_variants(s) : s }
  end
  @allowed_clamp_app_arguments.flatten!

  last_was_accepted = false
  ARGV.select do |arg|
    last_was_accepted = is_allowed_attribute_name?(arg) || (last_was_accepted && is_value?(arg))
  end
end
config_file() click to toggle source
# File lib/kafo/kafo_configure.rb, line 573
def config_file
  return CONFIG_FILE if defined?(CONFIG_FILE) && File.exist?(CONFIG_FILE)
  return self.class.scenario_manager.select_scenario if self.class.scenario_manager.configured?
  return '/etc/kafo/kafo.yaml' if File.exist?('/etc/kafo/kafo.yaml')
  return "#{::RbConfig::CONFIG['sysconfdir']}/kafo/kafo.yaml" if File.exist?("#{::RbConfig::CONFIG['sysconfdir']}/kafo/kafo.yaml")
  File.join(Dir.pwd, 'config', 'kafo.yaml')
end
is_allowed_attribute_name?(str) click to toggle source
# File lib/kafo/kafo_configure.rb, line 438
def is_allowed_attribute_name?(str)
  str =~ /([a-zA-Z0-9_-]*)([= ].*)?/ && @allowed_clamp_app_arguments.include?($1)
end
is_value?(str) click to toggle source
# File lib/kafo/kafo_configure.rb, line 442
def is_value?(str)
  !str.start_with?('-')
end
is_yes_no_flag?(s) click to toggle source
# File lib/kafo/kafo_configure.rb, line 430
def is_yes_no_flag?(s)
  s.include?('[no-]')
end
normalize_encoding(line) click to toggle source
# File lib/kafo/kafo_configure.rb, line 581
def normalize_encoding(line)
  line.valid_encoding? ? line : line.encode('UTF-16be', :invalid => :replace, :replace => '?').encode('UTF-8')
end
parse_app_arguments() click to toggle source
# File lib/kafo/kafo_configure.rb, line 446
def parse_app_arguments
  self.class.declared_options.each do |option|
    name                    = option.attribute_name
    value                   = send(option.flag? ? "#{name}?" : name)
    config.app[name.to_sym] = value.nil? ? option.default_value : value
  end
end
parse_cli_arguments() click to toggle source
# File lib/kafo/kafo_configure.rb, line 454
def parse_cli_arguments
  # enable/disable modules according to CLI
  config.modules.each { |mod| send("enable_#{mod.name}?") ? mod.enable : mod.disable }

  # set and reset values coming from CLI arguments
  params.each do |param|
    if send("reset_#{u(with_prefix(param))}?")
      param.unset_value
    end
    variable_name = u(with_prefix(param))
    variable_name += '_list' if param.multivalued?
    cli_value     = instance_variable_get("@#{variable_name}")
    if argument_missing?(cli_value)
      puts "Parameter #{with_prefix(param)} is missing a value on the command line"
      self.class.exit(:missing_argument)
    end
    param.value   = cli_value unless cli_value.nil?
  end
end
progress_log(method, message, logger) click to toggle source
# File lib/kafo/kafo_configure.rb, line 564
def progress_log(method, message, logger)
  @progress_bar.print_error(message + "\n") if method == :error && @progress_bar
  logger.send(method, message)
end
reload_config() click to toggle source
# File lib/kafo/kafo_configure.rb, line 298
def reload_config
  if @config_reload_requested
    scenario_manager = setup_scenario_manager
    self.class.scenario_manager = scenario_manager
    setup_config(self.class.config_file)
    self.class.logger.notice('Installer configuration was reloaded')
    @config_reload_requested = false
  end
end
run_installation() click to toggle source
# File lib/kafo/kafo_configure.rb, line 494
def run_installation
  self.class.hooking.execute(:pre)

  execution_env = ExecutionEnvironment.new(config)
  self.class.exit_handler.register_cleanup_path(execution_env.directory)

  execution_env.store_answers
  puppetconf = execution_env.configure_puppet(
    'color'     => false,
    'evaltrace' => true,
    'noop'      => !!noop?,
    'profile'   => !!profile?,
    'show_diff' => true,
  )

  self.class.exit_handler.exit_code = 0
  exit_status = nil
  options     = [
      '--verbose',
      '--debug',
      '--detailed-exitcodes',
  ]
  begin
    command = PuppetCommand.new('include kafo_configure', options, puppetconf).command
    log_parser = PuppetLogParser.new
    logger = Logger.new('configure')

    logger.notice("Starting system configuration.")

    PTY.spawn(*PuppetCommand.format_command(command)) do |stdin, stdout, pid|
      stdin.each do |line|
        line = normalize_encoding(line)
        method, message = log_parser.parse(line)
        progress_log(method, message, logger)

        if (output = line.match(/(?:.+\]): Starting to evaluate the resource(?: \((?<count>\d+) of (?<total>\d+)\))?/))
          if (output[:count].to_i % 250) == 1 && output[:count].to_i != 1
            logger.notice("#{output[:count].to_i - 1} configuration steps out of #{output[:total]} steps complete.")
          end
        end

        @progress_bar.update(line) if @progress_bar
      end
    rescue Errno::EIO # we reach end of input
      exit_status = PTY.check(pid, true)
      if exit_status.nil? # process is still running
        begin
          Process.wait(pid)
        rescue Errno::ECHILD # process could exit meanwhile so we rescue
        end
        self.class.exit_handler.exit_code = $?.exitstatus
      end
    end
  rescue PTY::ChildExited => e # could be raised by PTY.check
    self.class.exit_handler.exit_code = e.status.exitstatus
  end

  @progress_bar.close if @progress_bar
  logger.notice "System configuration has finished."

  if (last_report = execution_env.reports.last)
    # For debugging: you can easily copy the last report to fixtures
    # FileUtils.cp(last_report, File.join(__dir__, '..', '..', 'test', 'fixtures', 'reports', File.basename(last_report)))
    self.puppet_report = PuppetReport.load_report_file(last_report)
  end

  self.class.hooking.execute(:post)
  self.class.exit(exit_code)
end
set_app_options() click to toggle source
# File lib/kafo/kafo_configure.rb, line 353
def set_app_options
  app_option ['--[no-]colors'], :flag, 'Use color output on STDOUT',
             :default => config.app[:colors], :advanced => true
  app_option ['--color-of-background'], 'COLOR', 'Your terminal background is :bright or :dark',
             :default => config.app[:color_of_background], :advanced => true
  app_option ['--dont-save-answers'], :flag, "Skip saving answers to '#{self.class.config.answer_file}'?",
             :default => config.app[:dont_save_answers], :advanced => true
  app_option '--ignore-undocumented', :flag, 'Ignore inconsistent parameter documentation',
             :default => config.app[:ignore_undocumented], :advanced => true
  app_option ['-i', '--interactive'], :flag, 'Run in interactive mode'
  app_option '--log-level', 'LEVEL', 'Log level for log file output',
             :default => config.app[:log_level], :advanced => true
  app_option ['-n', '--noop'], :flag, 'Run puppet in noop mode?',
             :default => false
  app_option ['-p', '--profile'], :flag, 'Run puppet in profile mode?',
             :default => false, :advanced => true
  app_option ['-s', '--skip-checks-i-know-better'], :flag, 'Skip all system checks',
             :default => false
  app_option ['--checks-only'], :flag, 'Run only system checks and exit',
             :default => false
  app_option ['--skip-puppet-version-check'], :flag, 'Skip check for compatible Puppet versions',
             :default => false, :advanced => true
  app_option ['-v', '--[no-]verbose'], :flag, 'Display log on STDOUT instead of progressbar',
             :default => config.app[:verbose]
  app_option ['-l', '--verbose-log-level'], 'LEVEL', terminal_log_levels_message,
             :default => 'notice'
  app_option ['-S', '--scenario'], 'SCENARIO', 'Use installation scenario'
  app_option ['--disable-scenario'], 'SCENARIO', 'Disable installation scenario',
             :advanced => true
  app_option ['--enable-scenario'], 'SCENARIO', 'Enable installation scenario',
             :advanced => true
  app_option ['--list-scenarios'], :flag, 'List available installation scenarios'
  app_option ['--force'], :flag, 'Force change of installation scenario',
             :advanced => true
  app_option ['--compare-scenarios'], :flag, 'Show changes between last used scenario and the scenario specified with -S or --scenario argument',
             :advanced => true
  app_option ['--migrations-only'], :flag, 'Apply migrations to a selected scenario and exit',
             :advanced => true
  app_option ['--[no-]parser-cache'], :flag, 'Force use or bypass of Puppet module parser cache',
             :advanced => true
end
set_options() click to toggle source
# File lib/kafo/kafo_configure.rb, line 395
def set_options
  app_option '--full-help', :flag, "print complete help" do
    @full_help = true
    request_help
  end

  modules.each do |mod|
    app_option d("--[no-]enable-#{mod.name}"), :flag, "Enable '#{mod.name}' puppet module",
               :default => mod.enabled?
  end

  params.sort.each do |param|
    doc = param.doc.nil? ? 'UNDOCUMENTED' : param.doc.join("\n")
    app_option parametrize(param), '', doc + " (current: #{param.value_to_s})",
               :multivalued => param.multivalued?
    app_option parametrize(param, 'reset-'), :flag,
               "Reset #{param.name} to the default value (#{param.default_to_s})"
  end
end
set_parameters() click to toggle source
# File lib/kafo/kafo_configure.rb, line 320
def set_parameters
  config.preset_defaults_from_puppet
  self.class.hooking.execute(:pre_values)
  config.preset_defaults_from_yaml
  if self.class.scenario_manager.scenario_changed?(config.config_file)
    prev_scenario = self.class.scenario_manager.load_and_setup_configuration(self.class.scenario_manager.previous_scenario)
    config.preset_defaults_from_other_config(prev_scenario)
  end
end
setup_config(conf_file) click to toggle source
# File lib/kafo/kafo_configure.rb, line 279
def setup_config(conf_file)
  self.class.config_file      = conf_file
  self.class.config           = Configuration.new(self.class.config_file)

  if self.class.config.parser_cache
    self.class.config.parser_cache.force = true if ARGV.include?('--parser-cache')
    self.class.config.parser_cache.force = false if ARGV.include?('--no-parser-cache')
  end

  self.class.root_dir         = self.class.config.root_dir
  self.class.check_dirs       = self.class.config.check_dirs
  self.class.module_dirs      = self.class.config.module_dirs
  self.class.gem_root         = self.class.config.gem_root
  self.class.kafo_modules_dir = self.class.config.kafo_modules_dir
  self.class.hooking.load
  self.class.store            = setup_store
  self.class.hooking.kafo     = self
end
setup_scenario_manager() click to toggle source
# File lib/kafo/kafo_configure.rb, line 308
def setup_scenario_manager
  ScenarioManager.new((defined?(CONFIG_DIR) && CONFIG_DIR) || (defined?(CONFIG_FILE) && CONFIG_FILE))
end
setup_store() click to toggle source
# File lib/kafo/kafo_configure.rb, line 312
def setup_store
  store = Store.new()
  store_path = self.class.config.app[:store_dir]
  store_path = File.expand_path(File.join(CONFIG_DIR, '../store.d')) if store_path.empty? && defined?(CONFIG_DIR)
  store.add_dir(store_path) if File.exist?(store_path)
  store
end
store_params(file = nil) click to toggle source
# File lib/kafo/kafo_configure.rb, line 478
def store_params(file = nil)
  data = Hash[config.modules.map { |mod| [mod.identifier, mod.enabled? ? mod.params_hash : false] }]
  config.store(data, file)
end
terminal_log_levels_message() click to toggle source
# File lib/kafo/kafo_configure.rb, line 334
    def terminal_log_levels_message
      if ARGV.include?('--full-help')
        <<~HEREDOC.chomp
          Log level for log based terminal output.
          The available levels are
          ERROR  - Only show errors which prevented the installer from completing successfully.
          WARN   - Deprecation warnings and other information users may want to be aware of.
          NOTICE - High level information about installer execution and progress.
          INFO   - More detailed information about execution and progress. Also shows when the installer makes a change to system configuration.
          DEBUG  - Show all information about execution, including configuration items where no change was needed.
        HEREDOC
      else
        <<~HEREDOC.chomp
          Log level for log based terminal output.
          The available levels are ERROR, WARN, NOTICE, INFO, DEBUG. See --full-help for definitions.
        HEREDOC
      end
    end
unset() click to toggle source
# File lib/kafo/kafo_configure.rb, line 569
def unset
  params.select { |p| p.module.enabled? && p.value_set.nil? }
end
validate_all(logging = true) click to toggle source
# File lib/kafo/kafo_configure.rb, line 483
def validate_all(logging = true)
  logger.info "Running validation checks."
  results = enabled_params.map do |param|
    result = param.valid?
    errors = param.validation_errors.join(', ')
    progress_log(:error, "Parameter #{with_prefix(param)} invalid: #{errors}", logger) if logging && !result
    result
  end
  results.all?
end