Policyfile is an experimental policy builder implementation that gets run list and cookbook version information from a single document.
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.
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. |
would be ignored anyway, so it raises an error. |
chef-solo |
not currently supported. Need more design thought around |
how this should work.
# 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 Chef::Log.warn("Using experimental Policyfile feature") if Chef::Config[:solo] raise UnsupportedFeature, "Policyfile does not support chef-solo at this time." end if override_runlist raise UnsupportedFeature, "Policyfile does not support override run lists at this time" end if json_attribs && json_attribs.key?("run_list") raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data at this time" end if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty? raise UnsupportedFeature, "Policyfile does not work with Chef Environments" end end
# File lib/chef/policy_builder/policyfile.rb, line 214 def apply_policyfile_attributes node.attributes.role_default = policy["default_attributes"] node.attributes.role_override = policy["override_attributes"] end
Applies environment, external JSON attributes, and override run list to the node, Then expands the run_list.
node<Chef::Node> |
The modified node object. node is modified in place. |
# File lib/chef/policy_builder/policyfile.rb, line 133 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
# File lib/chef/policy_builder/policyfile.rb, line 337 def config Chef::Config end
# File lib/chef/policy_builder/policyfile.rb, line 228 def cookbook_lock_for(cookbook_name) cookbook_locks[cookbook_name] end
# File lib/chef/policy_builder/policyfile.rb, line 329 def cookbook_locks policy["cookbook_locks"] end
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 296 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
# File lib/chef/policy_builder/policyfile.rb, line 282 def deployment_group Chef::Config[:deployment_group] or raise ConfigurationError, "Setting `deployment_group` is not configured." end
# File lib/chef/policy_builder/policyfile.rb, line 170 def expand_run_list node.run_list(run_list) node.automatic_attrs[:roles] = [] node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes run_list_expansion_ish end
# File lib/chef/policy_builder/policyfile.rb, line 333 def http_api @api_service ||= Chef::REST.new(config[:chef_server_url]) end
Loads the node state from the server.
# File lib/chef/policy_builder/policyfile.rb, line 116 def load_node events.node_load_start(node_name, Chef::Config) Chef::Log.debug("Building node object for #{node_name}") @node = Chef::Node.find_or_create(node_name) validate_policyfile node rescue Exception => e events.node_load_failed(node_name, e, Chef::Config) raise end
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 319 def manifest_for(cookbook_name, lock_data) xyz_version = lock_data["dotted_decimal_identifier"] http_api.get("cookbooks/#{cookbook_name}/#{xyz_version}") rescue Exception => e message = "Error loading cookbook #{cookbook_name} at version #{xyz_version}: #{e.class} - #{e.message}" err = Chef::Exceptions::CookbookNotFound.new(message) err.set_backtrace(e.backtrace) raise err end
Override run_list is not supported.
# File lib/chef/policy_builder/policyfile.rb, line 94 def original_runlist nil end
Override run_list is not supported.
# File lib/chef/policy_builder/policyfile.rb, line 99 def override_runlist nil end
# File lib/chef/policy_builder/policyfile.rb, line 219 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
# File lib/chef/policy_builder/policyfile.rb, line 236 def policy @policy ||= http_api.get(policyfile_location) rescue Net::HTTPServerException => e raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}" end
# File lib/chef/policy_builder/policyfile.rb, line 242 def policyfile_location "data/policyfiles/#{deployment_group}" end
# File lib/chef/policy_builder/policyfile.rb, line 232 def run_list policy["run_list"] 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.
A RunListExpansion duck type |
# File lib/chef/policy_builder/policyfile.rb, line 109 def run_list_expansion run_list_expansion_ish end
# File lib/chef/policy_builder/policyfile.rb, line 206 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
Internal Public API ##
# File lib/chef/policy_builder/policyfile.rb, line 197 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
# File lib/chef/policy_builder/policyfile.rb, line 156 def setup_run_context(specific_recipes=nil) # TODO: This file vendor stuff is duplicated and initializing it with a # block traps a reference to this object in a global context which will # prevent it from getting GC'd. Simplify it. Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, api_service) } sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync) run_context = Chef::RunContext.new(node, cookbook_collection, events) run_context.load(run_list_expansion_ish) run_context end
# File lib/chef/policy_builder/policyfile.rb, line 178 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
Whether or not this is a temporary policy. Since PolicyBuilder doesn’t support override_runlist, this is always false.
# File lib/chef/policy_builder/policyfile.rb, line 191 def temporary_policy? false 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 251 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
Generated with the Darkfish Rdoc Generator 2.