Class: Chef::PolicyBuilder::Policyfile

Inherits:
Object
  • Object
show all
Defined in:
lib/chef/policy_builder/policyfile.rb

Overview

Policyfile is a policy builder implementation that gets run list and cookbook version information from a single document.

== Unsupported Options:

  • override_runlist:: This could potentially be integrated into the policyfile, or replaced with a similar feature that has different semantics.
  • specific_recipes:: put more design thought into this use case.
  • run_list in json_attribs:: would be ignored anyway, so it raises an error.
  • chef-solo:: not currently supported. Need more design thought around how this should work.

Defined Under Namespace

Classes: ConfigurationError, PolicyfileError, RunListExpansionIsh, UnsupportedFeature

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(node_name, ohai_data, json_attribs, override_runlist, events) ⇒ Policyfile

Returns a new instance of Policyfile.


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/chef/policy_builder/policyfile.rb', line 85

def initialize(node_name, ohai_data, json_attribs, override_runlist, events)
  @node_name = node_name
  @ohai_data = ohai_data
  @json_attribs = json_attribs
  @events = events

  @node = nil

  if Chef::Config[:solo_legacy_mode]
    raise UnsupportedFeature, "Policyfile does not support chef-solo. Use #{ChefUtils::Dist::Infra::CLIENT} local mode instead."
  end

  if override_runlist
    raise UnsupportedFeature, "Policyfile does not support override run lists. Use named run_lists instead."
  end

  if json_attribs && json_attribs.key?("run_list")
    raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data."
  end

  if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty?
    raise UnsupportedFeature, "Policyfile does not work with an Environment configured."
  end
end

Instance Attribute Details

#eventsObject (readonly)

Returns the value of attribute events


78
79
80
# File 'lib/chef/policy_builder/policyfile.rb', line 78

def events
  @events
end

#json_attribsObject (readonly)

Returns the value of attribute json_attribs


82
83
84
# File 'lib/chef/policy_builder/policyfile.rb', line 82

def json_attribs
  @json_attribs
end

#nodeObject (readonly)

Returns the value of attribute node


79
80
81
# File 'lib/chef/policy_builder/policyfile.rb', line 79

def node
  @node
end

#node_nameObject (readonly)

Returns the value of attribute node_name


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

def node_name
  @node_name
end

#ohai_dataObject (readonly)

Returns the value of attribute ohai_data


81
82
83
# File 'lib/chef/policy_builder/policyfile.rb', line 81

def ohai_data
  @ohai_data
end

#run_contextObject (readonly)

Returns the value of attribute run_context


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

def run_context
  @run_context
end

Instance Method Details

#api_serviceObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


502
503
504
505
# File 'lib/chef/policy_builder/policyfile.rb', line 502

def api_service
  @api_service ||= Chef::ServerAPI.new(config[:chef_server_url],
    { version_class: Chef::CookbookManifestVersions })
end

#apply_policyfile_attributesObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sets attributes from the policyfile on the node, using the role priority.


273
274
275
276
277
# File 'lib/chef/policy_builder/policyfile.rb', line 273

def apply_policyfile_attributes
  node.attributes.role_default = policy["default_attributes"]
  node.attributes.role_override = policy["override_attributes"]
  hoist_policyfile_attributes(node.policy_group) if node.policy_group
end

#build_nodeObject

Applies environment, external JSON attributes, and override run list to the node, Then expands the run_list.

=== Returns nodeChef::Node:: The modified node object. node is modified in place.


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/chef/policy_builder/policyfile.rb', line 147

def build_node
  # consume_external_attrs may add items to the run_list. Save the
  # expanded run_list, which we will pass to the server later to
  # determine which versions of cookbooks to use.
  node.reset_defaults_and_overrides

  node.consume_external_attrs(ohai_data, json_attribs)

  expand_run_list
  apply_policyfile_attributes

  Chef::Log.info("Run List is [#{run_list}]")
  Chef::Log.info("Run List expands to [#{run_list_with_versions_for_display.join(", ")}]")

  events.node_load_completed(node, run_list_with_versions_for_display, Chef::Config)
  events.run_list_expanded(run_list_expansion_ish)

  # we must do this after `node.consume_external_attrs`
  node.automatic_attrs[:policy_name] = node.policy_name
  node.automatic_attrs[:policy_group] = node.policy_group
  node.automatic_attrs[:chef_environment] = node.policy_group

  node
