Class: Chef::RunContext

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
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 = nil, 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:


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/chef/run_context.rb', line 222

def initialize(node = nil, cookbook_collection = nil, events = nil, logger = nil)
  @events = events
  @logger = logger || Chef::Log.with_child
  self.node = node if node
  self.cookbook_collection = cookbook_collection if cookbook_collection
  @definitions = {}
  @loaded_recipes_hash = {}
  @loaded_attributes_hash = {}
  @reboot_info = {}
  @cookbook_compiler = nil
  @input_collection = Chef::Compliance::InputCollection.new(events)
  @waiver_collection = Chef::Compliance::WaiverCollection.new(events)
  @profile_collection = Chef::Compliance::ProfileCollection.new(events)
  @default_secret_service = nil
  @default_secret_config = {}

  initialize_child_state
end

Instance Attribute Details

#action_collectionChef::ActionCollection

Handle to the global action_collection of executed actions for reporting / data_collector /etc


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

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:


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

def before_notification_collection
  @before_notification_collection
end

#cookbook_collectionChef::CookbookCollection

The set of cookbooks involved in this run


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

def cookbook_collection
  @cookbook_collection
end

#default_secret_configHash<Symbol,Object>

Returns:


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

def default_secret_config
  @default_secret_config
end

#default_secret_serviceSymbol?

Returns:


151
152
153
# File 'lib/chef/run_context.rb', line 151

def default_secret_service
  @default_secret_service
end

#definitionsArray[Chef::ResourceDefinition] (readonly)

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

Returns:


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

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:


195
196
197
# File 'lib/chef/run_context.rb', line 195

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:


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

def delayed_notification_collection
  @delayed_notification_collection
end

#eventsChef::EventDispatch::Dispatcher

Event dispatcher for this run.


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

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:


180
181
182
# File 'lib/chef/run_context.rb', line 180

def immediate_notification_collection
  @immediate_notification_collection
end

#input_collectionChef::Compliance::inputCollection

Handle to the global input_collection of inspec input files for the compliance phase

Returns:

  • (Chef::Compliance::inputCollection)

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

def input_collection
  @input_collection
end

#loggerObject (readonly)

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

Returns:

  • Mixlib::Log::Child A child logger


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

def logger
  @logger
end

#nodeChef::Node

The node for this run

Returns:


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

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.


103
104
105
# File 'lib/chef/run_context.rb', line 103

def parent_run_context
  @parent_run_context
end

#profile_collectionChef::Compliance::ProfileCollection

Handle to the global profile_collection of inspec profiles for the compliance phase


134
135
136
# File 'lib/chef/run_context.rb', line 134

def profile_collection
  @profile_collection
end

#reboot_infoHash

Hash of factoids for a reboot request.

Returns:

  • (Hash)

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

def reboot_info
  @reboot_info
end

#resource_collectionBoolean

Returns If the resource_collection is in unified_mode (no separate converge phase).

Returns:

  • (Boolean)

    If the resource_collection is in unified_mode (no separate converge phase)


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

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:


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

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:


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

def rest_clean
  @rest_clean
end

#runnerObject

Pointer back to the Chef::Runner that created this


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

def runner
  @runner
end

#updated_resourcesObject (readonly)

A Set keyed by the string name, of all the resources that are updated. We do not track actions or individual resource objects, since this matches the behavior of the notification collections which are keyed by Strings.


201
202
203
# File 'lib/chef/run_context.rb', line 201

def updated_resources
  @updated_resources
end

#waiver_collectionChef::Compliance::WaiverCollection

Handle to the global waiver_collection of inspec waiver files for the compliance phase


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

def waiver_collection
  @waiver_collection
end

Instance Method Details

#add_delayed_action(notification) ⇒ Object

Adds a delayed action to the delayed_actions collection


319
320
321
322
323
324
325
326
# File 'lib/chef/run_context.rb', line 319

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

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

