class Chef::Application

Public Class Methods

logger() click to toggle source
# File lib/chef/application.rb, line 116
def self.logger
  Chef::Log
end
new() click to toggle source
Calls superclass method
# File lib/chef/application.rb, line 38
def initialize
  super

  @chef_client = nil
  @chef_client_json = nil

  # Always switch to a readable directory. Keeps subsequent Dir.chdir() {}
  # from failing due to permissions when launched as a less privileged user.
end
use_separate_defaults?() click to toggle source

Configure mixlib-cli to always separate defaults from user-supplied CLI options

# File lib/chef/application.rb, line 49
def self.use_separate_defaults?
  true
end

Private Class Methods

debug_stacktrace(e) click to toggle source
# File lib/chef/application.rb, line 379
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)
  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
exit!(msg, err = nil) click to toggle source
# File lib/chef/application.rb, line 408
def exit!(msg, err = nil)
  logger.debug(msg)
  Process.exit(normalize_exit_code(err))
end
fatal!(msg, err = nil) click to toggle source

Log a fatal error message to both STDERR and the Logger, exit the application

# File lib/chef/application.rb, line 403
def fatal!(msg, err = nil)
  logger.fatal(msg)
  Process.exit(normalize_exit_code(err))
end
normalize_exit_code(exit_code) click to toggle source
# File lib/chef/application.rb, line 398
def normalize_exit_code(exit_code)
  Chef::Application::ExitCode.normalize_exit_code(exit_code)
end

Public Instance Methods

apply_extra_config_options(extra_config_options) click to toggle source
# File lib/chef/application.rb, line 152
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
auto_log_level?() click to toggle source
# File lib/chef/application.rb, line 231
def auto_log_level?
  chef_config[:log_level] == :auto
end
check_license_acceptance() click to toggle source
# File lib/chef/application.rb, line 259
def check_license_acceptance
  LicenseAcceptance::Acceptor.check_and_persist!(
    "infra-client",
    Chef::VERSION.to_s,
    logger: logger,
    provided: Chef::Config[:chef_license]
  )
end
chef_config() click to toggle source

@api private (test injection)

# File lib/chef/application.rb, line 107
def chef_config
  Chef::Config
end
chef_configfetcher() click to toggle source

@api private (test injection)

# File lib/chef/application.rb, line 121
def chef_configfetcher
  Chef::ConfigFetcher
end
configure_chef() click to toggle source

Parse configuration (options and config file)

# File lib/chef/application.rb, line 98
def configure_chef
  parse_options
  load_config_file
  chef_config.export_proxies
  chef_config.init_openssl
  File.umask chef_config[:umask]
end
configure_encoding() click to toggle source

