class Chef::PolicyBuilder::Policyfile

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

Unsupported Options:

policyfile, or replaced with a similar feature that has different semantics.

how this should work.

Constants

RunListExpansionIsh

Attributes

events[R]
json_attribs[R]
node[R]
node_name[R]
ohai_data[R]
run_context[R]

Public Class Methods

new(node_name, ohai_data, json_attribs, override_runlist, events) click to toggle source
# 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 #{Chef::Dist::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

Public Instance Methods

api_service() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 501
def api_service
  @api_service ||= Chef::ServerAPI.new(config[:chef_server_url],
                                       { version_class: Chef::CookbookManifestVersions })
end
apply_policyfile_attributes() click to toggle source

@api private

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

# File lib/chef/policy_builder/policyfile.rb, line 272
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_node() click to toggle source

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

Returns

node<Chef::Node>

The modified node object. node is modified in place.

# 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
config() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 507
def config
  Chef::Config
end
cookbook_lock_for(cookbook_name) click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 298
def cookbook_lock_for(cookbook_name)
  cookbook_locks[cookbook_name]
end
cookbook_locks() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 491
def cookbook_locks
  policy["cookbook_locks"]
end
cookbooks_to_sync() click to toggle source

@api private 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.

# File lib/chef/policy_builder/policyfile.rb, line 458
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_group() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 368
def deployment_group
  Chef::Config[:deployment_group] || raise(ConfigurationError, "Setting `deployment_group` is not configured.")
end
expand_run_list() click to toggle source

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

@return [RunListExpansionIsh] A RunListExpansion duck-type.

# File lib/chef/policy_builder/policyfile.rb, line 207
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) click to toggle source

PolicyBuilder API ##

# 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) click to toggle source

@api private

Hoists attributes from role_X up to the equivalent role_X level

# File lib/chef/policy_builder/policyfile.rb, line 281
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) click to toggle source

@api private 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.

# File lib/chef/policy_builder/policyfile.rb, line 482
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_runlist() click to toggle source

Override run_list is not supported.

# File lib/chef/policy_builder/policyfile.rb, line 114
def original_runlist
  nil
end
override_runlist() click to toggle source

Override run_list is not supported.

# File lib/chef/policy_builder/policyfile.rb, line 119
def override_runlist
  nil
end
parse_recipe_spec(recipe_spec) click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 288
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
policy() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 314
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_group() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 380
def policy_group
  Chef::Config[:policy_group]
end
policy_group_from_config() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 429
def policy_group_from_config
  Chef::Config[:policy_group]
end
policy_group_from_json_attribs() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 419
def policy_group_from_json_attribs
  json_attribs["policy_group"]
end
policy_group_from_node() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 439
def policy_group_from_node
  node.policy_group
end
policy_name() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 385
def policy_name
  Chef::Config[:policy_name]
end
policy_name_from_config() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 434
def policy_name_from_config
  Chef::Config[:policy_name]
end
policy_name_from_json_attribs() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 424
def policy_name_from_json_attribs
  json_attribs["policy_name"]
end
policy_name_from_node() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 444
def policy_name_from_node
  node.policy_name
end
policyfile_location() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 321
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_id() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 496
def revision_id
  policy["revision_id"]
end
run_list() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 303
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_expansion() click to toggle source

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

# File lib/chef/policy_builder/policyfile.rb, line 129
def run_list_expansion
  run_list_expansion_ish
end
run_list_expansion_ish() click to toggle source

@api private

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

# File lib/chef/policy_builder/policyfile.rb, line 261
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() click to toggle source

@api private

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

# File lib/chef/policy_builder/policyfile.rb, line 247
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_group() click to toggle source

@api private

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.

# File lib/chef/policy_builder/policyfile.rb, line 399
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) click to toggle source

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

@return [Chef::RunContext]

# File lib/chef/policy_builder/policyfile.rb, line 179
def setup_run_context(specific_recipes = nil, run_context = 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
  run_context.node = node
  run_context.cookbook_collection = cookbook_collection
  run_context.events = events

  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_cookbooks() click to toggle source

Synchronizes cookbooks. In a normal chef-client run, this is handled by setup_run_context, but may be called directly in some circumstances.

@return [Hash{String => Chef::CookbookManifest}] A map of

CookbookManifest objects by cookbook name.
# File lib/chef/policy_builder/policyfile.rb, line 222
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?() click to toggle source

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

@return [false]

# File lib/chef/policy_builder/policyfile.rb, line 237
def temporary_policy?
  false
end
validate_policy_config!() click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 373
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_policyfile() click to toggle source

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.

# File lib/chef/policy_builder/policyfile.rb, line 335
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) click to toggle source

@api private

# File lib/chef/policy_builder/policyfile.rb, line 358
def validate_recipe_spec(recipe_spec)
  parse_recipe_spec(recipe_spec)
  nil
rescue PolicyfileError => e
  e.message
end

Private Instance Methods

artifact_manifest_for(cookbook_name, lock_data) click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 554
def artifact_manifest_for(cookbook_name, lock_data)
  identifier = lock_data["identifier"]
  rel_url = "cookbook_artifacts/#{cookbook_name}/#{identifier}"
  inflate_cbv_object(api_service.get(rel_url))
rescue Exception => e
  message = "Error loading cookbook #{cookbook_name} with identifier #{identifier} from #{rel_url}: #{e.class} - #{e.message}"
  err = Chef::Exceptions::CookbookNotFound.new(message)
  err.set_backtrace(e.backtrace)
  raise err
end
available_named_run_lists() click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 531
def available_named_run_lists
  (policy["named_run_lists"] || {}).keys
end
compat_mode_manifest_for(cookbook_name, lock_data) click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 543
def compat_mode_manifest_for(cookbook_name, lock_data)
  xyz_version = lock_data["dotted_decimal_identifier"]
  rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}"
  inflate_cbv_object(api_service.get(rel_url))
rescue Exception => e
  message = "Error loading cookbook #{cookbook_name} at version #{xyz_version} from #{rel_url}: #{e.class} - #{e.message}"
  err = Chef::Exceptions::CookbookNotFound.new(message)
  err.set_backtrace(e.backtrace)
  raise err
end
inflate_cbv_object(raw_manifest) click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 565
def inflate_cbv_object(raw_manifest)
  Chef::CookbookVersion.from_cb_artifact_data(raw_manifest)
end
named_run_list() click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 527
def named_run_list
  policy["named_run_lists"] && policy["named_run_lists"][named_run_list_name]
end
named_run_list_name() click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 539
def named_run_list_name
  Chef::Config[:named_run_list]
end
named_run_list_requested?() click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 535
def named_run_list_requested?
  !!Chef::Config[:named_run_list]
end
retrieved_policy_name() click to toggle source
# File lib/chef/policy_builder/policyfile.rb, line 523
def retrieved_policy_name
  policy["name"]
end
setup_chef_class(run_context) click to toggle source

This method injects the run_context and into the Chef class.

NOTE: This is duplicated with the ExpandNodeObject implementation. If it gets any more complicated, it needs to be moved elsewhere.

@param run_context [Chef::RunContext] the run_context to inject

# File lib/chef/policy_builder/policyfile.rb, line 519
def setup_chef_class(run_context)
  Chef.set_run_context(run_context)
end