class Chef::CookbookManifest
Handles the details of representing a cookbook in JSON form for uploading to a Chef
Server.
Attributes
Public Class Methods
Create a new CookbookManifest
object for the given `cookbook_version`. You can subsequently call to_h
to get a Hash representation of the cookbook_version
in the “manifest” format, or to_json
to get a JSON representation of the cookbook_version.
The interface for this behavior is expected to change as we implement new manifest formats. The entire class should be considered a private API for now.
@api private @param policy_mode [Boolean] whether to convert cookbooks to Hash/JSON in
the format used by the `cookbook_artifacts` endpoint (for policyfiles). Setting this option also changes the behavior of #save_url and #force_save_url such that CookbookVersions will be uploaded to the new `cookbook_artifacts` API.
# File lib/chef/cookbook_manifest.rb, line 57 def initialize(cookbook_version, policy_mode: false) @cookbook_version = cookbook_version @policy_mode = !!policy_mode reset! end
Public Instance Methods
@api private takes a list of hashes
# File lib/chef/cookbook_manifest.rb, line 167 def add_files_to_manifest(files) manifest[:all_files].concat(Array(files)) @checksums = extract_checksums_from_manifest(@manifest) @manifest_records_by_path = extract_manifest_records_by_path(@manifest) end
# File lib/chef/cookbook_manifest.rb, line 193 def by_parent_directory @by_parent_directory ||= manifest[:all_files].inject({}) do |memo, file| parts = file[:name].split("/") parent = if parts.length == 1 "root_files" else parts[0] end memo[parent] ||= [] memo[parent] << file memo end end
# File lib/chef/cookbook_manifest.rb, line 108 def checksums @manifest || generate_manifest @checksums end
# File lib/chef/cookbook_manifest.rb, line 182 def each_file(excluded_parts: [], &block) excluded_parts = Array(excluded_parts).map(&:to_s) manifest[:all_files].each do |file| seg = file[:name].split("/")[0] next if excluded_parts.include?(seg) yield file if block_given? end end
# File lib/chef/cookbook_manifest.rb, line 173 def files_for(part) return root_files if part.to_s == "root_files" manifest[:all_files].select do |file| seg = file[:name].split("/")[0] part.to_s == seg end end
Adds the `force=true` parameter to the upload URL. This allows the user to overwrite a frozen cookbook (a PUT against the normal save_url
raises a 409 Conflict in this case).
# File lib/chef/cookbook_manifest.rb, line 152 def force_save_url "#{save_url}?force=true" end
Returns a 'manifest' data structure that can be uploaded to a Chef
Server.
The format is as follows:
{ :cookbook_name => name, # String :metadata => metadata, # Chef::Cookbook::Metadata :version => version, # Chef::Version :name => full_name, # String of "#{name}-#{version}" :recipes => Array<FileSpec>, :definitions => Array<FileSpec>, :libraries => Array<FileSpec>, :attributes => Array<FileSpec>, :files => Array<FileSpec>, :templates => Array<FileSpec>, :resources => Array<FileSpec>, :providers => Array<FileSpec>, :root_files => Array<FileSpec> }
Where a `FileSpec` is a Hash of the form:
{ :name => file_name, :path => path, :checksum => csum, :specificity => specificity }
# File lib/chef/cookbook_manifest.rb, line 103 def manifest @manifest || generate_manifest @manifest end
# File lib/chef/cookbook_manifest.rb, line 113 def manifest_records_by_path @manifest || generate_manifest @manifest_records_by_path end
# File lib/chef/cookbook_manifest.rb, line 145 def named_cookbook_url "#{cookbook_url_path}/#{name}" end
# File lib/chef/cookbook_manifest.rb, line 118 def policy_mode? @policy_mode end
Resets all lazily computed values.
# File lib/chef/cookbook_manifest.rb, line 65 def reset! @manifest = nil @checksums = nil @manifest_records_by_path = nil true end
# File lib/chef/cookbook_manifest.rb, line 209 def root_files manifest[:all_files].select do |file| segment, name = file[:name].split("/") name.nil? || segment == "root_files" end end
Return the URL to save (PUT) this object to the server via the REST api. If there is an existing document on the server and it is marked frozen, a PUT will result in a 409 Conflict.
# File lib/chef/cookbook_manifest.rb, line 137 def save_url if policy_mode? "#{named_cookbook_url}/#{identifier}" else "#{named_cookbook_url}/#{version}" end end
# File lib/chef/cookbook_manifest.rb, line 122 def to_h CookbookManifestVersions.to_h(self) end
# File lib/chef/cookbook_manifest.rb, line 128 def to_json(*a) result = to_h result["json_class"] = "Chef::CookbookVersion" Chef::JSONCompat.to_json(result, *a) end
Update this CookbookManifest
from the contents of another manifest, and make the corresponding changes to the cookbook_version
object. Required to provide backward compatibility with CookbookVersion#manifest=
method.
# File lib/chef/cookbook_manifest.rb, line 159 def update_from(new_manifest) @manifest = Chef::CookbookManifestVersions.from_hash(new_manifest) @checksums = extract_checksums_from_manifest(@manifest) @manifest_records_by_path = extract_manifest_records_by_path(@manifest) end
Private Instance Methods
# File lib/chef/cookbook_manifest.rb, line 308 def checksum_cookbook_file(filepath) CookbookVersion.checksum_cookbook_file(filepath) end
# File lib/chef/cookbook_manifest.rb, line 218 def cookbook_url_path policy_mode? ? "cookbook_artifacts" : "cookbooks" end
# File lib/chef/cookbook_manifest.rb, line 301 def extract_checksums_from_manifest(manifest) manifest[:all_files].inject({}) do |memo, manifest_record| memo[manifest_record[:checksum]] = nil memo end end
# File lib/chef/cookbook_manifest.rb, line 312 def extract_manifest_records_by_path(manifest) manifest[:all_files].inject({}) do |memo, manifest_record| memo[manifest_record[:path]] = manifest_record memo end end
See manifest
for a description of the manifest return value. See preferred_manifest_record for a description an individual manifest record.
# File lib/chef/cookbook_manifest.rb, line 224 def generate_manifest manifest = Mash.new({ all_files: [], }) @checksums = {} if !root_paths || root_paths.size == 0 Chef::Log.error("Cookbook #{name} does not have root_paths! Cannot generate manifest.") raise "Cookbook #{name} does not have root_paths! Cannot generate manifest." end @cookbook_version.all_files.each do |file| next if File.directory?(file) name, path, specificity = parse_file_from_root_paths(file) csum = checksum_cookbook_file(file) @checksums[csum] = file rs = Mash.new({ name: name, path: path, checksum: csum, specificity: specificity, # full_path is not a part of the normal manifest, but is very useful to keep around. # uploaders should strip this out. full_path: file, }) manifest[:all_files] << rs end manifest[:metadata] = metadata manifest[:version] = metadata.version if policy_mode? manifest[:name] = name.to_s manifest[:identifier] = identifier else manifest[:name] = full_name manifest[:cookbook_name] = name.to_s end @manifest_records_by_path = extract_manifest_records_by_path(manifest) @manifest = manifest end
# File lib/chef/cookbook_manifest.rb, line 270 def parse_file_from_root_paths(file) root_paths.each do |root_path| pathname = Chef::Util::PathHelper.relative_path_from(root_path, file) parts = pathname.each_filename.take(2) # Check if path is actually under root_path next if parts[0] == ".." # if we have a root_file, such as metadata.rb, the first part will be "." return [ "root_files/#{pathname}", pathname.to_s, "default" ] if parts.length == 1 segment = parts[0] name = File.join(segment, pathname.basename.to_s) if %w{templates files}.include?(segment) # Check if pathname looks like files/foo or templates/foo (unscoped) if pathname.each_filename.to_a.length == 2 # Use root_default in case the same path exists at root_default and default return [ name, pathname.to_s, "root_default" ] else return [ name, pathname.to_s, parts[1] ] end else return [ name, pathname.to_s, "default" ] end end Chef::Log.error("Cookbook file #{file} not under cookbook root paths #{root_paths.inspect}.") raise "Cookbook file #{file} not under cookbook root paths #{root_paths.inspect}." end