Sets the default external encoding to UTF-8 (users can change this, but they shouldn't)

# File lib/chef/application.rb, line 250
def configure_encoding
  Encoding.default_external = chef_config[:ruby_encoding]
end
configure_log_location() click to toggle source

Turn `log_location :syslog` and `log_location :win_evt` into the appropriate loggers.

# File lib/chef/application.rb, line 202
def configure_log_location
  log_location = chef_config[:log_location]
  return unless log_location.respond_to?(:to_sym)

  chef_config[:log_location] =
    case log_location.to_sym
      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
configure_logging() click to toggle source

Initialize and configure the logger.

Loggers and Formatters

In Chef 10.x and previous, the Logger was the primary/only way that Chef communicated information to the user. In Chef 10.14, a new system, “output formatters” was added, and in Chef 11.0+ it is the default when running chef in a console (detected by `STDOUT.tty?`). Because output formatters are more complex than the logger system and users have less experience with them, the config option `force_logger` is provided to restore the Chef 10.x behavior.

Conversely, for users who want formatter output even when chef is running unattended, the `force_formatter` option is provided.

Auto Log Level

The `log_level` of `:auto` means `:warn` in the formatter and `:info` in the logger.

# File lib/chef/application.rb, line 188
def configure_logging
  configure_log_location
  logger.init(MonoLogger.new(chef_config[:log_location]))
  if want_additional_logger?
    configure_stdout_logger
  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
configure_stdout_logger() click to toggle source
# File lib/chef/application.rb, line 220
def configure_stdout_logger
  stdout_logger = MonoLogger.new(STDOUT)
  stdout_logger.formatter = logger.logger.formatter
  logger.loggers << stdout_logger
end
emit_warnings() click to toggle source
# File lib/chef/application.rb, line 93
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
load_config_file() click to toggle source

Parse the config file

# File lib/chef/application.rb, line 126
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
logger() click to toggle source

@api private (test injection)

# File lib/chef/application.rb, line 112
def logger
  Chef::Log
end
reconfigure() click to toggle source

Reconfigure the application. You'll want to override and super this method.

# File lib/chef/application.rb, line 54
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
resolve_log_level() click to toggle source

if log_level is `:auto`, convert it to :warn (when using output formatter) or :info (no output formatter). See also using_output_formatter?

# File lib/chef/application.rb, line 237
def resolve_log_level
  if auto_log_level?
    if using_output_formatter?
      :warn
    else
      :info
    end
  else
    chef_config[:log_level]
  end
end
run(enforce_license: false) click to toggle source

Get this party started

# File lib/chef/application.rb, line 64
def run(enforce_license: false)
  setup_signal_handlers
  reconfigure
  setup_application
  check_license_acceptance if enforce_license
  run_application
end
run_application() click to toggle source

Actually run the application

# File lib/chef/application.rb, line 269
def run_application
  raise Chef::Exceptions::Application, "#{self}: you must override run_application"
end
run_chef_client(specific_recipes = []) click to toggle source

Initializes Chef::Client instance and runs it

# File lib/chef/application.rb, line 274
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_specific_recipes() click to toggle source

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 160
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 arguments are not supported by the chef-client: \"" +
      cli_arguments.select { |file| !File.file?(file) }.join('", "') + '"')
  end
end
setup_application() click to toggle source

Called prior to starting the application, by the run method

# File lib/chef/application.rb, line 255
def setup_application
  raise Chef::Exceptions::Application, "#{self}: you must override setup_application"
end
setup_signal_handlers() click to toggle source
# File lib/chef/application.rb, line 72
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 Chef::Platform.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
using_output_formatter?() click to toggle source

Use of output formatters is assumed if `force_formatter` is set or if `force_logger` is not set

# File lib/chef/application.rb, line 227
def using_output_formatter?
  chef_config[:force_formatter] || !chef_config[:force_logger]
end
want_additional_logger?() click to toggle source

Based on config and whether or not STDOUT is a tty, should we setup a secondary logger for stdout?

# File lib/chef/application.rb, line 216
def want_additional_logger?
  ( Chef::Config[:log_location].class != IO ) && STDOUT.tty? && !Chef::Config[:daemonize]
end

Private Instance Methods

apply_config(config_content, config_file_path) click to toggle source
# File lib/chef/application.rb, line 364
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 ) }
  Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", error)
end
can_fork?() click to toggle source
# File lib/chef/application.rb, line 304
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) && !Chef::Platform.windows?
end
env() click to toggle source

This is a hook for testing

# File lib/chef/application.rb, line 374
def env
  ENV
end
fork_chef_client() click to toggle source
# File lib/chef/application.rb, line 324
def fork_chef_client
  logger.info "Forking #{Chef::Dist::PRODUCT} instance to converge..."
  pid = fork do
    # Want to allow forked processes to finish converging when
    # TERM singal 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] ? "#{Chef::Dist::SOLOEXEC}" : "#{Chef::Dist::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 chef pid: #{pid}"
  result = Process.waitpid2(pid)
  handle_child_exit(result)
  logger.trace "Forked instance successfully reaped (pid: #{pid})"
  true
end
handle_child_exit(pid_and_status) click to toggle source
# File lib/chef/application.rb, line 353
def handle_child_exit(pid_and_status)
  status = pid_and_status[1]
  return true if status.success?
  message = if status.signaled?
              "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})"
            else
              "Chef run process exited unsuccessfully (exit code #{status.exitstatus})"
            end
  raise Exceptions::ChildConvergeError, message
end
run_with_graceful_exit_option() click to toggle source

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 313
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