class Chef::Cookbook::CookbookVersionLoader
This class is only used directly from the Chef::CookbookLoader
and from chef-fs, so it only affects legacy-mode chef-client runs and knife. It is not used by server or zolo/zero modes.
This seems to be mostly a glorified factory method for creating CookbookVersion
objects now, with creating Metadata
objects bolted onto the side? It used to be also responsible for the merging of multiple objects when creating shadowed/merged cookbook versions from multiple sources. It also handles Chefignore
files.
Constants
- UPLOADED_COOKBOOK_VERSION_FILE
Attributes
The cookbook's name as inferred from its directory.
Public Class Methods
# File lib/chef/cookbook/cookbook_version_loader.rb, line 49 def initialize(path, chefignore = nil) @cookbook_path = File.expand_path( path ) # cookbook_path from which this was loaded @inferred_cookbook_name = File.basename( path ) @chefignore = chefignore @metadata = nil @relative_path = %r{#{Regexp.escape(cookbook_path)}/(.+)$} @metadata_loaded = false @cookbook_settings = { all_files: {}, } @metadata_filenames = [] @metadata_error = nil end
Public Instance Methods
# File lib/chef/cookbook/cookbook_version_loader.rb, line 146 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
# File lib/chef/cookbook/cookbook_version_loader.rb, line 104 def cookbook_version return nil if empty? Chef::CookbookVersion.new(cookbook_name, cookbook_path).tap do |c| c.all_files = cookbook_settings[:all_files].values c.metadata = metadata c.freeze_version if frozen end end
# File lib/chef/cookbook/cookbook_version_loader.rb, line 84 def load Chef.deprecated(:internal_api, "Chef::Cookbook::CookbookVersionLoader's load method is deprecated. Please use load! instead.") 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
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 67 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? raise Exceptions::CookbookNotFoundInRepo, "The directory #{cookbook_path} does not contain a cookbook" end cookbook_settings end
Generates the Cookbook::Metadata
object
# File lib/chef/cookbook/cookbook_version_loader.rb, line 116 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
Private Instance Methods
# File lib/chef/cookbook/cookbook_version_loader.rb, line 257 def apply_json_cookbook_version_metadata(file) data = Chef::JSONCompat.parse(IO.read(file)) @metadata.from_hash(data["metadata"]) # the JSON cookbook 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
# File lib/chef/cookbook/cookbook_version_loader.rb, line 250 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
# File lib/chef/cookbook/cookbook_version_loader.rb, line 243 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
# File lib/chef/cookbook/cookbook_version_loader.rb, line 197 def chefignore @chefignore ||= Chefignore.new(cookbook_path) end
# File lib/chef/cookbook/cookbook_version_loader.rb, line 193 def empty? cookbook_settings.values.all?(&:empty?) && metadata_filenames.size == 0 end
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 207 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
# File lib/chef/cookbook/cookbook_version_loader.rb, line 160 def metadata_filenames return @metadata_filenames unless @metadata_filenames.empty? if File.exist?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)) @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE) end if File.exist?(File.join(cookbook_path, "metadata.json")) @metadata_filenames << File.join(cookbook_path, "metadata.json") elsif File.exist?(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
# File lib/chef/cookbook/cookbook_version_loader.rb, line 180 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 [#{cookbook_path}] has invalid metadata: #{metadata.errors.join("; ")}" raise Exceptions::MetadataNotValid, message end false end
# File lib/chef/cookbook/cookbook_version_loader.rb, line 237 def remove_ignored_files cookbook_settings[:all_files].reject! do |relative_path, full_path| chefignore.ignored?(relative_path) end end
# File lib/chef/cookbook/cookbook_version_loader.rb, line 274 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