class Chef::Client
Chef::Client¶ ↑
The main object in a Chef run. Preps a Chef::Node and Chef::RunContext, syncs cookbooks if necessary, and triggers convergence.
Constants
- STDERR_FD
IO stream that will be used as 'STDERR' for formatters. Formatters are configured during `initialize`, so this provides a convenience for setting alternative IO stream during tests.
- STDOUT_FD
IO stream that will be used as 'STDOUT' for formatters. Formatters are configured during `initialize`, so this provides a convenience for setting alternative IO stream during tests.
Attributes
Public Class Methods
Clears all notifications for client run status events. Primarily for testing purposes.
# File lib/chef/client.rb, line 70 def self.clear_notifications @run_start_notifications = nil @run_completed_successfully_notifications = nil @run_failed_notifications = nil end
Creates a new Chef::Client.
# File lib/chef/client.rb, line 147 def initialize(json_attribs=nil, args={}) @json_attribs = json_attribs || {} @node = nil @run_status = nil @runner = nil @ohai = Ohai::System.new event_handlers = configure_formatters event_handlers += Array(Chef::Config[:event_handlers]) @events = EventDispatch::Dispatcher.new(*event_handlers) @override_runlist = args.delete(:override_runlist) @specific_recipes = args.delete(:specific_recipes) if new_runlist = args.delete(:runlist) @json_attribs["run_list"] = new_runlist end end
The list of notifications to be run when the client run completes successfully.
# File lib/chef/client.rb, line 83 def self.run_completed_successfully_notifications @run_completed_successfully_notifications ||= [] end
The list of notifications to be run when the client run fails.
# File lib/chef/client.rb, line 88 def self.run_failed_notifications @run_failed_notifications ||= [] end
The list of notifications to be run when the client run starts.
# File lib/chef/client.rb, line 77 def self.run_start_notifications @run_start_notifications ||= [] end
Add a notification for the 'client run success' event. The notification is provided as a block. The current Chef::RunStatus object will be passed to the notification_block when the event is triggered.
# File lib/chef/client.rb, line 102 def self.when_run_completes_successfully(¬ification_block) run_completed_successfully_notifications << notification_block end
Add a notification for the 'client run failed' event. The notification is provided as a block. The current Chef::RunStatus is passed to the notification_block when the event is triggered.
# File lib/chef/client.rb, line 109 def self.when_run_fails(¬ification_block) run_failed_notifications << notification_block end
Add a notification for the 'client run started' event. The notification is provided as a block. The current Chef::RunStatus object will be passed to the notification_block when the event is triggered.
# File lib/chef/client.rb, line 95 def self.when_run_starts(¬ification_block) run_start_notifications << notification_block end
Public Instance Methods
Mutates the `node` object to prepare it for the chef run. Delegates to #policy_builder
Returns¶ ↑
- Chef::Node
-
The updated node object
# File lib/chef/client.rb, line 258 def build_node policy_builder.build_node @run_status = Chef::RunStatus.new(node, events) node end
# File lib/chef/client.rb, line 166 def configure_formatters formatters_for_run.map do |formatter_name, output_path| if output_path.nil? Chef::Formatters.new(formatter_name, STDOUT_FD, STDERR_FD) else io = File.open(output_path, "a+") io.sync = true Chef::Formatters.new(formatter_name, io, io) end end end
Converges the node.
Returns¶ ↑
- true
-
Always returns true
# File lib/chef/client.rb, line 341 def converge(run_context) @events.converge_start(run_context) Chef::Log.debug("Converging node #{node_name}") @runner = Chef::Runner.new(run_context) runner.converge @events.converge_complete true rescue Exception # TODO: should this be a separate #converge_failed(exception) method? @events.converge_complete raise end
# File lib/chef/client.rb, line 186 def default_formatter if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter] [:doc] else [:null] end end
# File lib/chef/client.rb, line 367 def do_windows_admin_check if Chef::Platform.windows? Chef::Log.debug("Checking for administrator privileges....") if !has_admin_privileges? message = "chef-client doesn't have administrator privileges on node #{node_name}." if Chef::Config[:fatal_windows_admin_check] Chef::Log.fatal(message) Chef::Log.fatal("fatal_windows_admin_check is set to TRUE.") raise Chef::Exceptions::WindowsNotAdmin, message else Chef::Log.warn("#{message} This might cause unexpected resource failures.") end else Chef::Log.debug("chef-client has administrator privileges on node #{node_name}.") end end end
Expands the run list. Delegates to the policy_builder.
Normally this does not need to be called from here, it will be called by build_node. This is provided so external users (like the chefspec project) can inject custom behavior into the run process.
Returns¶ ↑
RunListExpansion: A RunListExpansion or API compatible object.
# File lib/chef/client.rb, line 362 def expanded_run_list policy_builder.expand_run_list end
# File lib/chef/client.rb, line 178 def formatters_for_run if Chef::Config.formatters.empty? [default_formatter] else Chef::Config.formatters end end
# File lib/chef/client.rb, line 231 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
Instantiates a Chef::Node object, possibly loading the node's prior state when using chef-client. Delegates to #policy_builder
Returns¶ ↑
- Chef::Node
-
The node object for this chef run
# File lib/chef/client.rb, line 248 def load_node policy_builder.load_node @node = policy_builder.node end
# File lib/chef/client.rb, line 295 def node_name name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname] Chef::Config[:node_name] = name raise Chef::Exceptions::CannotDetermineNodeName unless name # node names > 90 bytes only work with authentication protocol >= 1.1 # see discussion in config.rb. if name.bytesize > 90 Chef::Config[:authentication_protocol_version] = "1.1" end name end
# File lib/chef/client.rb, line 275 def policy_builder @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, @override_runlist, events) end
Returns¶ ↑
- rest<Chef::REST>
-
returns Chef::REST connection object
# File lib/chef/client.rb, line 313 def register(client_name=node_name, config=Chef::Config) if !config[:client_key] @events.skipping_registration(client_name, config) Chef::Log.debug("Client key is unspecified - skipping registration") elsif File.exists?(config[:client_key]) @events.skipping_registration(client_name, config) Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration") else @events.registration_start(node_name, config) Chef::Log.info("Client key #{config[:client_key]} is not present - registering") Chef::ApiClient::Registration.new(node_name, config[:client_key]).run @events.registration_completed end # We now have the client key, and should use it from now on. @rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key]) @resource_reporter = Chef::ResourceReporter.new(@rest) @events.register(@resource_reporter) rescue Exception => e # TODO: munge exception so a semantic failure message can be given to the # user @events.registration_failed(node_name, e, config) raise end
Do a full run for this Chef::Client. Calls:
This provides a wrapper around do_run allowing the run to be optionally forked.
Returns¶ ↑
- boolean
-
Return value from do_run. Should always returns true.
# File lib/chef/client.rb, line 201 def run # win32-process gem exposes some form of :fork for Process # class. So we are seperately ensuring that the platform we're # running on is not windows before forking. if(Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows?) Chef::Log.info "Forking chef instance to converge..." pid = fork do [:INT, :TERM].each {|s| trap(s, "EXIT") } 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" do_run rescue Exception => e Chef::Log.error(e.to_s) exit 1 else exit 0 end end Chef::Log.debug "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})" true else do_run end end
Callback to fire notifications that the run completed successfully
# File lib/chef/client.rb, line 122 def run_completed_successfully success_handlers = self.class.run_completed_successfully_notifications success_handlers.each do |notification| notification.call(run_status) end end
Callback to fire notifications that the Chef run failed
# File lib/chef/client.rb, line 130 def run_failed failure_handlers = self.class.run_failed_notifications failure_handlers.each do |notification| notification.call(run_status) end end
# File lib/chef/client.rb, line 291 def run_ohai ohai.all_plugins end
Callback to fire notifications that the Chef run is starting
# File lib/chef/client.rb, line 114 def run_started self.class.run_start_notifications.each do |notification| notification.call(run_status) end @events.run_started(run_status) end
# File lib/chef/client.rb, line 280 def save_updated_node if Chef::Config[:solo] # nothing to do elsif policy_builder.temporary_policy? Chef::Log.warn("Skipping final node save because override_runlist was given") else Chef::Log.debug("Saving the current state of node #{node_name}") @node.save end end
# File lib/chef/client.rb, line 264 def setup_run_context run_context = policy_builder.setup_run_context(@specific_recipes) assert_cookbook_path_not_empty(run_context) run_status.run_context = run_context run_context end
# File lib/chef/client.rb, line 271 def sync_cookbooks policy_builder.sync_cookbooks end
Private Instance Methods
# File lib/chef/client.rb, line 471 def assert_cookbook_path_not_empty(run_context) if Chef::Config[:solo] # Check for cookbooks in the path given # Chef::Config[:cookbook_path] can be a string or an array # if it's an array, go through it and check each one, raise error at the last one if no files are found cookbook_paths = Array(Chef::Config[:cookbook_path]) Chef::Log.debug "Loading from cookbook_path: #{cookbook_paths.map { |path| File.expand_path(path) }.join(', ')}" if cookbook_paths.all? {|path| empty_directory?(path) } msg = "None of the cookbook paths set in Chef::Config[:cookbook_path], #{cookbook_paths.inspect}, contain any cookbooks" Chef::Log.fatal(msg) raise Chef::Exceptions::CookbookNotFound, msg end else Chef::Log.warn("Node #{node_name} has an empty run list.") if run_context.node.run_list.empty? end end
# File lib/chef/client.rb, line 495 def check_ssl_config if Chef::Config[:ssl_verify_mode] == :verify_none and !Chef::Config[:verify_api_cert] Chef::Log.warn(" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * SSL validation of HTTPS requests is disabled. HTTPS connections are still encrypted, but chef is not able to detect forged replies or man in the middle attacks. To fix this issue add an entry like this to your configuration file: ``` # Verify all HTTPS connections (recommended) ssl_verify_mode :verify_peer # OR, Verify only connections to chef-server verify_api_cert true ``` To check your SSL configuration, or troubleshoot errors, you can use the `knife ssl check` command like so: ``` knife ssl check -c #{Chef::Config.config_file} ``` * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ") end end
Do a full run for this Chef::Client. Calls:
* run_ohai - Collect information about the system * build_node - Get the last known state, merge with local changes * register - If not in solo mode, make sure the server knows about this client * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks * converge - Bring this system up to date
Returns¶ ↑
- true
-
Always returns true.
# File lib/chef/client.rb, line 398 def do_run runlock = RunLock.new(Chef::Config.lockfile) runlock.acquire # don't add code that may fail before entering this section to be sure to release lock begin runlock.save_pid check_ssl_config request_id = Chef::RequestID.instance.request_id run_context = nil @events.run_start(Chef::VERSION) Chef::Log.info("*** Chef #{Chef::VERSION} ***") Chef::Log.info "Chef-client pid: #{Process.pid}" Chef::Log.debug("Chef-client request_id: #{request_id}") enforce_path_sanity run_ohai @events.ohai_completed(node) register unless Chef::Config[:solo] load_node build_node run_status.run_id = request_id run_status.start_clock Chef::Log.info("Starting Chef Run for #{node.name}") run_started do_windows_admin_check run_context = setup_run_context converge(run_context) save_updated_node run_status.stop_clock Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds") run_completed_successfully @events.run_completed(node) true rescue Exception => e # CHEF-3336: Send the error first in case something goes wrong below and we don't know why Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}") # If we failed really early, we may not have a run_status yet. Too early for these to be of much use. if run_status run_status.stop_clock run_status.exception = e run_failed end Chef::Application.debug_stacktrace(e) @events.run_failed(e) raise ensure Chef::RequestID.instance.reset_request_id request_id = nil @run_status = nil run_context = nil runlock.release GC.start end true end
# File lib/chef/client.rb, line 463 def empty_directory?(path) !File.exists?(path) || (Dir.entries(path).size <= 2) end
# File lib/chef/client.rb, line 489 def has_admin_privileges? require 'chef/win32/security' Chef::ReservedNames::Win32::Security.has_admin_privileges? end
# File lib/chef/client.rb, line 467 def is_last_element?(index, object) object.kind_of?(Array) ? index == object.size - 1 : true end