Class: Chef::PolicyBuilder::Policyfile

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

Overview

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

== WARNING This implementation is experimental. It may be changed in incompatible ways in minor or even patch releases, or even abandoned altogether. If using this with other tools, you may be forced to upgrade those tools in lockstep with chef-client because of incompatible behavior changes.

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


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/chef/policy_builder/policyfile.rb', line 63

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 chef-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 Chef Environments."
  end
end

Instance Attribute Details

#eventsObject (readonly)

Returns the value of attribute events


56
57
58
# File 'lib/chef/policy_builder/policyfile.rb', line 56

def events
  @events
end

#json_attribsObject (readonly)

Returns the value of attribute json_attribs


60
61
62
# File 'lib/chef/policy_builder/policyfile.rb', line 60

def json_attribs
  @json_attribs
end

#nodeObject (readonly)

Returns the value of attribute node


57
58
59
# File 'lib/chef/policy_builder/policyfile.rb', line 57

def node
  @node
end

#node_nameObject (readonly)

Returns the value of attribute node_name


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

def node_name
  @node_name
end

#ohai_dataObject (readonly)

Returns the value of attribute ohai_data


59
60
61
# File 'lib/chef/policy_builder/policyfile.rb', line 59

def ohai_data
  @ohai_data
end

#run_contextObject (readonly)

Returns the value of attribute run_context


61
62
63
# File 'lib/chef/policy_builder/policyfile.rb', line 61

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.


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

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.


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

def apply_policyfile_attributes
  node.attributes.role_default = policy["default_attributes"]
  node.attributes.role_override = policy["override_attributes"]
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.


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/chef/policy_builder/policyfile.rb', line 125

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)

  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.


461
462
463
# File 'lib/chef/policy_builder/policyfile.rb', line 461

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.


253
254
255
# File 'lib/chef/policy_builder/policyfile.rb', line 253

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.


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

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.


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

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.


323
324
325
# File 'lib/chef/policy_builder/policyfile.rb', line 323

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:


172
173
174
175
176
177
178
179
180
# File 'lib/chef/policy_builder/policyfile.rb', line 172

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


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

def finish_load_node(node)
  @node = node
  select_policy_name_and_group
  validate_policyfile
  events.policyfile_loaded(policy)
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 identifer 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.


436
437
438
439
440
441
442
# File 'lib/chef/policy_builder/policyfile.rb', line 436

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.


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

def original_runlist
  nil
end

#override_runlistObject

Override run_list is not supported.


97
98
99
# File 'lib/chef/policy_builder/policyfile.rb', line 97

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.


243
244
245
246
247
248
249
250
# File 'lib/chef/policy_builder/policyfile.rb', line 243

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.


269
270
271
272
273
# File 'lib/chef/policy_builder/policyfile.rb', line 269

def policy
  @policy ||= api_service.get(policyfile_location)
rescue Net::HTTPServerException => 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.


335
336
337
# File 'lib/chef/policy_builder/policyfile.rb', line 335

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.


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

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.


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

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.


393
394
395
# File 'lib/chef/policy_builder/policyfile.rb', line 393

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.


340
341
342
# File 'lib/chef/policy_builder/policyfile.rb', line 340

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.


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

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.


378
379
380
# File 'lib/chef/policy_builder/policyfile.rb', line 378

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.


398
399
400
# File 'lib/chef/policy_builder/policyfile.rb', line 398

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.


276
277
278
279
280
281
282
283
# File 'lib/chef/policy_builder/policyfile.rb', line 276

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.


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

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.


258
259
260
261
262
263
264
265
266
# File 'lib/chef/policy_builder/policyfile.rb', line 258

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


107
108
109
# File 'lib/chef/policy_builder/policyfile.rb', line 107

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


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

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.


212
213
214
215
216
217
218
219
# File 'lib/chef/policy_builder/policyfile.rb', line 212

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.


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 354

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

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

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

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

Returns:


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/chef/policy_builder/policyfile.rb', line 151

def setup_run_context(specific_recipes = nil)
  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 = Chef::RunContext.new(node, cookbook_collection, events)

  setup_chef_class(run_context)

  run_context.load(run_list_expansion_ish)

  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:


187
188
189
190
191
192
193
194
195
196
# File 'lib/chef/policy_builder/policyfile.rb', line 187

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

202
203
204
# File 'lib/chef/policy_builder/policyfile.rb', line 202

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:


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

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


290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/chef/policy_builder/policyfile.rb', line 290

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


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

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