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

events[R]
json_attribs[R]
node[RW]
ohai[RW]
rest[RW]
run_status[R]
runner[RW]

Public Class Methods

clear_notifications() click to toggle source

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
new(json_attribs=nil, args={}) click to toggle source

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
run_completed_successfully_notifications() click to toggle source

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
run_failed_notifications() click to toggle source

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
run_start_notifications() click to toggle source

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
when_run_completes_successfully(¬ification_block) click to toggle source

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(&notification_block)
  run_completed_successfully_notifications << notification_block
end
when_run_fails(¬ification_block) click to toggle source

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(&notification_block)
  run_failed_notifications << notification_block
end
when_run_starts(¬ification_block) click to toggle source

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(&notification_block)
  run_start_notifications << notification_block
end

Public Instance Methods

build_node() click to toggle source

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
configure_formatters() click to toggle source
# 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
converge(run_context) click to toggle source

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
default_formatter() click to toggle source
# 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
do_windows_admin_check() click to toggle source
# 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
expanded_run_list() click to toggle source

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
formatters_for_run() click to toggle source
# File lib/chef/client.rb, line 178
def formatters_for_run
  if Chef::Config.formatters.empty?
    [default_formatter]
  else
    Chef::Config.formatters
  end
end
handle_child_exit(pid_and_status) click to toggle source
# 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
load_node() click to toggle source

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
node_name() click to toggle source
# 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
policy_builder() click to toggle source
# 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
register(client_name=node_name, config=Chef::Config) click to toggle source

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
run() click to toggle source

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
run_completed_successfully() click to toggle source

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
run_failed() click to toggle source

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
run_ohai() click to toggle source
# File lib/chef/client.rb, line 291
def run_ohai
  ohai.all_plugins
end
run_started() click to toggle source

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
save_updated_node() click to toggle source
# 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
setup_run_context() click to toggle source
# 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
sync_cookbooks() click to toggle source
# File lib/chef/client.rb, line 271
def sync_cookbooks
  policy_builder.sync_cookbooks
end

Private Instance Methods

assert_cookbook_path_not_empty(run_context) click to toggle source
# 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
check_ssl_config() click to toggle source
# 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_run() click to toggle source

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
empty_directory?(path) click to toggle source
# File lib/chef/client.rb, line 463
def empty_directory?(path)
  !File.exists?(path) || (Dir.entries(path).size <= 2)
end
has_admin_privileges?() click to toggle source
# File lib/chef/client.rb, line 489
def has_admin_privileges?
  require 'chef/win32/security'

  Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
is_last_element?(index, object) click to toggle source
# File lib/chef/client.rb, line 467
def is_last_element?(index, object)
  object.kind_of?(Array) ? index == object.size - 1 : true
end