class Chef::Cookbook::CookbookVersionLoader

Constants

UPLOADED_COOKBOOK_VERSION_FILE

Attributes

cookbook_path[R]
cookbook_paths[R]
cookbook_settings[R]
frozen[R]
inferred_cookbook_name[R]

The cookbook's name as inferred from its directory.

metadata_error[R]
uploaded_cookbook_version_file[R]

Public Class Methods

new(path, chefignore = nil) click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 26
def initialize(path, chefignore = nil)
  @cookbook_path = File.expand_path( path ) # cookbook_path from which this was loaded
  # We keep a list of all cookbook paths that have been merged in
  @cookbook_paths = [ cookbook_path ]

  @inferred_cookbook_name = File.basename( path )
  @chefignore = chefignore
  @metadata = nil
  @relative_path = /#{Regexp.escape(@cookbook_path)}\/(.+)$/
  @metadata_loaded = false
  @cookbook_settings = {
    :all_files => {},
  }

  @metadata_filenames = []
  @metadata_error = nil
end

Public Instance Methods

apply_json_cookbook_version_metadata(file) click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 237
def apply_json_cookbook_version_metadata(file)
  data = Chef::JSONCompat.parse(IO.read(file))
  @metadata.from_hash(data["metadata"])
  # the JSON cookbok metadata file is only used by chef-zero.
  # The Chef Server API currently does not enforce that the metadata
  # have a `name` field, but that will cause an error when attempting
  # to load the cookbook. To keep compatibility, we fake it by setting
  # the metadata name from the cookbook version object's name.
  #
  # This behavior can be removed if/when Chef Server enforces that the
  # metadata contains a name key.
  @metadata.name(data["cookbook_name"]) unless data["metadata"].key?("name")
rescue Chef::Exceptions::JSON::ParseError
  Chef::Log.error("Couldn't parse cookbook metadata JSON for #{@inferred_cookbook_name} in " + file)
  raise
end
apply_json_metadata(file) click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 230
def apply_json_metadata(file)
  @metadata.from_json(IO.read(file))
rescue Chef::Exceptions::JSON::ParseError
  Chef::Log.error("Couldn't parse cookbook metadata JSON for #{@inferred_cookbook_name} in " + file)
  raise
end
apply_ruby_metadata(file) click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 223
def apply_ruby_metadata(file)
  @metadata.from_file(file)
rescue Chef::Exceptions::JSON::ParseError
  Chef::Log.error("Error evaluating metadata.rb for #{@inferred_cookbook_name} in " + file)
  raise
end
chefignore() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 177
def chefignore
  @chefignore ||= Chefignore.new(File.basename(cookbook_path))
end
cookbook_name() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 106
def cookbook_name
  # The `name` attribute is now required in metadata, so
  # inferred_cookbook_name generally should not be used. Per CHEF-2923,
  # we have to not raise errors in cookbook metadata immediately, so that
  # users can still `knife cookbook upload some-cookbook` when an
  # unrelated cookbook has an error in its metadata.  This situation
  # could prevent us from reading the `name` attribute from the metadata
  # entirely, but the name is used as a hash key in CookbookLoader, so we
  # fall back to the inferred name here.
  (metadata.name || @inferred_cookbook_name).to_sym
end
cookbook_version() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 95
def cookbook_version
  return nil if empty?

  Chef::CookbookVersion.new(cookbook_name, *cookbook_paths).tap do |c|
    c.all_files            = cookbook_settings[:all_files].values
    c.metadata             = metadata

    c.freeze_version if @frozen
  end
end
empty?() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 161
def empty?
  cookbook_settings.values.all? { |files_hash| files_hash.empty? } && metadata_filenames.size == 0
end
load() click to toggle source

Load the cookbook. Does not raise an error if given a non-cookbook directory as the cookbook_path. This behavior is provided for compatibility, it is recommended to use load! instead.

# File lib/chef/cookbook/cookbook_version_loader.rb, line 58
def load
  metadata # force lazy evaluation to occur

  # re-raise any exception that occurred when reading the metadata
  raise_metadata_error!

  load_all_files

  remove_ignored_files

  if empty?
    Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
  end
  @cookbook_settings
end
Also aliased as: load_cookbooks
load!() click to toggle source

Load the cookbook. Raises an error if the #cookbook_path given to the constructor doesn't point to a valid cookbook.

# File lib/chef/cookbook/cookbook_version_loader.rb, line 46
def load!
  file_paths_map = load

  if empty?
    raise Exceptions::CookbookNotFoundInRepo, "The directory #{cookbook_path} does not contain a cookbook"
  end
  file_paths_map
end
load_all_files() click to toggle source

Enumerate all the files in a cookbook and assign the resulting list to `cookbook_settings`. In order to behave in a compatible way with previous implementations, directories at the cookbook's root that begin with a dot are ignored. dotfiles are generally not ignored, however if the file is named “.uploaded-cookbook-version.json” it is assumed to be managed by chef-zero and not part of the cookbook.

