class Chef::CookbookManifest

Handles the details of representing a cookbook in JSON form for uploading to a Chef Server.

Attributes

cookbook_version[R]

Public Class Methods

new(cookbook_version, policy_mode: false) click to toggle source

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

add_files_to_manifest(files) click to toggle source

@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
by_parent_directory() click to toggle source
# 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
checksums() click to toggle source
# File lib/chef/cookbook_manifest.rb, line 108
def checksums
  @manifest || generate_manifest
  @checksums
end
each_file(excluded_parts: []) { |file| ... } click to toggle source
# 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
files_for(part) click to toggle source
# 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
force_save_url() click to toggle source

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

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
manifest_records_by_path() click to toggle source
# File lib/chef/cookbook_manifest.rb, line 113
def manifest_records_by_path
  @manifest || generate_manifest
  @manifest_records_by_path
end
named_cookbook_url() click to toggle source
# File lib/chef/cookbook_manifest.rb, line 145
def named_cookbook_url
  "#{cookbook_url_path}/#{name}"
end
policy_mode?() click to toggle source
# File lib/chef/cookbook_manifest.rb, line 118
def policy_mode?
  @policy_mode
end
reset!() click to toggle source

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

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
to_h() click to toggle source
# File lib/chef/cookbook_manifest.rb, line 122
def to_h
  CookbookManifestVersions.to_h(self)
end
Also aliased as: to_hash
to_hash()
Alias for: to_h
to_json(*a) click to toggle source
# 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_from(new_manifest) click to toggle source

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

checksum_cookbook_file(filepath) click to toggle source
# File lib/chef/cookbook_manifest.rb, line 308
def checksum_cookbook_file(filepath)
  CookbookVersion.checksum_cookbook_file(filepath)
end
cookbook_url_path() click to toggle source
# File lib/chef/cookbook_manifest.rb, line 218
def cookbook_url_path
  policy_mode? ? "cookbook_artifacts" : "cookbooks"
end
extract_checksums_from_manifest(manifest) click to toggle source
# 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
extract_manifest_records_by_path(manifest) click to toggle source
# 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
generate_manifest() click to toggle source

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