Class: Chef::RunContext

Inherits:
Object
  • Object
show all
Defined in:
lib/chef/run_context.rb,
lib/chef/run_context/cookbook_compiler.rb

Overview

Value object that loads and tracks the context of a Chef run

Direct Known Subclasses

ChildRunContext

Defined Under Namespace

Classes: ChildRunContext, CookbookCompiler

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(node, cookbook_collection, events, logger = nil) ⇒ RunContext

Creates a new Chef::RunContext object and populates its fields. This object gets used by the Chef Server to generate a fully compiled recipe list for a node.

Parameters:



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/chef/run_context.rb', line 172

def initialize(node, cookbook_collection, events, logger = nil)
  @node = node
  @cookbook_collection = cookbook_collection
  @events = events
  @logger = logger || Chef::Log.with_child

  node.run_context = self
  node.set_cookbook_attribute

  @definitions = Hash.new
  @loaded_recipes_hash = {}
  @loaded_attributes_hash = {}
  @reboot_info = {}
  @cookbook_compiler = nil
  @delayed_actions = []

  initialize_child_state
end

Instance Attribute Details

#auditsObject (readonly)

The list of control groups to execute during the audit phase



110
111
112
# File 'lib/chef/run_context.rb', line 110

def audits
  @audits
end

#before_notification_collectionHash[String, Array[Chef::Resource::Notification]] (readonly)

A Hash containing the before notifications triggered by resources during the converge phase of the chef run.

Returns:



128
129
130
# File 'lib/chef/run_context.rb', line 128

def before_notification_collection
  @before_notification_collection
end

#cookbook_collectionChef::CookbookCollection (readonly)

The set of cookbooks involved in this run



50
51
52
# File 'lib/chef/run_context.rb', line 50

def cookbook_collection
  @cookbook_collection
end

#definitionsArray[Chef::ResourceDefinition] (readonly)

