summaryrefslogtreecommitdiff
path: root/lib/chef/application.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/application.rb')
-rw-r--r--lib/chef/application.rb193
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