rescue Exception => e
  events.node_load_failed(node_name, e, Chef::Config)
  raise
end

#configObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


508
509
510
# File 'lib/chef/policy_builder/policyfile.rb', line 508

def config
  Chef::Config
end

#cookbook_lock_for(cookbook_name) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


299
300
301
# File 'lib/chef/policy_builder/policyfile.rb', line 299

def cookbook_lock_for(cookbook_name)
  cookbook_locks[cookbook_name]
end

#cookbook_locksObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

def cookbook_locks
  policy["cookbook_locks"]
end

#cookbooks_to_syncObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds a 'cookbook_hash' map of the form "COOKBOOK_NAME" => "IDENTIFIER"

This can be passed to a Chef::CookbookSynchronizer object to synchronize the cookbooks.

TODO: Currently this makes N API calls to the server to get the cookbook objects. With server support (bulk API or the like), this should be reduced to a single call.


459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'lib/chef/policy_builder/policyfile.rb', line 459

def cookbooks_to_sync
  @cookbook_to_sync ||= begin
    events.cookbook_resolution_start(run_list_with_versions_for_display)

    cookbook_versions_by_name = cookbook_locks.inject({}) do |cb_map, (name, lock_data)|
      cb_map[name] = manifest_for(name, lock_data)
      cb_map
    end
    events.cookbook_resolution_complete(cookbook_versions_by_name)

    cookbook_versions_by_name
  end
rescue Exception => e
  # TODO: wrap/munge exception to provide helpful error output
  events.cookbook_resolution_failed(run_list_with_versions_for_display, e)
  raise
end

#deployment_groupObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


369
370
371
# File 'lib/chef/policy_builder/policyfile.rb', line 369

def deployment_group
  Chef::Config[:deployment_group] || raise(ConfigurationError, "Setting `deployment_group` is not configured.")
end

#expand_run_listRunListExpansionIsh

Sets run_list on the node from the policy, sets roles and recipes attributes on the node accordingly.

Returns:


208
209
210
211
212
213
214
215
216
# File 'lib/chef/policy_builder/policyfile.rb', line 208

def expand_run_list
  CookbookCacheCleaner.instance.skip_removal = true if named_run_list_requested?

  node.run_list(run_list)
  node.automatic_attrs[:policy_revision] = revision_id
  node.automatic_attrs[:roles] = []
  node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes
  run_list_expansion_ish
end

#finish_load_node(node) ⇒ Object

PolicyBuilder API ##


135
136
137
138
139
140
# File 'lib/chef/policy_builder/policyfile.rb', line 135

def finish_load_node(node)
  @node = node
  select_policy_name_and_group
  validate_policyfile
  events.policyfile_loaded(policy)
end

#hoist_policyfile_attributes(policy_group) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Hoists attributes from role_X[policy_group] up to the equivalent role_X level


282
283
284
285
286
# File 'lib/chef/policy_builder/policyfile.rb', line 282

def hoist_policyfile_attributes(policy_group)
  Chef::Log.trace("Running attribute Hoist for group #{policy_group}")
  Chef::Mixin::DeepMerge.hash_only_merge!(node.role_default, node.role_default[policy_group]) if node.role_default.include?(policy_group)
  Chef::Mixin::DeepMerge.hash_only_merge!(node.role_override, node.role_override[policy_group]) if node.role_override.include?(policy_group)
end

#manifest_for(cookbook_name, lock_data) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Fetches the CookbookVersion object for the given name and identifier specified in the lock_data. TODO: This only implements Chef 11 compatibility mode, which means that cookbooks are fetched by the "dotted_decimal_identifier": a representation of a SHA1 in the traditional x.y.z version format.


483
484
485
486
487
488
489
# File 'lib/chef/policy_builder/policyfile.rb', line 483

def manifest_for(cookbook_name, lock_data)
  if Chef::Config[:policy_document_native_api]
    artifact_manifest_for(cookbook_name, lock_data)
  else
    compat_mode_manifest_for(cookbook_name, lock_data)
  end
end

#original_runlistObject

Override run_list is not supported.


114
115
116
# File 'lib/chef/policy_builder/policyfile.rb', line 114

def original_runlist
  nil
end

#override_runlistObject

Override run_list is not supported.


119
120
121
# File 'lib/chef/policy_builder/policyfile.rb', line 119

def override_runlist
  nil
end

#parse_recipe_spec(recipe_spec) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


