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.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# 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

Instance Attribute Details

#eventsObject (readonly)

Returns the value of attribute events.



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

def events
  @events
end

#json_attribsObject (readonly)

Returns the value of attribute json_attribs.



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

def json_attribs
  @json_attribs
end

#nodeObject

Returns the value of attribute node.



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

def node
  @node
end

#ohaiObject

Returns the value of attribute ohai.



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

def ohai
  @ohai
end

#restObject

Returns the value of attribute rest.



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

def rest
  @rest
end

#run_statusObject (readonly)

Returns the value of attribute run_status.



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

def run_status
  @run_status
end

#runnerObject

Returns the value of attribute runner.



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

def runner
  @runner
end

Class Method Details

.clear_notificationsObject

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



70
71
72
73
74
# 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

.run_completed_successfully_notificationsObject

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



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

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.



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

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

.run_start_notificationsObject

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



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

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.



102
103
104
# 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(&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.



109
110
111
# File 'lib/chef/client.rb', line 109

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.



95
96
97
# File 'lib/chef/client.rb', line 95

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



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

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

#configure_formattersObject



166
167
168
169
170
171
172
173
174
175
176
# 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) ⇒ Object

Converges the node.

Returns

true

Always returns true



341
342
343
344
345
346
347
348
349
350
351
352
# 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_formatterObject



186
187
188
189
190
191
192
# 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_checkObject



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# 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_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.



362
363
364
# File 'lib/chef/client.rb', line 362

def expanded_run_list
  policy_builder.expand_run_list
end

#formatters_for_runObject



178
179
180
181
182
183
184
# 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) ⇒ Object



231
232
233
234
235
236
237
238
239
240
# 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_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



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

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

#node_nameObject



295
296
297
298
299
300
301
302
303
304
305
306
307
308
# 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_builderObject



275
276
277
# 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) ⇒ Object

Returns

rest<Chef::REST>

returns Chef::REST connection object



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# 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

#runObject

Do a full run for this Chef::Client. Calls:

  • do_run

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.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# 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_successfullyObject

Callback to fire notifications that the run completed successfully



122
123
124
125
126
127
# 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_failedObject

Callback to fire notifications that the Chef run failed



130
131
132
133
134
135
# 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_ohaiObject



291
292
293
# File 'lib/chef/client.rb', line 291

def run_ohai
  ohai.all_plugins
end

#run_startedObject

Callback to fire notifications that the Chef run is starting



114
115
116
117
118
119
# 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_nodeObject



280
281
282
283
284
285
286
287
288
289
# 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_contextObject



264
265
266
267
268
269
# 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_cookbooksObject



271
272
273
# File 'lib/chef/client.rb', line 271

def sync_cookbooks
  policy_builder.sync_cookbooks
end