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
# 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
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_nodeObject

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

Returns

Chef::Node

The updated node object



242
243
244
245
246
# File 'lib/chef/client.rb', line 242

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

#configure_event_loggersObject



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/chef/client.rb', line 199

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



171
172
173
174
175
176
177
178
179
180
181
# File 'lib/chef/client.rb', line 171

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.



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/chef/client.rb', line 324

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.



344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/chef/client.rb', line 344

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



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

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

#do_windows_admin_checkObject



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'lib/chef/client.rb', line 388

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.



384
385
386
# File 'lib/chef/client.rb', line 384

def expanded_run_list
  policy_builder.expand_run_list
end

#formatters_for_runObject



183
184
185
186
187
188
189
# File 'lib/chef/client.rb', line 183

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

#load_nodeObject

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



232
233
234
235
# File 'lib/chef/client.rb', line 232

def load_node
  policy_builder.load_node
  @node = policy_builder.node
end

#node_nameObject



279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/chef/client.rb', line 279

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



259
260
261
# File 'lib/chef/client.rb', line 259

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



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/chef/client.rb', line 297

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



217
218
219
220
221
222
223
224
# File 'lib/chef/client.rb', line 217

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.



417
418
419
420
421
422
423
424
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
# File 'lib/chef/client.rb', line 417

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



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/chef/client.rb', line 357

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



274
275
276
277
# File 'lib/chef/client.rb', line 274

def run_ohai
  ohai.all_plugins
  @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



263
264
265
266
267
268
269
270
271
272
# File 'lib/chef/client.rb', line 263

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



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

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



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

def sync_cookbooks
  policy_builder.sync_cookbooks
end