class Chef::Application
Public Class Methods
# File lib/chef/application.rb, line 366 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("#{ChefUtils::Dist::Infra::SHORT}-stacktrace.out", chef_stacktrace_out) logger.fatal("Stacktrace dumped to #{Chef::FileCache.load("#{ChefUtils::Dist::Infra::SHORT}-stacktrace.out", false)}") logger.fatal("---------------------------------------------------------------------------------------") logger.fatal("PLEASE PROVIDE THE CONTENTS OF THE stacktrace.out FILE (above) IF YOU FILE A BUG REPORT") logger.fatal("---------------------------------------------------------------------------------------") if Chef::Config[:always_dump_stacktrace] logger.fatal(message) else logger.debug(message) end true end
# File lib/chef/application.rb, line 404 def exit!(msg, err = nil) logger.debug(msg) Process.exit(normalize_exit_code(err)) end
Log
a fatal error message to both STDERR and the Logger, exit the application
# File lib/chef/application.rb, line 396 def fatal!(msg, err = nil) if Chef::Config[:always_dump_stacktrace] msg << "\n#{err.backtrace.join("\n")}" end logger.fatal(msg) Process.exit(normalize_exit_code(err)) end
# File lib/chef/application.rb, line 118 def self.logger Chef::Log end
# File lib/chef/application.rb, line 39 def initialize super @chef_client = nil @chef_client_json = nil end
# File lib/chef/application.rb, line 391 def normalize_exit_code(exit_code) Chef::Application::ExitCode.normalize_exit_code(exit_code) end
Configure mixlib-cli to always separate defaults from user-supplied CLI options
# File lib/chef/application.rb, line 47 def self.use_separate_defaults? true end
Public Instance Methods
# File lib/chef/application.rb, line 155 def apply_extra_config_options(extra_config_options) chef_config.apply_extra_config_options(extra_config_options) end
# File lib/chef/application.rb, line 244 def check_license_acceptance LicenseAcceptance::Acceptor.check_and_persist!( "infra-client", Chef::VERSION.to_s, logger: logger, provided: Chef::Config[:chef_license] ) end
@api private (test injection)
# File lib/chef/application.rb, line 109 def chef_config Chef::Config end
@api private (test injection)
# File lib/chef/application.rb, line 123 def chef_configfetcher require_relative "config_fetcher" Chef::ConfigFetcher end
Parse configuration (options and config file)
# File lib/chef/application.rb, line 96 def configure_chef parse_options begin load_config_file rescue Exception => e Chef::Application.fatal!(e.message, Chef::Exceptions::ConfigurationError.new) end chef_config.export_proxies chef_config.init_openssl File.umask chef_config[:umask] end
Sets the default external encoding to UTF-8 (users can change this, but they shouldn’t)
# File lib/chef/application.rb, line 235 def configure_encoding Encoding.default_external = chef_config[:ruby_encoding] end
merge Chef::Config and config
- the nil default value of log_location_cli means STDOUT - the nil default value of log_location is removed - Arrays are supported - syslog + winevt are converted to those specific logger objects
# File lib/chef/application.rb, line 190 def configure_log_location log_location_cli = [ config[:log_location_cli] ].flatten.map { |log_location| log_location.nil? ? STDOUT : log_location } chef_config[:log_location] = [ chef_config[:log_location], log_location_cli ].flatten.compact.uniq chef_config[:log_location].map! do |log_location| case log_location when :syslog, "syslog" force_force_logger logger::Syslog.new when :win_evt, "win_evt" force_force_logger logger::WinEvt.new else # should be a path or STDOUT log_location end end end
# File lib/chef/application.rb, line 172 def configure_logging configure_log_location logger.init(MonoLogger.new(chef_config[:log_location][0])) chef_config[:log_location][1..].each do |log_location| logger.loggers << MonoLogger.new(log_location) end logger.level = resolve_log_level rescue StandardError => error 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
# File lib/chef/application.rb, line 91 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
Force the logger by default for the :winevt and :syslog loggers. Since we do not and cannot support multiple log levels in a mix-and-match situation with formatters and loggers, and the formatters do not support syslog, we force the formatter off by default and the log level is thus info by default. Users can add ‘–force-formatter -l info` to get back formatter output on STDOUT along with syslog logging.
# File lib/chef/application.rb, line 216 def force_force_logger chef_config[:force_logger] = true unless chef_config[:force_formatter] end
Parse the config file
# File lib/chef/application.rb, line 129 def load_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 # here. config[:config_file] = config_fetcher.expanded_path if config[:config_file].nil? logger.warn("No config file found or specified on command line. Using command line options instead.") elsif config_fetcher.config_missing? logger.warn("*****************************************") logger.warn("Did not find config file: #{config[:config_file]}. Using command line options instead.") logger.warn("*****************************************") else config_content = config_fetcher.read_config apply_config(config_content, config[:config_file]) end extra_config_options = config.delete(:config_option) chef_config.merge!(config) apply_extra_config_options(extra_config_options) end
@api private (test injection)
# File lib/chef/application.rb, line 114 def logger Chef::Log end
Reconfigure the application. You’ll want to override and super this method.
# File lib/chef/application.rb, line 52 def reconfigure # In case any gems were installed for use in the config. Gem.clear_paths configure_chef configure_logging configure_encoding emit_warnings end
The :auto formatter defaults to :warn with the formatter and :info with the logger
# File lib/chef/application.rb, line 226 def resolve_log_level if chef_config[:log_level] == :auto using_output_formatter? ? :warn : :info else chef_config[:log_level] end end
Get this party started
# File lib/chef/application.rb, line 62 def run(enforce_license: false) setup_signal_handlers reconfigure setup_application check_license_acceptance if enforce_license run_application end
Actually run the application
# File lib/chef/application.rb, line 254 def run_application raise Chef::Exceptions::Application, "#{self}: you must override run_application" end
Initializes Chef::Client
instance and runs it
# File lib/chef/application.rb, line 259 def run_chef_client(specific_recipes = []) unless specific_recipes.respond_to?(:size) raise ArgumentError, "received non-Array like specific_recipes argument" end Chef::LocalMode.with_server_connectivity do override_runlist = config[:override_runlist] @chef_client = Chef::Client.new( @chef_client_json, override_runlist: override_runlist, specific_recipes: specific_recipes, runlist: config[:runlist], logger: logger ) @chef_client_json = nil if can_fork? fork_chef_client # allowed to run client in forked process else # Unforked interval runs are disabled, so this runs chef-client # once and then exits. If TERM signal is received, will "ignore" # the signal to finish converge. run_with_graceful_exit_option end @chef_client = nil end end
Set the specific recipes to Chef::Config
if the recipes are valid otherwise log a fatal error message and exit the application.
# File lib/chef/application.rb, line 161 def set_specific_recipes if cli_arguments.is_a?(Array) && (cli_arguments.empty? || cli_arguments.all? { |file| File.file?(file) } ) chef_config[:specific_recipes] = cli_arguments.map { |file| File.expand_path(file) } else Chef::Application.fatal!("Invalid argument; could not find the following recipe files: \"" + cli_arguments.select { |file| !File.file?(file) }.join('", "') + '"') end end
Called prior to starting the application, by the run method
# File lib/chef/application.rb, line 240 def setup_application raise Chef::Exceptions::Application, "#{self}: you must override setup_application" end
# File lib/chef/application.rb, line 70 def setup_signal_handlers trap("INT") do Chef::Application.fatal!("SIGINT received, stopping", Chef::Exceptions::SigInt.new) end trap("TERM") do Chef::Application.fatal!("SIGTERM received, stopping", Chef::Exceptions::SigTerm.new) end unless ChefUtils.windows? trap("QUIT") do logger.info("SIGQUIT received, call stack:\n " + caller.join("\n ")) end trap("HUP") do logger.info("SIGHUP received, reconfiguring") reconfigure end end end
Use of output formatters is assumed if ‘force_formatter` is set or if `force_logger` is not set
# File lib/chef/application.rb, line 221 def using_output_formatter? chef_config[:force_formatter] || !chef_config[:force_logger] end
Private Instance Methods
# File lib/chef/application.rb, line 350 def apply_config(config_content, config_file_path) chef_config.from_string(config_content, config_file_path) rescue Exception => error logger.fatal("Configuration error #{error.class}: #{error.message}") filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/) filtered_trace.each { |line| logger.fatal(" " + line ) } raise Chef::Exceptions::ConfigurationError.new("Aborting due to error in '#{config_file_path}': #{error}") # Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", Chef::Exceptions::ConfigurationError.new(error)) end
# File lib/chef/application.rb, line 289 def can_fork? # 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) && !ChefUtils.windows? end
This is a hook for testing
# File lib/chef/application.rb, line 361 def env ENV end
# File lib/chef/application.rb, line 309 def fork_chef_client logger.info "Forking #{ChefUtils::Dist::Infra::PRODUCT} instance to converge..." pid = fork do # Want to allow forked processes to finish converging when # TERM signal is received (exit gracefully) trap("TERM") do logger.debug("SIGTERM received during converge," + " finishing converge to exit normally (send SIGINT to terminate immediately)") end client_solo = chef_config[:solo] ? ChefUtils::Dist::Solo::EXEC : ChefUtils::Dist::Infra::CLIENT $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};" begin logger.trace "Forked instance now converging" @chef_client.run rescue Exception => e logger.error(e.to_s) exit Chef::Application.normalize_exit_code(e) else exit 0 end end logger.trace "Fork successful. Waiting for new #{ChefUtils::Dist::Infra::CLIENT} pid: #{pid}" result = Process.waitpid2(pid) handle_child_exit(result) logger.trace "Forked instance successfully reaped (pid: #{pid})" true end
# File lib/chef/application.rb, line 338 def handle_child_exit(pid_and_status) status = pid_and_status[1] return true if status.success? message = if status.signaled? "#{ChefUtils::Dist::Infra::PRODUCT} run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})" else "#{ChefUtils::Dist::Infra::PRODUCT} run process exited unsuccessfully (exit code #{status.exitstatus})" end raise Exceptions::ChildConvergeError, message end
Run chef-client once and then exit. If TERM signal is received, ignores the signal to finish the converge and exists.
# File lib/chef/application.rb, line 298 def run_with_graceful_exit_option # Override the TERM signal. trap("TERM") do logger.debug("SIGTERM received during converge," + " finishing converge to exit normally (send SIGINT to terminate immediately)") end @chef_client.run true end