class Chef::PolicyBuilder::Policyfile
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
injson_attribs
-
would be ignored anyway, so it raises an error.
- chef-solo
-
not currently supported. Need more design thought around
how this should work.
Constants
- RunListExpansionIsh
Attributes
Public Class Methods
# 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 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
@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
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
@api private
# File lib/chef/policy_builder/policyfile.rb, line 507 def config Chef::Config end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 298 def cookbook_lock_for(cookbook_name) cookbook_locks[cookbook_name] end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 491 def cookbook_locks policy["cookbook_locks"] end
@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
@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
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
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
@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
@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
Override run_list
is not supported.
# File lib/chef/policy_builder/policyfile.rb, line 114 def original_runlist nil end
Override run_list
is not supported.
# File lib/chef/policy_builder/policyfile.rb, line 119 def override_runlist nil end
@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
@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
@api private
# File lib/chef/policy_builder/policyfile.rb, line 380 def policy_group Chef::Config[:policy_group] end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 429 def policy_group_from_config Chef::Config[:policy_group] end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 419 def policy_group_from_json_attribs json_attribs["policy_group"] end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 439 def policy_group_from_node node.policy_group end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 385 def policy_name Chef::Config[:policy_name] end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 434 def policy_name_from_config Chef::Config[:policy_name] end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 424 def policy_name_from_json_attribs json_attribs["policy_name"] end
@api private
# File lib/chef/policy_builder/policyfile.rb, line 444 def policy_name_from_node node.policy_name end
@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
@api private
# File lib/chef/policy_builder/policyfile.rb, line 496 def revision_id policy["revision_id"] end
@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
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
@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
@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
@api private
Selects the `policy_name` and `policy_group` from the following sources in priority order:
-
JSON attribs (i.e., `-j JSON_FILE`)
-
`Chef::Config`
-
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
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
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
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
@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
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
@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
# 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
# File lib/chef/policy_builder/policyfile.rb, line 531 def available_named_run_lists (policy["named_run_lists"] || {}).keys end
# 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
# File lib/chef/policy_builder/policyfile.rb, line 565 def inflate_cbv_object(raw_manifest) Chef::CookbookVersion.from_cb_artifact_data(raw_manifest) end
# 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
# File lib/chef/policy_builder/policyfile.rb, line 539 def named_run_list_name Chef::Config[:named_run_list] end
# File lib/chef/policy_builder/policyfile.rb, line 535 def named_run_list_requested? !!Chef::Config[:named_run_list] end
# File lib/chef/policy_builder/policyfile.rb, line 523 def retrieved_policy_name policy["name"] end
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