Class: Chef::Client

Inherits:
Object
  • Object
show all
Includes:
Mixin::PathSanity
Defined in:
lib/chef/client.rb

Overview

Chef::Client

The main object in a Chef run. Preps a Chef::Node and Chef::RunContext, syncs cookbooks if necessary, and triggers convergence.

Direct Known Subclasses

Shell::DoppelGangerClient

Constant Summary collapse

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.

STDOUT
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.

STDERR

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Mixin::PathSanity

#enforce_path_sanity

Constructor Details

#initialize(json_attribs = nil, args = {}) ⇒ Client

Creates a new Chef::Client.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/chef/client.rb', line 152

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 + configure_event_loggers
  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

  # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
  require 'chef/platform/provider_priority_map' unless defined? Chef::Platform::ProviderPriorityMap
  require 'chef/platform/resource_priority_map' unless defined? Chef::Platform::ResourcePriorityMap

  Chef.set_provider_priority_map(Chef::Platform::ProviderPriorityMap.instance)
  Chef.set_resource_priority_map(Chef::Platform::ResourcePriorityMap.instance)
end

Instance Attribute Details

#eventsObject (readonly)

Returns the value of attribute events.



149
150
151
# File 'lib/chef/client.rb', line 149

def events
  @events
end

#json_attribsObject (readonly)

Returns the value of attribute json_attribs.



147
148
149
# File 'lib/chef/client.rb', line 147

def json_attribs
  @json_attribs
end

#nodeObject

Returns the value of attribute node.



142
143
144
# File 'lib/chef/client.rb', line 142

def node
  @node
end

#ohaiObject

Returns the value of attribute ohai.



143
144
145
# File 'lib/chef/client.rb', line 143

def ohai
  @ohai
end

#restObject

Returns the value of attribute rest.



144
145
146
# File 'lib/chef/client.rb', line 144

def rest
  @rest
end

#run_statusObject (readonly)

Returns the value of attribute run_status.



148
149
150
# File 'lib/chef/client.rb', line 148

def run_status
  @run_status
end

#runnerObject

Returns the value of attribute runner.



145
146
147
# File 'lib/chef/client.rb', line 145

def runner
  @runner
end

Class Method Details

.clear_notificationsObject

Clears all notifications for client run status events. Primarily for testing purposes.



75
76
77
78
79
# File 'lib/chef/client.rb', line 75

def self.clear_notifications
  @run_start_notifications = nil
  @run_completed_successfully_notifications = nil
  @run_failed_notifications = nil
end

.run_completed_successfully_notificationsObject

The list of notifications to be run when the client run completes successfully.



88
89
90
# File 'lib/chef/client.rb', line 88

def self.run_completed_successfully_notifications
  @run_completed_successfully_notifications ||= []
end

.run_failed_notificationsObject

The list of notifications to be run when the client run fails.



93
94
95
# File 'lib/chef/client.rb', line 93

def self.run_failed_notifications
  @run_failed_notifications ||= []
end

.run_start_notificationsObject

The list of notifications to be run when the client run starts.



82
83
84
# File 'lib/chef/client.rb', line 82

def self.run_start_notifications
  @run_start_notifications ||= []
end

.when_run_completes_successfully(&notification_block) ⇒ Object

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.



107
108
109
# File 'lib/chef/client.rb', line 107

def self.when_run_completes_successfully(&notification_block)
  run_completed_successfully_notifications << notification_block
end

.when_run_fails(&notification_block) ⇒ Object

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.



114
115
116
# File 'lib/chef/client.rb', line 114

def self.when_run_fails(&notification_block)
  run_failed_notifications << notification_block
end

.when_run_starts(&notification_block) ⇒ Object

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.



100
101
102
# File 'lib/chef/client.rb', line 100

def self.when_run_starts(&notification_block)
  run_start_notifications << notification_block
end

Instance Method Details

#build_nodeChef::Node

Mutates the node object to prepare it for the chef run. Delegates to policy_builder

Returns:

  • The updated node object



249
250
251
252
253
# File 'lib/chef/client.rb', line 249

def build_node
  policy_builder.build_node
  @run_status = Chef::RunStatus.new(node, events)
  node
end

#configure_event_loggersObject



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/chef/client.rb', line 206

def configure_event_loggers
  if Chef::Config.disable_event_logger
    []
  else
    Chef::Config.event_loggers.map do |evt_logger|
      case evt_logger
      when Symbol
        Chef::EventLoggers.new(evt_logger)
      when Class
        evt_logger.new
      else
      end
    end
  end
end

#configure_formattersObject



178
179
180
181
182
183
184
185
186
187
188
# File 'lib/chef/client.rb', line 178

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) ⇒ Object

Converges the node.

Returns

The thrown exception, if there was one. If this returns nil the converge was successful.



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/chef/client.rb', line 332

def converge(run_context)
  converge_exception = nil
  catch(:end_client_run_early) do
    begin
      @events.converge_start(run_context)
      Chef::Log.debug("Converging node #{node_name}")
      @runner = Chef::Runner.new(run_context)
      runner.converge
      @events.converge_complete
    rescue Exception => e
      @events.converge_failed(e)
      raise e if Chef::Config[:audit_mode] == :disabled
      converge_exception = e
    end
  end
  converge_exception