def before_notifications(resource)
  key = resource.is_a?(String) ? resource : resource.declared_key
  before_notification_collection[key]
end

#cancel_rebootObject

Cancels a pending reboot


648
649
650
651
# File 'lib/chef/run_context.rb', line 648

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

#create_childObject

Create a child RunContext.


680
681
682
# File 'lib/chef/run_context.rb', line 680

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

364
365
366
367
# File 'lib/chef/run_context.rb', line 364

def delayed_notifications(resource)
  key = resource.is_a?(String) ? resource : resource.declared_key
  delayed_notification_collection[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:


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

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:


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

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

341
342
343
344
# File 'lib/chef/run_context.rb', line 341

def immediate_notifications(resource)
  key = resource.is_a?(String) ? resource : resource.declared_key
  immediate_notification_collection[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:


382
383
384
385
386
387
388
389
390
# File 'lib/chef/run_context.rb', line 382

def include_recipe(*recipe_names, current_cookbook: nil)
  result_recipes = []
  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


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

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 = []
  @updated_resources = Set.new
end

#load(run_list_expansion) ⇒ Object

Triggers the compile phase of the chef run.

Parameters:

See Also:


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

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:


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
# File 'lib/chef/run_context.rb', line 407

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:


442
443
444
445
446
447
448
449
450
451
# File 'lib/chef/run_context.rb', line 442

def load_recipe_file(recipe_file)
  unless 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.


559
560
561
# File 'lib/chef/run_context.rb', line 559

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


502
503
504
# File 'lib/chef/run_context.rb', line 502

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.


549
550
551
# File 'lib/chef/run_context.rb', line 549

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.


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

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.


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

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.


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

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:


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

def loaded_recipes
  loaded_recipes_hash.keys
end

#notifies_before(notification) ⇒ Object

Adds an before notification to the +before_notification_collection+.

Parameters:


279
280
281
282
283
284
285
286
287
288
# File 'lib/chef/run_context.rb', line 279

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.
  if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key)
    raise Chef::Exceptions::UnifiedModeBeforeSubscriptionEarlierResource.new(notification)
  end

  before_notification_collection[notification.notifying_resource.declared_key] << notification
end

#notifies_delayed(notification) ⇒ Object

Adds a delayed notification to the +delayed_notification_collection+.

Parameters:


307
308
309
310
311
312
313
314
315
# File 'lib/chef/run_context.rb', line 307

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.
  if unified_mode && updated_resources.include?(notification.notifying_resource.declared_key)
    add_delayed_action(notification)
  end
  delayed_notification_collection[notification.notifying_resource.declared_key] << notification
end

#notifies_immediately(notification) ⇒ Object

Adds an immediate notification to the +immediate_notification_collection+.

Parameters:


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

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:


620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/chef/run_context.rb', line 620

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)

657
658
659
# File 'lib/chef/run_context.rb', line 657

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

640
641
642
643
# File 'lib/chef/run_context.rb', line 640

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:


466
467
468
469
470
471
472
473
474
# File 'lib/chef/run_context.rb', line 466

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

#reverse_immediate_notifications(resource) ⇒ Array[Notification]

Get the list of immediate notifications pending to the given resource

Returns:

  • (Array[Notification])

350
351
352
353
354
355
356
357
# File 'lib/chef/run_context.rb', line 350

def reverse_immediate_notifications(resource)
  immediate_notification_collection.map do |k, v|
    v.select do |n|
      (n.resource.is_a?(String) && n.resource == resource.declared_key) ||
        n.resource == resource
    end
  end.flatten
end

#root_run_contextChef::RunContext

The root run context.

Returns:


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

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.


665
666
667
# File 'lib/chef/run_context.rb', line 665

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

#transport_connectionTrain::Plugins::Transport::BaseConnection

Remote connection object from Train

Returns:

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

673
674
675
# File 'lib/chef/run_context.rb', line 673

def transport_connection
  @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?

604
605
606
# File 'lib/chef/run_context.rb', line 604

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