Resource Definitions for this run. Populated when the files in definitions/ are evaluated (this is triggered by #load).

Returns:



58
59
60
# File 'lib/chef/run_context.rb', line 58

def definitions
  @definitions
end

#delayed_actionsArray[Chef::Resource::Notification] (readonly)

An Array containing the delayed (end of run) notifications triggered by resources during the converge phase of the chef run.

Returns:



154
155
156
# File 'lib/chef/run_context.rb', line 154

def delayed_actions
  @delayed_actions
end

#delayed_notification_collectionHash[String, Array[Chef::Resource::Notification]] (readonly)

A Hash containing the delayed (end of run) notifications triggered by resources during the converge phase of the chef run.

Returns:



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

def delayed_notification_collection
  @delayed_notification_collection
end

#eventsChef::EventDispatch::Dispatcher (readonly)

Event dispatcher for this run.



65
66
67
# File 'lib/chef/run_context.rb', line 65

def events
  @events
end

#immediate_notification_collectionHash[String, Array[Chef::Resource::Notification]] (readonly)

A Hash containing the immediate notifications triggered by resources during the converge phase of the chef run.

Returns:



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

def immediate_notification_collection
  @immediate_notification_collection
end

#loggerObject (readonly)

A child of the root Chef::Log logging object.

Returns:

  • Mixlib::Log::Child A child logger



161
162
163
# File 'lib/chef/run_context.rb', line 161

def logger
  @logger
end

#nodeChef::Node (readonly)

The node for this run

Returns:



43
44
45
# File 'lib/chef/run_context.rb', line 43

def node
  @node
end

#parent_run_contextChef::RunContext (readonly)

The parent run context.

Returns:

  • (Chef::RunContext)

    The parent run context, or ‘nil` if this is the root context.



84
85
86
# File 'lib/chef/run_context.rb', line 84

def parent_run_context
  @parent_run_context
end

#reboot_infoHash

Hash of factoids for a reboot request.

Returns:

  • (Hash)


72
73
74
# File 'lib/chef/run_context.rb', line 72

def reboot_info
  @reboot_info
end

#resource_collectionChef::ResourceCollection

The collection of resources intended to be converged (and able to be notified).



105
106
107
# File 'lib/chef/run_context.rb', line 105

def resource_collection
  @resource_collection
end

#runnerObject

Pointer back to the Chef::Runner that created this



115
116
117
# File 'lib/chef/run_context.rb', line 115

def runner
  @runner
end

Instance Method Details

#add_delayed_action(notification) ⇒ Object

Adds a delayed action to the delayed_actions.



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

def add_delayed_action(notification)
  if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) }
    logger.info( "#{notification.notifying_resource} not queuing delayed action #{notification.action} on #{notification.resource}"\
                   " (delayed), as it's already been queued")
  else
    delayed_actions << notification
  end
end

#before_notifications(resource) ⇒ Array[Notification]

Get the list of before notifications sent by the given resource.

Returns:

  • (Array[Notification])


267
268
269
# File 'lib/chef/run_context.rb', line 267

def before_notifications(resource)
  before_notification_collection[resource.declared_key]
end

#cancel_rebootObject

Cancels a pending reboot



569
570
571
572
# File 'lib/chef/run_context.rb', line 569

def cancel_reboot
  logger.info "Changing reboot status from #{reboot_info.inspect} to {}"
  @reboot_info = {}
end

#create_childObject

Create a child RunContext.



585
586
587
# File 'lib/chef/run_context.rb', line 585

def create_child
  ChildRunContext.new(self)
end

#delayed_notifications(resource) ⇒ Array[Notification]

Get the list of delayed (end of run) notifications sent by the given resource.

Returns:

  • (Array[Notification])


286
287
288
# File 'lib/chef/run_context.rb', line 286

def delayed_notifications(resource)
  delayed_notification_collection[resource.declared_key]
end

#has_cookbook_file_in_cookbook?(cookbook, cb_file_name) ⇒ Boolean

Find out if the cookbook has the given file.

Parameters:

  • cookbook (String)

    Cookbook name.

  • cb_file_name (String)

    File name.

Returns:

  • (Boolean)

    ‘true` if the file is in the cookbook, `false` otherwise.

See Also:



512
513
514
515
# File 'lib/chef/run_context.rb', line 512

def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
  cookbook = cookbook_collection[cookbook]
  cookbook.has_cookbook_file_for_node?(node, cb_file_name)
end

#has_template_in_cookbook?(cookbook, template_name) ⇒ Boolean

Find out if the cookbook has the given template.

Parameters:

  • cookbook (String)

    Cookbook name.

  • template_name (String)

    Template name.

Returns:

  • (Boolean)

    ‘true` if the template is in the cookbook, `false` otherwise.

See Also:



497
498
499
500
# File 'lib/chef/run_context.rb', line 497

def has_template_in_cookbook?(cookbook, template_name)
  cookbook = cookbook_collection[cookbook]
  cookbook.has_template_for_node?(node, template_name)
end

#immediate_notifications(resource) ⇒ Array[Notification]

Get the list of immediate notifications sent by the given resource.

Returns:

  • (Array[Notification])


276
277
278
# File 'lib/chef/run_context.rb', line 276

def immediate_notifications(resource)
  immediate_notification_collection[resource.declared_key]
end

#include_recipe(*recipe_names, current_cookbook: nil) ⇒ Object

Evaluates the recipes recipe_names. Used by DSL::IncludeRecipe

Parameters:

  • recipe_names (Array[String])

    The list of recipe names (e.g. ‘my_cookbook’ or ‘my_cookbook::my_resource’).

  • current_cookbook (defaults to: nil)

    The cookbook we are currently running in.

See Also:



303
304
305
306
307
308
309
310
311
# File 'lib/chef/run_context.rb', line 303

def include_recipe(*recipe_names, current_cookbook: nil)
  result_recipes = Array.new
  recipe_names.flatten.each do |recipe_name|
    if result = load_recipe(recipe_name, current_cookbook: current_cookbook)
      result_recipes << result
    end
  end
  result_recipes
end

#initialize_child_stateObject

Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext



205
206
207
208
209
210
211
212
# File 'lib/chef/run_context.rb', line 205

def initialize_child_state
  @audits = {}
  @resource_collection = Chef::ResourceCollection.new(self)
  @before_notification_collection = Hash.new { |h, k| h[k] = [] }
  @immediate_notification_collection = Hash.new { |h, k| h[k] = [] }
  @delayed_notification_collection = Hash.new { |h, k| h[k] = [] }
  @delayed_actions = []
end

#load(run_list_expansion) ⇒ Object

Triggers the compile phase of the chef run.

Parameters:

See Also:



197
198
199
200
# File 'lib/chef/run_context.rb', line 197

def load(run_list_expansion)
  @cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
  cookbook_compiler.compile
end

#load_recipe(recipe_name, current_cookbook: nil) ⇒ Object

Evaluates the recipe recipe_name. Used by DSL::IncludeRecipe

TODO I am sort of confused why we have both this and include_recipe …

I don't see anything different beyond accepting and returning an
array of recipes.

Parameters:

  • recipe_name (Array[String])

    The recipe name (e.g ‘my_cookbook’ or ‘my_cookbook::my_resource’).

  • current_cookbook (String) (defaults to: nil)

    The cookbook we are currently running in.

Returns:

  • A truthy value if the load occurred; ‘false` if already loaded.

See Also:



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/chef/run_context.rb', line 328

def load_recipe(recipe_name, current_cookbook: nil)
  logger.trace("Loading recipe #{recipe_name} via include_recipe")

  cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name, current_cookbook: current_cookbook)

  if unreachable_cookbook?(cookbook_name) # CHEF-4367
    logger.warn(<<-ERROR_MESSAGE)
