diff options
Diffstat (limited to 'lib/chef/application.rb')
-rw-r--r-- | lib/chef/application.rb | 193 |
1 files changed, 117 insertions, 76 deletions
diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 7dbffd8dec..811c713464 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -1,7 +1,7 @@ # # Author:: AJ Christensen (<aj@chef.io>) # Author:: Mark Mzyk (mmzyk@chef.io) -# Copyright:: Copyright 2008-2016, Chef Software, Inc. +# Copyright:: Copyright 2008-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,7 @@ require "chef/platform" require "mixlib/cli" require "tmpdir" require "rbconfig" +require "chef/application/exit_code" class Chef class Application @@ -42,8 +43,15 @@ class Chef # from failing due to permissions when launched as a less privileged user. end + # Configure mixlib-cli to always separate defaults from user-supplied CLI options + def self.use_separate_defaults? + true + end + # Reconfigure the application. You'll want to override and super this method. def reconfigure + # In case any gems were installed for use in the config. + Gem.clear_paths configure_chef configure_logging configure_encoding @@ -60,37 +68,63 @@ class Chef def setup_signal_handlers trap("INT") do - Chef::Application.fatal!("SIGINT received, stopping", 2) + Chef::Application.fatal!("SIGINT received, stopping", Chef::Exceptions::SigInt.new) end trap("TERM") do - Chef::Application.fatal!("SIGTERM received, stopping", 3) + Chef::Application.fatal!("SIGTERM received, stopping", Chef::Exceptions::SigTerm.new) end unless Chef::Platform.windows? trap("QUIT") do - Chef::Log.info("SIGQUIT received, call stack:\n " + caller.join("\n ")) + logger.info("SIGQUIT received, call stack:\n " + caller.join("\n ")) end trap("HUP") do - Chef::Log.info("SIGHUP received, reconfiguring") + logger.info("SIGHUP received, reconfiguring") reconfigure end end end + def emit_warnings + logger.warn "chef_config[:zypper_check_gpg] is set to false which disables security checking on zypper packages" unless chef_config[:zypper_check_gpg] + end + # Parse configuration (options and config file) def configure_chef parse_options load_config_file - Chef::Config.export_proxies - Chef::Config.init_openssl + chef_config.export_proxies + chef_config.init_openssl + File.umask chef_config[:umask] + end + + # @api private (test injection) + def chef_config + Chef::Config + end + + # @api private (test injection) + def logger + Chef::Log + end + + def self.logger + Chef::Log + end + + # @api private (test injection) + def chef_configfetcher + Chef::ConfigFetcher end # Parse the config file def load_config_file - config_fetcher = Chef::ConfigFetcher.new(config[:config_file]) + # apply the default cli options first + chef_config.merge!(default_config) + config_fetcher = chef_configfetcher.new(config[:config_file]) # Some config settings are derived relative to the config file path; if # given as a relative path, this is computed relative to cwd, but # chef-client will later chdir to root, so we need to get the absolute path @@ -98,22 +132,31 @@ class Chef config[:config_file] = config_fetcher.expanded_path if config[:config_file].nil? - Chef::Log.warn("No config file found or specified on command line, using command line options.") + logger.warn("No config file found or specified on command line, using command line options.") elsif config_fetcher.config_missing? - Chef::Log.warn("*****************************************") - Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") - Chef::Log.warn("*****************************************") + logger.warn("*****************************************") + logger.warn("Did not find config file: #{config[:config_file]}, using command line options.") + logger.warn("*****************************************") else config_content = config_fetcher.read_config apply_config(config_content, config[:config_file]) end - Chef::Config.merge!(config) + extra_config_options = config.delete(:config_option) + chef_config.merge!(config) + apply_extra_config_options(extra_config_options) + end + + def apply_extra_config_options(extra_config_options) + chef_config.apply_extra_config_options(extra_config_options) + rescue ChefConfig::UnparsableConfigOption => e + Chef::Application.fatal!(e.message) end def set_specific_recipes - Chef::Config[:specific_recipes] = - cli_arguments.map { |file| File.expand_path(file) } if - cli_arguments.respond_to?(:map) + if cli_arguments.respond_to?(:map) + chef_config[:specific_recipes] = + cli_arguments.map { |file| File.expand_path(file) } + end end # Initialize and configure the logger. @@ -130,62 +173,54 @@ class Chef # unattended, the `force_formatter` option is provided. # # === Auto Log Level - # When `log_level` is set to `:auto` (default), the log level will be `:warn` - # when the primary output mode is an output formatter (see - # +using_output_formatter?+) and `:info` otherwise. + # The `log_level` of `:auto` means `:warn` in the formatter and `:info` in + # the logger. # - # === Automatic STDOUT Logging - # When `force_logger` is configured (e.g., Chef 10 mode), a second logger - # with output on STDOUT is added when running in a console (STDOUT is a tty) - # and the configured log_location isn't STDOUT. This accounts for the case - # that a user has configured a log_location in client.rb, but is running - # chef-client by hand to troubleshoot a problem. def configure_logging configure_log_location - Chef::Log.init(MonoLogger.new(Chef::Config[:log_location])) + logger.init(MonoLogger.new(chef_config[:log_location])) if want_additional_logger? configure_stdout_logger end - Chef::Log.level = resolve_log_level + logger.level = resolve_log_level rescue StandardError => error - Chef::Log.fatal("Failed to open or create log file at #{Chef::Config[:log_location]}: #{error.class} (#{error.message})") - Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", 2) + logger.fatal("Failed to open or create log file at #{chef_config[:log_location]}: #{error.class} (#{error.message})") + Chef::Application.fatal!("Aborting due to invalid 'log_location' configuration", error) end # Turn `log_location :syslog` and `log_location :win_evt` into the # appropriate loggers. def configure_log_location - log_location = Chef::Config[:log_location] + log_location = chef_config[:log_location] return unless log_location.respond_to?(:to_sym) - Chef::Config[:log_location] = + chef_config[:log_location] = case log_location.to_sym - when :syslog then Chef::Log::Syslog.new - when :win_evt then Chef::Log::WinEvt.new + when :syslog then logger::Syslog.new + when :win_evt then logger::WinEvt.new else log_location # Probably a path; let MonoLogger sort it out end end - def configure_stdout_logger - stdout_logger = MonoLogger.new(STDOUT) - stdout_logger.formatter = Chef::Log.logger.formatter - Chef::Log.loggers << stdout_logger - end - # Based on config and whether or not STDOUT is a tty, should we setup a # secondary logger for stdout? def want_additional_logger? - ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger]) + !( chef_config[:log_location].is_a?(IO) && chef_config[:log_location].tty? ) && !chef_config[:daemonize] end - # Use of output formatters is assumed if `force_formatter` is set or if - # `force_logger` is not set and STDOUT is to a console (tty) + def configure_stdout_logger + stdout_logger = MonoLogger.new(STDOUT) + stdout_logger.formatter = logger.logger.formatter + logger.loggers << stdout_logger + end + + # Use of output formatters is assumed if `force_formatter` is set or if `force_logger` is not set def using_output_formatter? - Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) + chef_config[:force_formatter] || !chef_config[:force_logger] end def auto_log_level? - Chef::Config[:log_level] == :auto + chef_config[:log_level] == :auto end # if log_level is `:auto`, convert it to :warn (when using output formatter) @@ -198,13 +233,13 @@ class Chef :info end else - Chef::Config[:log_level] + chef_config[:log_level] end end # Sets the default external encoding to UTF-8 (users can change this, but they shouldn't) def configure_encoding - Encoding.default_external = Chef::Config[:ruby_encoding] + Encoding.default_external = chef_config[:ruby_encoding] end # Called prior to starting the application, by the run method @@ -230,7 +265,8 @@ class Chef @chef_client_json, override_runlist: override_runlist, specific_recipes: specific_recipes, - runlist: config[:runlist] + runlist: config[:runlist], + logger: logger ) @chef_client_json = nil @@ -252,7 +288,7 @@ class Chef # win32-process gem exposes some form of :fork for Process # class. So we are separately ensuring that the platform we're # running on is not windows before forking. - Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows? + chef_config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows? end # Run chef-client once and then exit. If TERM signal is received, ignores the @@ -260,7 +296,7 @@ class Chef def run_with_graceful_exit_option # Override the TERM signal. trap("TERM") do - Chef::Log.debug("SIGTERM received during converge," + + logger.debug("SIGTERM received during converge," + " finishing converge to exit normally (send SIGINT to terminate immediately)") end @@ -269,31 +305,31 @@ class Chef end def fork_chef_client - Chef::Log.info "Forking chef instance to converge..." + logger.info "Forking chef instance to converge..." pid = fork do # Want to allow forked processes to finish converging when # TERM singal is received (exit gracefully) trap("TERM") do - Chef::Log.debug("SIGTERM received during converge," + + logger.debug("SIGTERM received during converge," + " finishing converge to exit normally (send SIGINT to terminate immediately)") end - client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client" + client_solo = chef_config[:solo] ? "chef-solo" : "chef-client" $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};" begin - Chef::Log.debug "Forked instance now converging" + logger.trace "Forked instance now converging" @chef_client.run rescue Exception => e - Chef::Log.error(e.to_s) - exit 1 + logger.error(e.to_s) + exit Chef::Application.normalize_exit_code(e) else exit 0 end end - Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}" + logger.trace "Fork successful. Waiting for new chef pid: #{pid}" result = Process.waitpid2(pid) handle_child_exit(result) - Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})" + logger.trace "Forked instance successfully reaped (pid: #{pid})" true end @@ -309,12 +345,12 @@ class Chef end def apply_config(config_content, config_file_path) - Chef::Config.from_string(config_content, config_file_path) + chef_config.from_string(config_content, config_file_path) rescue Exception => error - Chef::Log.fatal("Configuration error #{error.class}: #{error.message}") + logger.fatal("Configuration error #{error.class}: #{error.message}") filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/) - filtered_trace.each { |line| Chef::Log.fatal(" " + line ) } - Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2) + filtered_trace.each { |line| logger.fatal(" " + line ) } + Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", error) end # This is a hook for testing @@ -322,34 +358,39 @@ class Chef ENV end - def emit_warnings - if Chef::Config[:chef_gem_compile_time] - Chef.log_deprecation "setting chef_gem_compile_time to true is deprecated" - end - end - class << self def debug_stacktrace(e) message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" + + cause = e.cause if e.respond_to?(:cause) + until cause.nil? + message << "\n\n>>>> Caused by #{cause.class}: #{cause}\n#{cause.backtrace.join("\n")}" + cause = cause.respond_to?(:cause) ? cause.cause : nil + end + chef_stacktrace_out = "Generated at #{Time.now}\n" chef_stacktrace_out += message Chef::FileCache.store("chef-stacktrace.out", chef_stacktrace_out) - Chef::Log.fatal("Stacktrace dumped to #{Chef::FileCache.load("chef-stacktrace.out", false)}") - Chef::Log.fatal("Please provide the contents of the stacktrace.out file if you file a bug report") - Chef::Log.debug(message) + logger.fatal("Stacktrace dumped to #{Chef::FileCache.load("chef-stacktrace.out", false)}") + logger.fatal("Please provide the contents of the stacktrace.out file if you file a bug report") + logger.debug(message) true end + def normalize_exit_code(exit_code) + Chef::Application::ExitCode.normalize_exit_code(exit_code) + end + # Log a fatal error message to both STDERR and the Logger, exit the application - def fatal!(msg, err = -1) - Chef::Log.fatal(msg) - Process.exit err + def fatal!(msg, err = nil) + logger.fatal(msg) + Process.exit(normalize_exit_code(err)) end - def exit!(msg, err = -1) - Chef::Log.debug(msg) - Process.exit err + def exit!(msg, err = nil) + logger.debug(msg) + Process.exit(normalize_exit_code(err)) end end |