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.

Does not support legacy chef-solo or roles/environments.

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.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/chef/policy_builder/policyfile.rb', line 80

def initialize(node_name, ohai_data, json_attribs, override_runlist, events)
  @node_name = node_name
  @ohai_data = ohai_data
  @override_runlist = override_runlist
  @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 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.



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

def events
  @events
end

#json_attribsObject (readonly)

Returns the value of attribute json_attribs.



76
77
78
# File 'lib/chef/policy_builder/policyfile.rb', line 76

def json_attribs
  @json_attribs
end

#nodeObject (readonly)

Returns the value of attribute node.



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

def node
  @node
end

#node_nameObject (readonly)

Returns the value of attribute node_name.



74
75
76
# File 'lib/chef/policy_builder/policyfile.rb', line 74

def node_name
  @node_name
end

#ohai_dataObject (readonly)

Returns the value of attribute ohai_data.



75
76
77
# File 'lib/chef/policy_builder/policyfile.rb', line 75

def ohai_data
  @ohai_data
end

#override_runlistObject (readonly)

Returns the value of attribute override_runlist.



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

def override_runlist
  @override_runlist
end

#run_contextObject (readonly)

Returns the value of attribute run_context.



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

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.



516
517
518
519
# File 'lib/chef/policy_builder/policyfile.rb', line 516

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.



275
276
277
278
279
# File 'lib/chef/policy_builder/policyfile.rb', line 275

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.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/chef/policy_builder/policyfile.rb', line 122

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.

  unless Chef::Config[:policy_document_native_api]
    Chef.deprecated(:policyfile_compat_mode, "The chef-server 11 policyfile compat mode is deprecated, please set policy_document_native_api to true in your config")
  end

  node.reset_defaults_and_overrides

  node.consume_external_attrs(ohai_data, json_attribs)

  # Preserve the fall back to loading an unencrypted data bag item if the item we're trying to load isn't actually a vault item.
  set_databag_fallback

  setup_run_list_override

  expand_run_list
  apply_policyfile_attributes

  if persistent_run_list_set?
    Chef::Log.warn("The node.run_list setting is overriding the Policyfile run_list")
  end
  Chef::Log.info("Run List is [#{run_list}]")
  Chef::Log.info("Run List expands to [#{run_list_with_versions_for_display(run_list).join(", ")}]")

  events.node_load_completed(node, run_list_with_versions_for_display(run_list), 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.



522
523
524
# File 'lib/chef/policy_builder/policyfile.rb', line 522

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.



306
307
308
# File 'lib/chef/policy_builder/policyfile.rb', line 306

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.



506
507
508
# File 'lib/chef/policy_builder/policyfile.rb', line 506

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.



473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/chef/policy_builder/policyfile.rb', line 473

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

    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(run_list), 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.



383
384
385
# File 'lib/chef/policy_builder/policyfile.rb', line 383

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:



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

def expand_run_list
  validate_run_list!(run_list)

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



110
111
112
113
114
115
# File 'lib/chef/policy_builder/policyfile.rb', line 110

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



284
285
286
287
288
# File 'lib/chef/policy_builder/policyfile.rb', line 284

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.



497
498
499
500
501
502
503
# File 'lib/chef/policy_builder/policyfile.rb', line 497

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

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



291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/chef/policy_builder/policyfile.rb', line 291

def parse_recipe_spec(recipe_spec)
  rmatch = recipe_spec.to_s.match(/recipe\[([^:]+)::([^:]+)\]/)
  if rmatch.nil?
    rmatch = recipe_spec.to_s.match(/recipe\[([^:]+)\]/)
    if rmatch.nil?
      raise PolicyfileError, "invalid recipe specification #{recipe_spec} in Policyfile from #{policyfile_location}"
    else
      [rmatch[1], "default"]
    end
  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.



329
330
331
332
333
# File 'lib/chef/policy_builder/policyfile.rb', line 329

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.



395
396
397
# File 'lib/chef/policy_builder/policyfile.rb', line 395

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.



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

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.



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

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.



454
455
456
# File 'lib/chef/policy_builder/policyfile.rb', line 454

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.



400
401
402
# File 'lib/chef/policy_builder/policyfile.rb', line 400

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.



449
450
451
# File 'lib/chef/policy_builder/policyfile.rb', line 449

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.



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

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.



459
460
461
# File 'lib/chef/policy_builder/policyfile.rb', line 459

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.



336
337
338
339
340
341
342
343
# File 'lib/chef/policy_builder/policyfile.rb', line 336

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.



511
512
513
# File 'lib/chef/policy_builder/policyfile.rb', line 511

def revision_id
  policy["revision_id"]
end

#run_listArray<String>

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.

Returns:



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/chef/policy_builder/policyfile.rb', line 312

def run_list
  return override_runlist.map(&:to_s) if override_runlist

  if json_attribs["run_list"]
    json_attribs["run_list"]
  elsif persistent_run_list_set?
    node.run_list
  elsif 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



104
105
106
# File 'lib/chef/policy_builder/policyfile.rb', line 104

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



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

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_display(run_list) ⇒ 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.

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



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

def run_list_with_versions_for_display(run_list)
  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.



414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/chef/policy_builder/policyfile.rb', line 414

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

#set_databag_fallbackObject

Preserve the fall back to loading an unencrypted data bag item if the item we're trying to load isn't actually a vault item.



198
199
200
# File 'lib/chef/policy_builder/policyfile.rb', line 198

def set_databag_fallback
  node.default["chef-vault"]["databag_fallback"] = ChefUtils.kitchen?(node)
end

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

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

Returns:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/chef/policy_builder/policyfile.rb', line 167

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)
  if specific_recipes
    specific_recipes.each do |recipe_file|
      run_context.load_recipe_file(recipe_file)
    end
  end

  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:



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

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

Indicates whether the policy is temporary, which means an override_runlist was provided. Chef::Client uses this to decide whether to do the final node save at the end of the run or not.

Returns:

  • (Boolean)


529
530
531
# File 'lib/chef/policy_builder/policyfile.rb', line 529

def temporary_policy?
  node.override_runlist_set?
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:



388
389
390
391
392
# File 'lib/chef/policy_builder/policyfile.rb', line 388

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.



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/chef/policy_builder/policyfile.rb', line 350

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.



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

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

#validate_run_list!(run_list) ⇒ 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.

Validate run_list against policyfile cookbooks



238
239
240
241
242
243
244
# File 'lib/chef/policy_builder/policyfile.rb', line 238

def validate_run_list!(run_list)
  run_list.map do |recipe_spec|
    cookbook, recipe = parse_recipe_spec(recipe_spec)
    lock_data = cookbook_lock_for(cookbook)
    raise PolicyfileError, "invalid run_list item '#{recipe_spec}' not in cookbook set of PolicyFile #{policyfile_location}" unless lock_data
  end
end