289
290
291
292
293
294
295
296
# File 'lib/chef/policy_builder/policyfile.rb', line 289

def parse_recipe_spec(recipe_spec)
  rmatch = recipe_spec.match(/recipe\[([^:]+)::([^:]+)\]/)
  if rmatch.nil?
    raise PolicyfileError, "invalid recipe specification #{recipe_spec} in Policyfile from #{policyfile_location}"
  else
    [rmatch[1], rmatch[2]]
  end
end

#policyObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


315
316
317
318
319
# File 'lib/chef/policy_builder/policyfile.rb', line 315

def policy
  @policy ||= api_service.get(policyfile_location)
rescue Net::HTTPClientException => e
  raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}"
end

#policy_groupObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


381
382
383
# File 'lib/chef/policy_builder/policyfile.rb', line 381

def policy_group
  Chef::Config[:policy_group]
end

#policy_group_from_configObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


430
431
432
# File 'lib/chef/policy_builder/policyfile.rb', line 430

def policy_group_from_config
  Chef::Config[:policy_group]
end

#policy_group_from_json_attribsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


420
421
422
# File 'lib/chef/policy_builder/policyfile.rb', line 420

def policy_group_from_json_attribs
  json_attribs["policy_group"]
end

#policy_group_from_nodeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


440
441
442
# File 'lib/chef/policy_builder/policyfile.rb', line 440

def policy_group_from_node
  node.policy_group
end

#policy_nameObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


386
387
388
# File 'lib/chef/policy_builder/policyfile.rb', line 386

def policy_name
  Chef::Config[:policy_name]
end

#policy_name_from_configObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


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

def policy_name_from_config
  Chef::Config[:policy_name]
end

#policy_name_from_json_attribsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


425
426
427
# File 'lib/chef/policy_builder/policyfile.rb', line 425

def policy_name_from_json_attribs
  json_attribs["policy_name"]
end

#policy_name_from_nodeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


445
446
447
# File 'lib/chef/policy_builder/policyfile.rb', line 445

def policy_name_from_node
  node.policy_name
end

#policyfile_locationObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


322
323
324
325
326
327
328
329
# File 'lib/chef/policy_builder/policyfile.rb', line 322

def policyfile_location
  if Chef::Config[:policy_document_native_api]
    validate_policy_config!
    "policy_groups/#{policy_group}/policies/#{policy_name}"
  else
    "data/policyfiles/#{deployment_group}"
  end
end

#revision_idObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


497
498
499
# File 'lib/chef/policy_builder/policyfile.rb', line 497

def revision_id
  policy["revision_id"]
end

#run_listObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


304
305
306
307
308
309
310
311
312
# File 'lib/chef/policy_builder/policyfile.rb', line 304

def run_list
  if named_run_list_requested?
    named_run_list || raise(ConfigurationError,
      "Policy '#{retrieved_policy_name}' revision '#{revision_id}' does not have named_run_list '#{named_run_list_name}'" +
      "(available named_run_lists: [#{available_named_run_lists.join(", ")}])")
  else
    policy["run_list"]
  end
end

#run_list_expansionObject

Policyfile gives you the run_list already expanded, but users of this class may expect to get a run_list expansion compatible object by calling this method.

=== Returns RunListExpansionIsh:: A RunListExpansion duck type


129
130
131
# File 'lib/chef/policy_builder/policyfile.rb', line 129

def run_list_expansion
  run_list_expansion_ish
end

#run_list_expansion_ishObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sets up a RunListExpansionIsh object so that it can be used in place of a RunListExpansion object, to satisfy the API contract of

expand_run_list


262
263
264
265
266
267
268
# File 'lib/chef/policy_builder/policyfile.rb', line 262

def run_list_expansion_ish
  recipes = run_list.map do |recipe_spec|
    cookbook, recipe = parse_recipe_spec(recipe_spec)
    "#{cookbook}::#{recipe}"
  end
  RunListExpansionIsh.new(recipes, [])
end

#run_list_with_versions_for_displayObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generates an array of strings with recipe names including version and identifier info.


248
249
250
251
252
253
254
255
# File 'lib/chef/policy_builder/policyfile.rb', line 248

def run_list_with_versions_for_display
  run_list.map do |recipe_spec|
    cookbook, recipe = parse_recipe_spec(recipe_spec)
    lock_data = cookbook_lock_for(cookbook)
    display = "#{cookbook}::#{recipe}@#{lock_data["version"]} (#{lock_data["identifier"][0...7]})"
    display
  end