end

#converge_and_save(run_context) ⇒ Object

We don’t want to change the old API on the converge method to have it perform saving. So we wrap it in this method.



352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/chef/client.rb', line 352

def converge_and_save(run_context)
  converge_exception = converge(run_context)
  unless converge_exception
    begin
      save_updated_node
    rescue Exception => e
      raise e if Chef::Config[:audit_mode] == :disabled
      converge_exception = e
    end
  end
  converge_exception
end

#default_formatterObject



198
199
200
201
202
203
204
# File 'lib/chef/client.rb', line 198

def default_formatter
  if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
    [:doc]
  else
    [:null]
  end
end

#do_windows_admin_checkObject



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/chef/client.rb', line 396

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_listObject

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.



392
393
394
# File 'lib/chef/client.rb', line 392

def expanded_run_list
  policy_builder.expand_run_list
end

#formatters_for_runObject



190
191
192
193
194
195
196
# File 'lib/chef/client.rb', line 190

def formatters_for_run
  if Chef::Config.formatters.empty?
    [default_formatter]
  else
    Chef::Config.formatters
  end
end

#load_nodeChef::Node

Instantiates a Chef::Node object, possibly loading the node’s prior state when using chef-client. Delegates to policy_builder. Injects the built node into the Chef class.

Returns:

  • The node object for this Chef run



238
239
240
241
242
243
# File 'lib/chef/client.rb', line 238

def load_node
  policy_builder.load_node
  @node = policy_builder.node
  Chef.set_node(@node)
  node
end

#node_nameObject

Raises:



287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/chef/client.rb', line 287

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_builderObject



266
267
268
# File 'lib/chef/client.rb', line 266

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) ⇒ Object

Returns

rest<Chef::REST>

returns Chef::REST connection object



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/chef/client.rb', line 305

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])
  register_reporters
rescue Exception => e
  # TODO: munge exception so a semantic failure message can be given to the
  # user
  @events.registration_failed(client_name, e, config)
  raise
end

#register_reportersObject

Resource repoters send event information back to the chef server for processing. Can only be called after we have a @rest object



224
225
226
227
228
229
230
231
# File 'lib/chef/client.rb', line 224

def register_reporters
  [
    Chef::ResourceReporter.new(rest),
    Chef::Audit::AuditReporter.new(rest)
  ].each do |r|
    events.register(r)
  end
end

#runObject

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.



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/chef/client.rb', line 425

def 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

    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

    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

    if Chef::Config[:audit_mode] != :audit_only
      converge_error = converge_and_save(run_context)
    end

    if Chef::Config[:why_run] == true
      # why_run should probably be renamed to why_converge
      Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes")
    elsif Chef::Config[:audit_mode] != :disabled
      audit_error = run_audits(run_context)
    end

    if converge_error || audit_error
      e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
      e.fill_backtrace
      raise e
    end

    run_status.stop_clock
    Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
    run_completed_successfully
    @events.run_completed(node)

    # rebooting has to be the last thing we do, no exceptions.
    Chef::Platform::Rebooter.reboot_if_needed!(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

#run_audits(run_context) ⇒ Object



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/chef/client.rb', line 365

def run_audits(run_context)
  audit_exception = nil
  begin
    @events.audit_phase_start(run_status)
    Chef::Log.info("Starting audit phase")
    auditor = Chef::Audit::Runner.new(run_context)
    auditor.run
    if auditor.failed?
      raise Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
    end
    @events.audit_phase_complete
  rescue Exception => e
    Chef::Log.error("Audit phase failed with error message: #{e.message}")
    @events.audit_phase_failed(e)
    audit_exception = e
  end
  audit_exception
end

#run_completed_successfullyObject

Callback to fire notifications that the run completed successfully



127
128
129
130
131
132
# File 'lib/chef/client.rb', line 127

def run_completed_successfully
  success_handlers = self.class.run_completed_successfully_notifications
  success_handlers.each do |notification|
    notification.call(run_status)
  end
end

#run_failedObject

Callback to fire notifications that the Chef run failed



135
136
137
138
139
140
# File 'lib/chef/client.rb', line 135

def run_failed
  failure_handlers = self.class.run_failed_notifications
  failure_handlers.each do |notification|
    notification.call(run_status)
  end
end

#run_ohaiObject



281
282
283
284
285
# File 'lib/chef/client.rb', line 281

def run_ohai
  filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil
  ohai.all_plugins(filter)
  @events.ohai_completed(node)
end

#run_startedObject

Callback to fire notifications that the Chef run is starting



119
120
121
122
123
124
# File 'lib/chef/client.rb', line 119

def run_started
  self.class.run_start_notifications.each do |notification|
    notification.call(run_status)
  end
  @events.run_started(run_status)
end

#save_updated_nodeObject



270
271
272
273
274
275
276
277
278
279
# File 'lib/chef/client.rb', line 270

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_contextObject



255
256
257
258
259
260
# File 'lib/chef/client.rb', line 255

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_cookbooksObject



262
263
264
# File 'lib/chef/client.rb', line 262

def sync_cookbooks
  policy_builder.sync_cookbooks
end