lib/kafo/kafo_configure.rb in kafo-0.2.2 vs lib/kafo/kafo_configure.rb in kafo-0.3.0

- old
+ new

@@ -4,308 +4,335 @@ require 'clamp' require 'kafo/exceptions' require 'kafo/configuration' require 'kafo/logger' require 'kafo/string_helper' +require 'kafo/help_builder' require 'kafo/wizard' require 'kafo/system_checker' require 'kafo/puppet_command' require 'kafo/progress_bar' +require 'kafo/hooking' -class KafoConfigure < Clamp::Command - include StringHelper +module Kafo + class KafoConfigure < Clamp::Command + include StringHelper - class << self - attr_accessor :config, :root_dir, :config_file, :gem_root, :temp_config_file, - :modules_dir, :kafo_modules_dir, :verbose, :app_options, :logger - end - def initialize(*args) - self.class.logger = Logger.new - self.class.config_file = config_file - self.class.config = Configuration.new(self.class.config_file) - self.class.root_dir = File.expand_path(self.class.config.app[:installer_dir]) - modules_dir = self.class.config.app[:module_dir] || (self.class.config.app[:installer_dir] + '/modules') - self.class.modules_dir = File.expand_path(modules_dir) - self.class.gem_root = File.join(File.dirname(__FILE__), '../../') - self.class.kafo_modules_dir = self.class.config.app[:kafo_modules_dir] || (self.class.gem_root + '/modules') - @progress_bar = nil + class << self + attr_accessor :config, :root_dir, :config_file, :gem_root, :temp_config_file, + :modules_dir, :kafo_modules_dir, :verbose, :app_options, :logger + attr_writer :hooking - super + def hooking + @hooking ||= Hooking.new + end + end - set_app_options - allowed = self.class.app_options.map(&:switches).flatten - allowed.map! { |s| s.include?('[no-]') ? [s.sub('[no-]', ''), s.sub('[no-]', 'no-')] : s }.flatten! - # 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 ARGV.select { |a| a =~ /([a-zA-Z0-9_-]*)([= ].*)?/ && allowed.include?($1) } - parse_app_arguments - Logger.setup + def initialize(*args) + self.class.logger = Logger.new + self.class.config_file = config_file + self.class.config = Configuration.new(self.class.config_file) + self.class.root_dir = File.expand_path(self.class.config.app[:installer_dir]) + modules_dir = self.class.config.app[:module_dir] || (self.class.config.app[:installer_dir] + '/modules') + self.class.modules_dir = File.expand_path(modules_dir) + self.class.gem_root = File.join(File.dirname(__FILE__), '../../') + self.class.kafo_modules_dir = self.class.config.app[:kafo_modules_dir] || (self.class.gem_root + '/modules') + @progress_bar = nil + self.class.hooking.kafo = self - set_parameters # here the params gets parsed and we need app config populated - set_options - end + super - def config - self.class.config - end + set_app_options + allowed = self.class.app_options.map(&:switches).flatten + allowed.map! { |s| s.include?('[no-]') ? [s.sub('[no-]', ''), s.sub('[no-]', 'no-')] : s }.flatten! + # 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 ARGV.select { |a| a =~ /([a-zA-Z0-9_-]*)([= ].*)?/ && allowed.include?($1) } + parse_app_arguments + Logger.setup - def execute - catch :exit do - parse_cli_arguments + set_parameters # here the params gets parsed and we need app config populated + set_options + end - if (self.class.verbose = verbose?) - Logger.setup_verbose - else - @progress_bar = self.class.config.app[:colors] ? ProgressBars::Colored.new : ProgressBars::BlackWhite.new - end - unless SystemChecker.check - puts "Your system does not meet configuration criteria" - exit(:invalid_system) - end + def config + self.class.config + end - if interactive? - wizard = Wizard.new - wizard.run - else - unless validate_all - puts "Error during configuration, exiting" - exit(:invalid_values) + def execute + catch :exit do + parse_cli_arguments + + if (self.class.verbose = verbose?) + Logger.setup_verbose + else + @progress_bar = self.class.config.app[:colors] ? ProgressBars::Colored.new : ProgressBars::BlackWhite.new end + + unless SystemChecker.check + puts "Your system does not meet configuration criteria" + exit(:invalid_system) + end + + if interactive? + wizard = Wizard.new(self) + wizard.run + else + unless validate_all + puts "Error during configuration, exiting" + exit(:invalid_values) + end + end + + if dont_save_answers? + self.class.temp_config_file = temp_config_file + store_params(temp_config_file) + else + store_params + end + run_installation end + return self + end - if dont_save_answers? - self.class.temp_config_file = temp_config_file - store_params(temp_config_file) - else - store_params + def self.run + catch :exit do + return super end - run_installation + Kernel.exit(self.exit_code) # fail in initialize end - return self - end - def self.run - catch :exit do - return super + def exit_code + self.class.exit_code end - Kernel.exit(self.exit_code) # fail in initialize - end - def exit_code - self.class.exit_code - end + def self.exit(code) + @exit_code = translate_exit_code(code) + throw :exit + end - def self.exit(code) - @exit_code = translate_exit_code(code) - throw :exit - end + def self.exit_code + @exit_code ||= 0 + end - def self.exit_code - @exit_code ||= 0 - end + def self.translate_exit_code(code) + return code if code.is_a? Fixnum + error_codes = { :invalid_system => 20, + :invalid_values => 21, + :manifest_error => 22, + :no_answer_file => 23, + :unknown_module => 24, + :defaults_error => 25 } + if error_codes.has_key? code + return error_codes[code] + else + raise "Unknown code #{code}" + end + end - def self.translate_exit_code(code) - return code if code.is_a? Fixnum - error_codes = { :invalid_system => 20, - :invalid_values => 21, - :manifest_error => 22, - :no_answer_file => 23, - :unknown_module => 24, - :defaults_error => 25} - if error_codes.has_key? code - return error_codes[code] - else - raise "Unknown code #{code}" + def help + self.class.help(invocation_path, self) end - end - def self.app_option(*args, &block) - self.app_options ||= [] - self.app_options.push self.option(*args, &block) - self.app_options.last - end + def self.help(*args) + kafo = args.pop + builder_class = kafo.full_help? ? HelpBuilders::Advanced : HelpBuilders::Basic + args.push builder_class.new(kafo.params) + super(*args) + end - def params - @params ||= modules.map(&:params).flatten - rescue ModuleName => e - puts e - exit(:unknown_module) - end + def self.app_option(*args, &block) + self.app_options ||= [] + self.app_options.push self.option(*args, &block) + self.app_options.last + end - def modules - config.modules.sort - end + def params + @params ||= modules.map(&:params).flatten + rescue ModuleName => e + puts e + exit(:unknown_module) + end - def module(name) - modules.detect { |m| m.name == name} - end + def modules + config.modules.sort + end - def param(mod, name) - params.detect { |p| p.name == name && p.module.name == mod } - end + def module(name) + modules.detect { |m| m.name == name } + end - private + def param(mod, name) + params.detect { |p| p.name == name && p.module.name == mod } + end - def logger - self.class.logger - end + private - def exit(code) - self.class.exit(code) - end + def logger + self.class.logger + end - def set_parameters - params.each do |param| - # set values based on default_values - param.set_default(config.params_default_values) - # set values based on YAML - param.set_value_by_config(config) + def exit(code) + self.class.exit(code) end - end - def set_app_options - self.class.app_option ['--[no-]colors'], :flag, 'Use color output on STDOUT', - :default => !!config.app[:colors] - self.class.app_option ['-d', '--dont-save-answers'], :flag, 'Skip saving answers to answers.yaml?', - :default => !!config.app[:dont_save_answers] - self.class.app_option '--ignore-undocumented', :flag, 'Ignore inconsistent parameters documentation', - :default => false - self.class.app_option ['-i', '--interactive'], :flag, 'Run in interactive mode' - self.class.app_option '--log-level', 'LEVEL', 'Log level for log file output', - :default => config.app[:log_level] - self.class.app_option ['-n', '--noop'], :flag, 'Run puppet in noop mode?', - :default => false - self.class.app_option ['-v', '--verbose'], :flag, 'Display log on STDOUT instead of progressbar' - self.class.app_option ['-l', '--verbose-log-level'], 'LEVEL', 'Log level for verbose mode output', - :default => 'info' - end + def set_parameters + params.each do |param| + # set values based on default_values + param.set_default(config.params_default_values) + # set values based on YAML + param.set_value_by_config(config) + end + end - def set_options - modules.each do |mod| - self.class.option d("--[no-]enable-#{mod.name}"), - :flag, - "Enable puppet module #{mod.name}?", - :default => mod.enabled? + def set_app_options + self.class.app_option ['--[no-]colors'], :flag, 'Use color output on STDOUT', + :default => !!config.app[:colors] + self.class.app_option ['-d', '--dont-save-answers'], :flag, 'Skip saving answers to answers.yaml?', + :default => !!config.app[:dont_save_answers] + self.class.app_option '--ignore-undocumented', :flag, 'Ignore inconsistent parameters documentation', + :default => false + self.class.app_option ['-i', '--interactive'], :flag, 'Run in interactive mode' + self.class.app_option '--log-level', 'LEVEL', 'Log level for log file output', + :default => config.app[:log_level] + self.class.app_option ['-n', '--noop'], :flag, 'Run puppet in noop mode?', + :default => false + self.class.app_option ['-v', '--verbose'], :flag, 'Display log on STDOUT instead of progressbar' + self.class.app_option ['-l', '--verbose-log-level'], 'LEVEL', 'Log level for verbose mode output', + :default => 'info' end - params.sort.each do |param| - doc = param.doc.nil? ? 'UNDOCUMENTED' : param.doc.join("\n") - self.class.option parametrize(param), '', doc, - :default => param.value, :multivalued => param.multivalued? + def set_options + self.class.option '--full-help', :flag, "print complete help" do + @full_help = true + request_help + end + + modules.each do |mod| + self.class.option d("--[no-]enable-#{mod.name}"), + :flag, + "Enable puppet module #{mod.name}?", + :default => mod.enabled? + end + + params.sort.each do |param| + doc = param.doc.nil? ? 'UNDOCUMENTED' : param.doc.join("\n") + self.class.option parametrize(param), '', doc, + :default => param.value, :multivalued => param.multivalued? + end end - end - def parse_app_arguments - self.class.app_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 + def parse_app_arguments + self.class.app_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 - end - def parse_cli_arguments - # enable/disable modules according to CLI - config.modules.each { |mod| send("enable_#{mod.name}?") ? mod.enable : mod.disable } + def parse_cli_arguments + # enable/disable modules according to CLI + config.modules.each { |mod| send("enable_#{mod.name}?") ? mod.enable : mod.disable } - # set values coming from CLI arguments - params.each do |param| - variable_name = u(with_prefix(param)) - variable_name += '_list' if param.multivalued? - cli_value = instance_variable_get("@#{variable_name}") - param.value = cli_value unless cli_value.nil? + # set values coming from CLI arguments + params.each do |param| + variable_name = u(with_prefix(param)) + variable_name += '_list' if param.multivalued? + cli_value = instance_variable_get("@#{variable_name}") + param.value = cli_value unless cli_value.nil? + end end - end - def store_params(file = nil) - data = Hash[config.modules.map { |mod| [mod.name, mod.enabled? ? mod.params_hash : false] }] - config.store(data, file) - end + def store_params(file = nil) + data = Hash[config.modules.map { |mod| [mod.name, mod.enabled? ? mod.params_hash : false] }] + config.store(data, file) + end - def validate_all(logging = true) - logger.info 'Running validation checks' - results = params.map do |param| - result = param.valid? - progress_log(:error, "Parameter #{with_prefix(param)} invalid") if logging && !result - result + def validate_all(logging = true) + logger.info 'Running validation checks' + results = params.map do |param| + result = param.valid? + progress_log(:error, "Parameter #{with_prefix(param)} invalid") if logging && !result + result + end + results.all? end - results.all? - end - def run_installation - exit_code = 0 - exit_status = nil - options = [ - '--verbose', - '--debug', - '--color=false', - '--show_diff', - '--detailed-exitcodes', - ] - options.push '--noop' if noop? - begin - command = PuppetCommand.new('include kafo_configure', options).command - PTY.spawn(command) do |stdin, stdout, pid| - begin - stdin.each do |line| - progress_log(*puppet_parse(line)) - @progress_bar.update(line) if @progress_bar - end - rescue Errno::EIO # we reach end of input - exit_status = PTY.check(pid, true) if PTY.respond_to?(:check) # ruby >= 1.9.2 - if exit_status.nil? # process is still running or we have old ruby so we don't know - begin - Process.wait(pid) - rescue Errno::ECHILD # process could exit meanwhile so we rescue + def run_installation + self.class.hooking.execute(:pre) if self.class.hooking + exit_code = 0 + exit_status = nil + options = [ + '--verbose', + '--debug', + '--color=false', + '--show_diff', + '--detailed-exitcodes', + ] + options.push '--noop' if noop? + begin + command = PuppetCommand.new('include kafo_configure', options).command + PTY.spawn(command) do |stdin, stdout, pid| + begin + stdin.each do |line| + progress_log(*puppet_parse(line)) + @progress_bar.update(line) if @progress_bar end - exit_code = $?.exitstatus # after check $? should be set correctly - else # this should never happen (unless they change PTY.check behavior) in new ruby - exit_code = exit_code.exitstatus + rescue Errno::EIO # we reach end of input + exit_status = PTY.check(pid, true) if PTY.respond_to?(:check) # ruby >= 1.9.2 + if exit_status.nil? # process is still running or we have old ruby so we don't know + begin + Process.wait(pid) + rescue Errno::ECHILD # process could exit meanwhile so we rescue + end + end end end + rescue PTY::ChildExited => e # could be raised by Process.wait on older ruby or by PTY.check + exit_code = e.status.exitstatus end - rescue PTY::ChildExited => e # could be raised by Process.wait on older ruby or by PTY.check - exit_code = e.status.exitstatus + @progress_bar.close if @progress_bar + logger.info "Puppet has finished, bye!" + FileUtils.rm(temp_config_file, :force => true) + self.class.hooking.execute(:post) if self.class.hooking + exit(exit_code) end - @progress_bar.close if @progress_bar - logger.info "Puppet has finished, bye!" - FileUtils.rm(temp_config_file, :force => true) - exit(exit_code) - end - def progress_log(method, message) - @progress_bar.print_error(message + "\n") if method == :error && @progress_bar - logger.send(method, message) - end + def progress_log(method, message) + @progress_bar.print_error(message + "\n") if method == :error && @progress_bar + logger.send(method, message) + end - def puppet_parse(line) - method, message = case - when line =~ /^Error:(.*)/i || line =~ /^Err:(.*)/i - [:error, $1] - when line =~ /^Warning:(.*)/i || line =~ /^Notice:(.*)/i - [:warn, $1] - when line =~ /^Info:(.*)/i - [:info, $1] - when line =~ /^Debug:(.*)/i - [:debug, $1] - else - [:info, line] - end + def puppet_parse(line) + method, message = case + when line =~ /^Error:(.*)/i || line =~ /^Err:(.*)/i + [:error, $1] + when line =~ /^Warning:(.*)/i || line =~ /^Notice:(.*)/i + [:warn, $1] + when line =~ /^Info:(.*)/i + [:info, $1] + when line =~ /^Debug:(.*)/i + [:debug, $1] + else + [:info, line] + end - return [method, message.chomp] - end + return [method, message.chomp] + end - def unset - params.select { |p| p.module.enabled? && p.value_set.nil? } - end + def unset + params.select { |p| p.module.enabled? && p.value_set.nil? } + end - def config_file - return CONFIG_FILE if defined?(CONFIG_FILE) && File.exists?(CONFIG_FILE) - return '/etc/kafo/kafo.yaml' if File.exists?('/etc/kafo/kafo.yaml') - File.join(Dir.pwd, 'config', 'kafo.yaml') - end + def config_file + return CONFIG_FILE if defined?(CONFIG_FILE) && File.exists?(CONFIG_FILE) + return '/etc/kafo/kafo.yaml' if File.exists?('/etc/kafo/kafo.yaml') + File.join(Dir.pwd, 'config', 'kafo.yaml') + end - def temp_config_file - @temp_config_file ||= "/tmp/kafo_answers_#{rand(1_000_000)}.yaml" + def temp_config_file + @temp_config_file ||= "/tmp/kafo_answers_#{rand(1_000_000)}.yaml" + end end end