# File lib/chef/cookbook/cookbook_version_loader.rb, line 187
def load_all_files
  return unless File.exist?(cookbook_path)

  # If cookbook_path is a symlink, Find on Windows Ruby 2.3 will not traverse it.
  # Dir.entries will do so on all platforms, so we iterate the top level using
  # Dir.entries. Since we have different behavior at the top anyway (hidden
  # directories at the top level are not included for backcompat), this
  # actually keeps things a bit cleaner.
  Dir.entries(cookbook_path).each do |top_filename|
    # Skip top-level directories starting with "."
    top_path = File.join(cookbook_path, top_filename)
    next if File.directory?(top_path) && top_filename.start_with?(".")

    # Use Find.find because it:
    # (a) returns any children, recursively
    # (b) includes top_path as well
    # (c) skips symlinks, which is backcompat (no judgement on whether it was *right*)
    Find.find(top_path) do |path|
      # Only add files, not directories
      next unless File.file?(path)
      # Don't add .uploaded-cookbook-version.json
      next if File.basename(path) == UPLOADED_COOKBOOK_VERSION_FILE

      relative_path = Chef::Util::PathHelper.relative_path_from(cookbook_path, path)
      path = Pathname.new(path).cleanpath.to_s
      cookbook_settings[:all_files][relative_path] = path
    end
  end
end
load_cookbooks()
Alias for: load
merge!(other_cookbook_loader) click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 165
def merge!(other_cookbook_loader)
  other_cookbook_settings = other_cookbook_loader.cookbook_settings
  cookbook_settings.each do |file_type, file_list|
    file_list.merge!(other_cookbook_settings[file_type])
  end
  metadata_filenames.concat(other_cookbook_loader.metadata_filenames)
  @cookbook_paths += other_cookbook_loader.cookbook_paths
  @frozen = true if other_cookbook_loader.frozen
  @metadata = nil # reset metadata so it gets reloaded and all metadata files applied.
  self
end
metadata() click to toggle source

Generates the Cookbook::Metadata object

# File lib/chef/cookbook/cookbook_version_loader.rb, line 119
def metadata
  return @metadata unless @metadata.nil?

  @metadata = Chef::Cookbook::Metadata.new

  metadata_filenames.each do |metadata_file|
    case metadata_file
    when /\.rb$/
      apply_ruby_metadata(metadata_file)
    when @uploaded_cookbook_version_file
      apply_json_cookbook_version_metadata(metadata_file)
    when /\.json$/
      apply_json_metadata(metadata_file)
    else
      raise "Invalid metadata file: #{metadata_file} for cookbook: #{cookbook_version}"
    end
  end

  @metadata

  # Rescue errors so that users can upload cookbooks via `knife cookbook
  # upload` even if some cookbooks in their chef-repo have errors in
  # their metadata. We only rescue StandardError because you have to be
  # doing something *really* terrible to raise an exception that inherits
  # directly from Exception in your metadata.rb file.
rescue StandardError => e
  @metadata_error = e
  @metadata
end
metadata_filenames() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 76
def metadata_filenames
  return @metadata_filenames unless @metadata_filenames.empty?
  if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE))
    @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)
  end

  if File.exists?(File.join(cookbook_path, "metadata.json"))
    @metadata_filenames << File.join(cookbook_path, "metadata.json")
  elsif File.exists?(File.join(cookbook_path, "metadata.rb"))
    @metadata_filenames << File.join(cookbook_path, "metadata.rb")
  elsif @uploaded_cookbook_version_file
    @metadata_filenames << @uploaded_cookbook_version_file
  end

  # Set frozen based on .uploaded-cookbook-version.json
  set_frozen
  @metadata_filenames
end
raise_metadata_error!() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 149
def raise_metadata_error!
  raise @metadata_error unless @metadata_error.nil?
  # Metadata won't be valid if the cookbook is empty. If the cookbook is
  # actually empty, a metadata error here would be misleading, so don't
  # raise it (if called by #load!, a different error is raised).
  if !empty? && !metadata.valid?
    message = "Cookbook loaded at path(s) [#{@cookbook_paths.join(', ')}] has invalid metadata: #{metadata.errors.join('; ')}"
    raise Exceptions::MetadataNotValid, message
  end
  false
end
remove_ignored_files() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 217
def remove_ignored_files
  cookbook_settings[:all_files].reject! do |relative_path, full_path|
    chefignore.ignored?(relative_path)
  end
end
set_frozen() click to toggle source
# File lib/chef/cookbook/cookbook_version_loader.rb, line 254
def set_frozen
  if uploaded_cookbook_version_file
    begin
      data = Chef::JSONCompat.parse(IO.read(uploaded_cookbook_version_file))
      @frozen = data["frozen?"]
    rescue Chef::Exceptions::JSON::ParseError
      Chef::Log.error("Couldn't parse cookbook metadata JSON for #{@inferred_cookbook_name} in #{uploaded_cookbook_version_file}")
      raise
    end
  end
end