diff options
Diffstat (limited to 'lib/chef')
-rw-r--r-- | lib/chef/cookbook_version.rb | 401 |
1 files changed, 241 insertions, 160 deletions
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 3bf19296c9..b2d98e7b8f 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -26,8 +26,208 @@ require 'chef/version_class' require 'pathname' require 'chef/digester' +require 'forwardable' + class Chef + # Handles the details of representing a cookbook in JSON form for uploading + # to a Chef Server. + class CookbookManifest + + # TODO: duplicates the same constant in CookbookVersion + COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ].freeze + + extend Forwardable + + attr_reader :cookbook_version + + def_delegator :@cookbook_version, :root_paths + def_delegator :@cookbook_version, :segment_filenames + def_delegator :@cookbook_version, :name + def_delegator :@cookbook_version, :metadata + def_delegator :@cookbook_version, :full_name + + def initialize(cookbook_version) + @cookbook_version = cookbook_version + + reset! + end + + # Resets all lazily computed values. + def reset! + @manifest = nil + @checksums = nil + @manifest_records_by_path = nil + 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 + # } + # + def manifest + @manifest || generate_manifest + @manifest + end + + def checksums + @manifest || generate_manifest + @checksums + end + + def manifest_records_by_path + @manifest || generate_manifest + @manifest_records_by_path + end + + # See #manifest for a description of the manifest return value. + # See #preferred_manifest_record for a description an individual manifest record. + def generate_manifest + manifest = Mash.new({ + :recipes => Array.new, + :definitions => Array.new, + :libraries => Array.new, + :attributes => Array.new, + :files => Array.new, + :templates => Array.new, + :resources => Array.new, + :providers => Array.new, + :root_files => Array.new + }) + @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_SEGMENTS.each do |segment| + segment_filenames(segment).each do |segment_file| + next if File.directory?(segment_file) + + path, specificity = parse_segment_file_from_root_paths(segment, segment_file) + file_name = File.basename(path) + + csum = checksum_cookbook_file(segment_file) + @checksums[csum] = segment_file + rs = Mash.new({ + :name => file_name, + :path => path, + :checksum => csum, + :specificity => specificity + }) + + manifest[segment] << rs + end + end + + manifest[:cookbook_name] = name.to_s + manifest[:metadata] = metadata + manifest[:version] = metadata.version + manifest[:name] = full_name + + @manifest_records_by_path = extract_manifest_records_by_path(manifest) + @manifest = manifest + end + + # TODO: This is kind of terrible. investigate removing it + def update_from(new_manifest) + @manifest = Mash.new new_manifest + @checksums = extract_checksums_from_manifest(@manifest) + @manifest_records_by_path = extract_manifest_records_by_path(@manifest) + + # TODO: this part of this method is "feature envious" it only deals with + # mutating the CookbookVersion object. + COOKBOOK_SEGMENTS.each do |segment| + next unless @manifest.has_key?(segment) + filenames = @manifest[segment].map{|manifest_record| manifest_record['name']} + + cookbook_version.replace_segment_filenames(segment, filenames) + end + end + + private + + def parse_segment_file_from_root_paths(segment, segment_file) + root_paths.each do |root_path| + pathname = Chef::Util::PathHelper.relative_path_from(root_path, segment_file) + + parts = pathname.each_filename.take(2) + # Check if path is actually under root_path + next if parts[0] == '..' + if segment == :templates || segment == :files + # 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 [ pathname.to_s, 'root_default' ] + else + return [ pathname.to_s, parts[1] ] + end + else + return [ pathname.to_s, 'default' ] + end + end + Chef::Log.error("Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}.") + raise "Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}." + end + + def extract_checksums_from_manifest(manifest) + checksums = {} + COOKBOOK_SEGMENTS.each do |segment| + next unless manifest.has_key?(segment) + manifest[segment].each do |manifest_record| + checksums[manifest_record[:checksum]] = nil + end + end + checksums + end + + # TODO: delegating to a class method like this is ugly. We should be able + # to fix this by moving logic into a class in a way that will make it easy + # to add support for SHA-2 + def checksum_cookbook_file(filepath) + CookbookVersion.checksum_cookbook_file(filepath) + end + + def extract_manifest_records_by_path(manifest) + manifest_records_by_path = {} + COOKBOOK_SEGMENTS.each do |segment| + next unless manifest.has_key?(segment) + manifest[segment].each do |manifest_record| + manifest_records_by_path[manifest_record[:path]] = manifest_record + end + end + manifest_records_by_path + end + end + # == Chef::CookbookVersion # CookbookVersion is a model object encapsulating the data about a Chef # cookbook. Chef supports maintaining multiple versions of a cookbook on a @@ -37,11 +237,40 @@ class Chef # TODO: timh/cw: 5-24-2010: mutators for files (e.g., recipe_filenames=, # recipe_filenames.insert) should dirty the manifest so it gets regenerated. class CookbookVersion + + ## METHODS DELEGATED TO CookbookResourceAdapter + # TODO: reorganize these + + private def cookbook_manifest + @cookbook_manifest ||= CookbookManifest.new(self) + end + + def manifest + cookbook_manifest.manifest + end + + # Returns a hash of checksums to either nil or the on disk path (which is + # done by generate_manifest). + def checksums + cookbook_manifest.checksums + end + + def manifest_records_by_path + cookbook_manifest.manifest_records_by_path + end + + def manifest=(new_manifest) + cookbook_manifest.update_from(new_manifest) + end + include Comparable COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] + # TODO: deprecate setter attr_accessor :root_paths + + # TODO: deprecate setter for all *_filenames, only used when consuming JSON attr_accessor :definition_filenames attr_accessor :template_filenames attr_accessor :file_filenames @@ -51,6 +280,8 @@ class Chef attr_accessor :root_filenames attr_accessor :name attr_accessor :metadata_filenames + + # TODO: unused, deprecate attr_accessor :status # A Chef::Cookbook::Metadata object. It has a setter that fixes up the @@ -95,6 +326,7 @@ class Chef @name = name @root_paths = root_paths @frozen = false + @attribute_filenames = Array.new @definition_filenames = Array.new @template_filenames = Array.new @@ -106,8 +338,9 @@ class Chef @provider_filenames = Array.new @metadata_filenames = Array.new @root_filenames = Array.new + + # TODO: unused, deprecate. @status = :ready - @manifest = nil @file_vendor = nil @metadata = Chef::Cookbook::Metadata.new end @@ -128,66 +361,10 @@ class Chef end def version=(new_version) - manifest["version"] = new_version + cookbook_manifest.reset! metadata.version(new_version) end - # A manifest is a Mash that maps segment names to arrays of manifest - # records (see #preferred_manifest_record for format of manifest records), - # as well as describing cookbook metadata. The manifest follows a form - # like the following: - # - # { - # :cookbook_name = "apache2", - # :version = "1.0", - # :name = "Apache 2" - # :metadata = ???TODO: timh/cw: 5-24-2010: describe this format, - # - # :files => [ - # { - # :name => "afile.rb", - # :path => "files/ubuntu-9.10/afile.rb", - # :checksum => "2222", - # :specificity => "ubuntu-9.10" - # }, - # ], - # :templates => [ manifest_record1, ... ], - # ... - # } - def manifest - unless @manifest - generate_manifest - end - @manifest - end - - def manifest=(new_manifest) - @manifest = Mash.new new_manifest - @checksums = extract_checksums_from_manifest(@manifest) - @manifest_records_by_path = extract_manifest_records_by_path(@manifest) - - COOKBOOK_SEGMENTS.each do |segment| - next unless @manifest.has_key?(segment) - filenames = @manifest[segment].map{|manifest_record| manifest_record['name']} - - replace_segment_filenames(segment, filenames) - end - end - - # Returns a hash of checksums to either nil or the on disk path (which is - # done by generate_manifest). - def checksums - unless @checksums - generate_manifest - end - @checksums - end - - def manifest_records_by_path - @manifest_records_by_path || generate_manifest - @manifest_records_by_path - end - def full_name "#{name}-#{version}" end @@ -310,7 +487,7 @@ class Chef def preferred_manifest_record(node, segment, filename) found_pref = find_preferred_manifest_record(node, segment, filename) if found_pref - @manifest_records_by_path[found_pref] + manifest_records_by_path[found_pref] else if segment == :files || segment == :templates error_message = "Cookbook '#{name}' (#{version}) does not contain a file at any of these locations:\n" @@ -497,7 +674,11 @@ class Chef cookbook_version end + # @deprecated This method was used by the Ruby Chef Server and is no longer + # needed. There is no replacement. def generate_manifest_with_urls(&url_generator) + Chef::Log.warn("Deprecated method #generate_manifest_with_urls called from #{caller(1).first}") + rendered_manifest = manifest.dup COOKBOOK_SEGMENTS.each do |segment| if rendered_manifest.has_key?(segment) @@ -605,12 +786,8 @@ class Chef def find_preferred_manifest_record(node, segment, filename) preferences = preferences_for_path(node, segment, filename) - # ensure that we generate the manifest, which will also generate - # @manifest_records_by_path - manifest - # in order of prefernce, look for the filename in the manifest - preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] } + preferences.find {|preferred_filename| manifest_records_by_path[preferred_filename] } end # For each filename, produce a mapping of base filename (i.e. recipe name @@ -619,80 +796,6 @@ class Chef filenames.select{|filename| filename =~ /\.rb$/}.inject({}){|memo, filename| memo[File.basename(filename, '.rb')] = filename ; memo } end - # See #manifest for a description of the manifest return value. - # See #preferred_manifest_record for a description an individual manifest record. - def generate_manifest - manifest = Mash.new({ - :recipes => Array.new, - :definitions => Array.new, - :libraries => Array.new, - :attributes => Array.new, - :files => Array.new, - :templates => Array.new, - :resources => Array.new, - :providers => Array.new, - :root_files => Array.new - }) - checksums_to_on_disk_paths = {} - - 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_SEGMENTS.each do |segment| - segment_filenames(segment).each do |segment_file| - next if File.directory?(segment_file) - - path, specificity = parse_segment_file_from_root_paths(segment, segment_file) - file_name = File.basename(path) - - csum = self.class.checksum_cookbook_file(segment_file) - checksums_to_on_disk_paths[csum] = segment_file - rs = Mash.new({ - :name => file_name, - :path => path, - :checksum => csum, - :specificity => specificity - }) - - manifest[segment] << rs - end - end - - manifest[:cookbook_name] = name.to_s - manifest[:metadata] = metadata - manifest[:version] = metadata.version - manifest[:name] = full_name - - @checksums = checksums_to_on_disk_paths - @manifest = manifest - @manifest_records_by_path = extract_manifest_records_by_path(manifest) - end - - def parse_segment_file_from_root_paths(segment, segment_file) - root_paths.each do |root_path| - pathname = Chef::Util::PathHelper.relative_path_from(root_path, segment_file) - - parts = pathname.each_filename.take(2) - # Check if path is actually under root_path - next if parts[0] == '..' - if segment == :templates || segment == :files - # 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 [ pathname.to_s, 'root_default' ] - else - return [ pathname.to_s, parts[1] ] - end - else - return [ pathname.to_s, 'default' ] - end - end - Chef::Log.error("Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}.") - raise "Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}." - end - def file_vendor unless @file_vendor @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest) @@ -700,27 +803,5 @@ class Chef @file_vendor end - def extract_checksums_from_manifest(manifest) - checksums = {} - COOKBOOK_SEGMENTS.each do |segment| - next unless manifest.has_key?(segment) - manifest[segment].each do |manifest_record| - checksums[manifest_record[:checksum]] = nil - end - end - checksums - end - - def extract_manifest_records_by_path(manifest) - manifest_records_by_path = {} - COOKBOOK_SEGMENTS.each do |segment| - next unless manifest.has_key?(segment) - manifest[segment].each do |manifest_record| - manifest_records_by_path[manifest_record[:path]] = manifest_record - end - end - manifest_records_by_path - end - end end |