diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chef/cookbook_manifest.rb | 220 | ||||
-rw-r--r-- | lib/chef/cookbook_version.rb | 202 |
2 files changed, 221 insertions, 201 deletions
diff --git a/lib/chef/cookbook_manifest.rb b/lib/chef/cookbook_manifest.rb new file mode 100644 index 0000000000..82d4da7008 --- /dev/null +++ b/lib/chef/cookbook_manifest.rb @@ -0,0 +1,220 @@ +# Author:: Daniel DeLeo (<dan@chef.io>) +# Copyright:: Copyright 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'forwardable' +require 'chef/util/path_helper' +require 'chef/log' + +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 +end diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index b2d98e7b8f..025828beb5 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -23,211 +23,11 @@ require 'chef/log' require 'chef/cookbook/file_vendor' require 'chef/cookbook/metadata' require 'chef/version_class' -require 'pathname' require 'chef/digester' - -require 'forwardable' +require 'chef/cookbook_manifest' 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 |