class Chef::Cookbook::CookbookVersionLoader
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 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
# 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
# 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
# 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
# File lib/chef/cookbook/cookbook_version_loader.rb, line 177 def chefignore @chefignore ||= Chefignore.new(File.basename(cookbook_path)) end
# 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
# 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
# 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 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
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
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
# 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
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
# 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
# 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
# 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
# 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