end

#select_policy_name_and_groupObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Selects the policy_name and policy_group from the following sources in priority order:

  1. JSON attribs (i.e., -j JSON_FILE)
  2. Chef::Config
  3. The node object

The selected values are then copied to Chef::Config and the node.


400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/chef/policy_builder/policyfile.rb', line 400

def select_policy_name_and_group
  policy_name_to_set =
    policy_name_from_json_attribs ||
    policy_name_from_config ||
    policy_name_from_node

  policy_group_to_set =
    policy_group_from_json_attribs ||
    policy_group_from_config ||
    policy_group_from_node

  node.policy_name = policy_name_to_set
  node.policy_group = policy_group_to_set
  node.chef_environment = policy_group_to_set

  Chef::Config[:policy_name] = policy_name_to_set
  Chef::Config[:policy_group] = policy_group_to_set
end

#setup_run_context(specific_recipes = nil, run_context = nil) ⇒ Chef::RunContext

Synchronizes cookbooks and initializes the run context object for the run.

Returns:


179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/chef/policy_builder/policyfile.rb', line 179

def setup_run_context(specific_recipes = nil, run_context = nil)
  run_context ||= Chef::RunContext.new
  run_context.node = node
  run_context.events = events

  Chef::Cookbook::FileVendor.fetch_from_remote(api_service)
  sync_cookbooks
  cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync)
  cookbook_collection.validate!
  cookbook_collection.install_gems(events)

  run_context.cookbook_collection = cookbook_collection

  setup_chef_class(run_context)

  events.cookbook_compilation_start(run_context)

  run_context.load(run_list_expansion_ish)

  events.cookbook_compilation_complete(run_context)

  setup_chef_class(run_context)
  run_context
end

#sync_cookbooksHash{String => Chef::CookbookManifest}

Synchronizes cookbooks. In a normal chef-client run, this is handled by

setup_run_context, but may be called directly in some circumstances.

Returns:


223
224
225
226
227
228
229
230
231
232
# File 'lib/chef/policy_builder/policyfile.rb', line 223

def sync_cookbooks
  Chef::Log.trace("Synchronizing cookbooks")
  synchronizer = Chef::CookbookSynchronizer.new(cookbooks_to_sync, events)
  synchronizer.sync_cookbooks

  # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks
  Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks")

  cookbooks_to_sync
end

#temporary_policy?false

Whether or not this is a temporary policy. Since PolicyBuilder doesn't support override_runlist, this is always false.

Returns:

  • (false)

238
239
240
# File 'lib/chef/policy_builder/policyfile.rb', line 238

def temporary_policy?
  false
end

#validate_policy_config!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raises:


374
375
376
377
378
# File 'lib/chef/policy_builder/policyfile.rb', line 374

def validate_policy_config!
  raise ConfigurationError, "Setting `policy_group` is not configured." unless policy_group

  raise ConfigurationError, "Setting `policy_name` is not configured." unless policy_name
end

#validate_policyfileObject

Do some minimal validation of the policyfile we fetched from the server. Compatibility mode relies on using data bags to store policy files; therefore no real validation will be performed server-side and we need to make additional checks to ensure the data will be formatted correctly.


336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/chef/policy_builder/policyfile.rb', line 336

def validate_policyfile
  errors = []
  unless run_list
    errors << "Policyfile is missing run_list element"
  end
  unless policy.key?("cookbook_locks")
    errors << "Policyfile is missing cookbook_locks element"
  end
  if run_list.is_a?(Array)
    run_list_errors = run_list.select do |maybe_recipe_spec|
      validate_recipe_spec(maybe_recipe_spec)
    end
    errors += run_list_errors
  else
    errors << "Policyfile run_list is malformed, must be an array of `recipe[cb_name::recipe_name]` items: #{policy["run_list"]}"
  end

  unless errors.empty?
    raise PolicyfileError, "Policyfile fetched from #{policyfile_location} was invalid:\n#{errors.join("\n")}"
  end
end

#validate_recipe_spec(recipe_spec) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.


359
360
361
362
363
364
# File 'lib/chef/policy_builder/policyfile.rb', line 359

def validate_recipe_spec(recipe_spec)
  parse_recipe_spec(recipe_spec)
  nil
rescue PolicyfileError => e
  e.message
end