Class: Chef::Client

Inherits:
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.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/chef/client.rb', line 149

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.



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

def events
  @events
end

#json_attribsObject (readonly)

Returns the value of attribute json_attribs.



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

def json_attribs
  @json_attribs
end

#nodeObject

Returns the value of attribute node.



139
140
141
# File 'lib/chef/client.rb', line 139

def node
  @node
end

#ohaiObject

Returns the value of attribute ohai.



140
141
142
# File 'lib/chef/client.rb', line 140

def ohai
  @ohai
end

#restObject

Returns the value of attribute rest.



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

def rest
  @rest
end

#run_statusObject (readonly)

Returns the value of attribute run_status.



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

def run_status
  @run_status
end

#runnerObject

Returns the value of attribute runner.



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

def runner
  @runner
end

Class Method Details

.clear_notificationsObject

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



72
73
74
75
76
# File 'lib/chef/client.rb', line 72

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.



85
86
87
# File 'lib/chef/client.rb', line 85

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.



90
91
92
# File 'lib/chef/client.rb', line 90

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

.run_start_notificationsObject

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



79
80
81
# File 'lib/chef/client.rb', line 79

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.



104
105
106
# File 'lib/chef/client.rb', line 104

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.



111
112
113
# File 'lib/chef/client.rb', line 111

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.



97
98
99
# File 'lib/chef/client.rb', line 97

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



228
229
230
231
232
# File 'lib/chef/client.rb', line 228

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

#configure_event_loggersObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/chef/client.rb', line 196

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



168
169
170
171
172
173
174
175
176
177
178
# File 'lib/chef/client.rb', line 168

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

true

Always returns true



311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/chef/client.rb', line 311

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_formatterObject



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

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

#do_windows_admin_checkObject



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/chef/client.rb', line 337

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.



332
333
334
# File 'lib/chef/client.rb', line 332

def expanded_run_list
  policy_builder.expand_run_list
end

#formatters_for_runObject



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

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



218
219
220
221
# File 'lib/chef/client.rb', line 218

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

#node_nameObject



265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/chef/client.rb', line 265

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



245
246
247
# File 'lib/chef/client.rb', line 245

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



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/chef/client.rb', line 283

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(client_name, e, config)
  raise
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.



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/chef/client.rb', line 366

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

    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

    catch(:end_client_run_early) do
      converge(run_context)
    end

    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)

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

Callback to fire notifications that the run completed successfully



124
125
126
127
128
129
# File 'lib/chef/client.rb', line 124

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



132
133
134
135
136
137
# File 'lib/chef/client.rb', line 132

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

#run_ohaiObject



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

def run_ohai
  ohai.all_plugins
end

#run_startedObject

Callback to fire notifications that the Chef run is starting



116
117
118
119
120
121
# File 'lib/chef/client.rb', line 116

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



250
251
252
253
254
255
256
257
258
259
# File 'lib/chef/client.rb', line 250

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



234
235
236
237
238
239
# File 'lib/chef/client.rb', line 234

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



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

def sync_cookbooks
  policy_builder.sync_cookbooks
end