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 = nil, cookbook_collection = {}, events = nil, 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:



183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/chef/run_context.rb', line 183

def initialize(node = nil, cookbook_collection = {}, events = nil, logger = nil)
  @events = events
  @logger = logger || Chef::Log.with_child
  @cookbook_collection = cookbook_collection
  self.node = node if node
  @definitions = Hash.new
  @loaded_recipes_hash = {}
  @loaded_attributes_hash = {}
  @reboot_info = {}
  @cookbook_compiler = nil
  @delayed_actions = []

  initialize_child_state
end

Instance Attribute Details

#action_collectionObject

Returns the value of attribute action_collection



122
123
124
# File 'lib/chef/run_context.rb', line 122

def action_collection
  @action_collection
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:



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

def before_notification_collection
  @before_notification_collection
end

#cookbook_collectionChef::CookbookCollection

The set of cookbooks involved in this run



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

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:



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

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:



165
166
167
# File 'lib/chef/run_context.rb', line 165

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:



157
158
159
# File 'lib/chef/run_context.rb', line 157

def delayed_notification_collection
  @delayed_notification_collection
end

#eventsChef::EventDispatch::Dispatcher

Event dispatcher for this run.



80
81
82
# File 'lib/chef/run_context.rb', line 80

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:



148
149
150
# File 'lib/chef/run_context.rb', line 148

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



172
173
174
# File 'lib/chef/run_context.rb', line 172

def logger
  @logger
end

#nodeChef::Node

The node for this run

Returns:



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

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.



99
100
101
# File 'lib/chef/run_context.rb', line 99

def parent_run_context
  @parent_run_context
end

#reboot_infoHash

Hash of factoids for a reboot request.

Returns:

  • (Hash)


87
88
89
# File 'lib/chef/run_context.rb', line 87

def reboot_info
  @reboot_info
end

#resource_collectionChef::ResourceCollection

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



120
121
122
# File 'lib/chef/run_context.rb', line 120

def resource_collection
  @resource_collection
end

#restChef::ServerAPI

Common rest object for using to talk to the Chef Server, this strictly 'validates' utf8 and will throw. (will be nil on solo-legacy runs)

Returns:



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

def rest
  @rest
end

#rest_cleanChef::ServerAPI

Common rest object for using to talk to the Chef Server, this has utf8 sanitization turned on and will replace invalid utf8 with valid characters. (will be nil on solo-legacy runs)

Returns:



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

def rest_clean
  @rest_clean
end

#runnerObject

Pointer back to the Chef::Runner that created this



126
127
128
# File 'lib/chef/run_context.rb', line 126

def runner
  @runner
end

Instance Method Details

#add_delayed_action(notification) ⇒ Object

Adds a delayed action to the delayed_actions.



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

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


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

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

#cancel_rebootObject

Cancels a pending reboot



581
582
583
584
# File 'lib/chef/run_context.rb', line 581

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

#create_childObject

Create a child RunContext.



613
614
615
# File 'lib/chef/run_context.rb', line 613

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


298
299
300
# File 'lib/chef/run_context.rb', line 298

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:



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

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:



509
510
511
512
# File 'lib/chef/run_context.rb', line 509

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


288
289
290
# File 'lib/chef/run_context.rb', line 288

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

    The cookbook we are currently running in.

See Also:



315
316
317
318
319
320
321
322
323
# File 'lib/chef/run_context.rb', line 315

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



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

def initialize_child_state
  @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:



210
211
212
213
# File 'lib/chef/run_context.rb', line 210

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)

    The cookbook we are currently running in.

Returns:

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

See Also:



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/chef/run_context.rb', line 340

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:



375
376
377
378
379
380
381
382
383
384
# File 'lib/chef/run_context.rb', line 375

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.



492
493
494
# File 'lib/chef/run_context.rb', line 492

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



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

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.



482
483
484
# File 'lib/chef/run_context.rb', line 482

def loaded_fully_qualified_attribute?(cookbook, attribute_file)
  loaded_attributes_hash.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.



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

def loaded_fully_qualified_recipe?(cookbook, recipe)
  loaded_recipes_hash.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.



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

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.



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

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:



421
422
423
# File 'lib/chef/run_context.rb', line 421

def loaded_recipes
  loaded_recipes_hash.keys
end

#notifies_before(notification) ⇒ Object

Adds an before notification to the before_notification_collection.

Parameters:



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

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:



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

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:



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

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)

    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:



553
554
555
556
557
558
559
560
561
562
563
564
# File 'lib/chef/run_context.rb', line 553

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)


590
591
592
# File 'lib/chef/run_context.rb', line 590

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



573
574
575
576
# File 'lib/chef/run_context.rb', line 573

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:



399
400
401
402
403
404
405
406
407
# File 'lib/chef/run_context.rb', line 399

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:



106
107
108
109
110
# File 'lib/chef/run_context.rb', line 106

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

#transportTrain::Plugins::Transport

Remote transport from Train

Returns:

  • (Train::Plugins::Transport)

    The child class for our train transport.



598
599
600
# File 'lib/chef/run_context.rb', line 598

def transport
  @transport ||= Chef::TrainTransport.build_transport(logger)
end

#transport_connectionTrain::Plugins::Transport::BaseConnection

Remote connection object from Train

Returns:

  • (Train::Plugins::Transport::BaseConnection)


606
607
608
# File 'lib/chef/run_context.rb', line 606

def transport_connection
  transport.connection
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?


537
538
539
# File 'lib/chef/run_context.rb', line 537

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