MissingCookbookDependency:
Recipe `#{recipe_name}` is not in the run_list, and cookbook '#{cookbook_name}'
is not a dependency of any cookbook in the run_list.  To load this recipe,
first add a dependency on cookbook '#{cookbook_name}' in the cookbook you're
including it from in that cookbook's metadata.
ERROR_MESSAGE
  end

  if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name)
    logger.trace("I am not loading #{recipe_name}, because I have already seen it.")
    false
  else
    loaded_recipe(cookbook_name, recipe_short_name)
    node.loaded_recipe(cookbook_name, recipe_short_name)
    cookbook = cookbook_collection[cookbook_name]
    cookbook.load_recipe(recipe_short_name, self)
  end
end

#load_recipe_file(recipe_file) ⇒ Chef::Recipe

Load the given recipe from a filename.

Parameters:

  • recipe_file (String)

    The recipe filename.

Returns:

Raises:



363
364
365
366
367
368
369
370
371
372
# File 'lib/chef/run_context.rb', line 363

def load_recipe_file(recipe_file)
  if !File.exist?(recipe_file)
    raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
  end

  logger.trace("Loading recipe file #{recipe_file}")
  recipe = Chef::Recipe.new("@recipe_files", recipe_file, self)
  recipe.from_file(recipe_file)
  recipe
end

#loaded_attribute(cookbook, attribute_file) ⇒ Object

Mark a given attribute file as having been loaded.

Parameters:

  • cookbook (String)

    Cookbook name.

  • attribute_file (String)

    Attribute file name.



480
481
482
# File 'lib/chef/run_context.rb', line 480

def loaded_attribute(cookbook, attribute_file)
  loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true
end

#loaded_attributesArray[String]

A list of all attributes files that have been loaded.

Stored internally using a Hash, so order is predictable.

TODO is the above statement true in a 1.9+ ruby world? Is it relevant?

Returns:

  • (Array[String])

    A list of attribute file names in fully qualified form, e.g. the “nginx” will be given as “nginx::default”.



423
424
425
# File 'lib/chef/run_context.rb', line 423

def loaded_attributes
  loaded_attributes_hash.keys
end

#loaded_fully_qualified_attribute?(cookbook, attribute_file) ⇒ Boolean

Find out if a given attribute file has been loaded.

Parameters:

  • cookbook (String)

    Cookbook name.

  • attribute_file (String)

    Attribute file name.

Returns:

  • (Boolean)

    ‘true` if the recipe has been loaded, `false` otherwise.



470
471
472
# File 'lib/chef/run_context.rb', line 470

def loaded_fully_qualified_attribute?(cookbook, attribute_file)
  loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
end

#loaded_fully_qualified_recipe?(cookbook, recipe) ⇒ Boolean

Find out if a given recipe has been loaded.

Parameters:

  • cookbook (String)

    Cookbook name.

  • recipe (String)

    Recipe name.

Returns:

  • (Boolean)

    ‘true` if the recipe has been loaded, `false` otherwise.



435
436
437
# File 'lib/chef/run_context.rb', line 435

def loaded_fully_qualified_recipe?(cookbook, recipe)
  loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
end

#loaded_recipe(cookbook, recipe) ⇒ Object

Mark a given recipe as having been loaded.

Parameters:

  • cookbook (String)

    Cookbook name.

  • recipe (String)

    Recipe name.



458
459
460
# File 'lib/chef/run_context.rb', line 458

def loaded_recipe(cookbook, recipe)
  loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
end

#loaded_recipe?(recipe) ⇒ Boolean

Find out if a given recipe has been loaded.

Parameters:

  • recipe (String)

    Recipe name. “nginx” and “nginx::default” yield the same results.

