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



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/chef/run_context.rb', line 165

def initialize(node, cookbook_collection, events)
  @node = node
  @cookbook_collection = cookbook_collection
  @events = events

  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

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



245
246
247
248
249
250
251
252
# File 'lib/chef/run_context.rb', line 245

def add_delayed_action(notification)
  if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) }
    Chef::Log.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])


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

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

#cancel_rebootObject

Cancels a pending reboot



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

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

#create_childObject

Create a child RunContext.



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

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])


278
279
280
# File 'lib/chef/run_context.rb', line 278

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:



504
505
506
507
# File 'lib/chef/run_context.rb', line 504

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:



489
490
491
492
# File 'lib/chef/run_context.rb', line 489

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])


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

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:



295
296
297
298
299
300
301
302
303
# File 'lib/chef/run_context.rb', line 295

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



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

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:



189
190
191
192
# File 'lib/chef/run_context.rb', line 189

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_names (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:



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/chef/run_context.rb', line 320

def load_recipe(recipe_name, current_cookbook: nil)
  Chef::Log.debug("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
    Chef::Log.warn("MissingCookbookDependency:\nRecipe `\#{recipe_name}` is not in the run_list, and cookbook '\#{cookbook_name}'\nis not a dependency of any cookbook in the run_list.  To load this recipe,\nfirst add a dependency on cookbook '\#{cookbook_name}' in the cookbook you're\nincluding it from in that cookbook's metadata.\n")
  end

  if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name)
    Chef::Log.debug("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:



355
356
357
358
359
360
361
362
363
364
# File 'lib/chef/run_context.rb', line 355

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

  Chef::Log.debug("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.



472
473
474
# File 'lib/chef/run_context.rb', line 472

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



415
416
417
# File 'lib/chef/run_context.rb', line 415

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.



462
463
464
# File 'lib/chef/run_context.rb', line 462

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.



427
428
429
# File 'lib/chef/run_context.rb', line 427

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.



450
451
452
# File 'lib/chef/run_context.rb', line 450

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.



439
440
441
442
# File 'lib/chef/run_context.rb', line 439

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:



401
402
403
# File 'lib/chef/run_context.rb', line 401

def loaded_recipes
  loaded_recipes_hash.keys
end

#notifies_before(notification) ⇒ Object

Adds an before notification to the before_notification_collection.

Parameters:



211
212
213
214
215
216
# File 'lib/chef/run_context.rb', line 211

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:



235
236
237
238
239
240
# File 'lib/chef/run_context.rb', line 235

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:



223
224
225
226
227
228
# File 'lib/chef/run_context.rb', line 223

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:



533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/chef/run_context.rb', line 533

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)


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

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



553
554
555
556
# File 'lib/chef/run_context.rb', line 553

def request_reboot(reboot_info)
  Chef::Log.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:



379
380
381
382
383
384
385
386
387
# File 'lib/chef/run_context.rb', line 379

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?


517
518
519
# File 'lib/chef/run_context.rb', line 517

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