Returns:

  • (Boolean)

    ‘true` if the recipe has been loaded, `false` otherwise.



447
448
449
450
# File 'lib/chef/run_context.rb', line 447

def loaded_recipe?(recipe)
  cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
  loaded_fully_qualified_recipe?(cookbook, recipe_name)
end

#loaded_recipesArray[String]

A list of all recipes that have been loaded.

This is stored internally as a Hash, so ordering is predictable.

TODO is the above statement true in a 1.9+ ruby world? Is it relevant?

Returns:

  • (Array[String])

    A list of recipes in fully qualified form, e.g. the recipe “nginx” will be given as “nginx::default”.

See Also:



409
410
411
# File 'lib/chef/run_context.rb', line 409

def loaded_recipes
  loaded_recipes_hash.keys
end

#notifies_before(notification) ⇒ Object

Adds an before notification to the before_notification_collection.

Parameters:



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

def notifies_before(notification)
  # Note for the future, notification.notifying_resource may be an instance
  # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
  # with a string value.
  before_notification_collection[notification.notifying_resource.declared_key] << notification
end

#notifies_delayed(notification) ⇒ Object

Adds a delayed notification to the delayed_notification_collection.

Parameters:



243
244
245
246
247
248
# File 'lib/chef/run_context.rb', line 243

def notifies_delayed(notification)
  # Note for the future, notification.notifying_resource may be an instance
  # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
  # with a string value.
  delayed_notification_collection[notification.notifying_resource.declared_key] << notification
end

#notifies_immediately(notification) ⇒ Object

Adds an immediate notification to the immediate_notification_collection.

Parameters:



231
232
233
234
235
236
# File 'lib/chef/run_context.rb', line 231

def notifies_immediately(notification)
  # Note for the future, notification.notifying_resource may be an instance
  # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes}
  # with a string value.
  immediate_notification_collection[notification.notifying_resource.declared_key] << notification
end

#open_stream(name: nil, **options) {|stream| ... } ⇒ EventDispatch::EventsOutputStream

Open a stream object that can be printed into and will dispatch to events

Parameters:

  • name (String) (defaults to: nil)

    The name of the stream.

  • options (Hash)

    Other options for the stream.

Yields:

  • If a block is passed, it will be run and the stream will be closed afterwards.

Yield Parameters:

Returns:



541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/chef/run_context.rb', line 541

def open_stream(name: nil, **options)
  stream = EventDispatch::EventsOutputStream.new(events, name: name, **options)
  if block_given?
    begin
      yield stream
    ensure
      stream.close
    end
  else
    stream
  end
end

#reboot_requested?Boolean

Checks to see if a reboot has been requested

Returns:

  • (Boolean)


578
579
580
# File 'lib/chef/run_context.rb', line 578

def reboot_requested?
  reboot_info.size > 0
end

#request_reboot(reboot_info) ⇒ Object

there are options for how to handle multiple calls to these functions:

  1. first call always wins (never change reboot_info once set).

  2. last call always wins (happily change reboot_info whenever).

  3. raise an exception on the first conflict.

  4. disable reboot after this run if anyone ever calls :cancel.

  5. raise an exception on any second call.

  6. ?



561
562
563
564
# File 'lib/chef/run_context.rb', line 561

def request_reboot(reboot_info)
  logger.info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
  @reboot_info = reboot_info
end

#resolve_attribute(cookbook_name, attr_file_name) ⇒ String

Look up an attribute filename.

Parameters:

  • cookbook_name (String)

    The cookbook name of the attribute file.

  • attr_file_name (String)

    The attribute file’s name (not path).

Returns:

Raises:

See Also:



387
388
389
390
391
392
393
394
395
# File 'lib/chef/run_context.rb', line 387

def resolve_attribute(cookbook_name, attr_file_name)
  cookbook = cookbook_collection[cookbook_name]
  raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook

  attribute_filename = cookbook.attribute_filenames_by_short_filename[attr_file_name]
  raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{attr_file_name} in cookbook #{cookbook_name}" unless attribute_filename

  attribute_filename
end

#root_run_contextChef::RunContext

The root run context.

Returns:



91
92
93
94
95
# File 'lib/chef/run_context.rb', line 91

def root_run_context
  rc = self
  rc = rc.parent_run_context until rc.parent_run_context.nil?
  rc
end

#unreachable_cookbook?(cookbook_name) ⇒ Boolean

Find out whether the given cookbook is in the cookbook dependency graph.

Parameters:

  • cookbook_name (String)

    Cookbook name.

Returns:

  • (Boolean)

    ‘true` if the cookbook is reachable, `false` otherwise.

See Also:

  • CookbookCompiler#unreachable_cookbook?


525
526
527
# File 'lib/chef/run_context.rb', line 525

def unreachable_cookbook?(cookbook_name)
  cookbook_compiler.unreachable_cookbook?(cookbook_name)
end