diff options
-rw-r--r-- | lib/chef/chef_fs/chef_fs_data_store.rb | 3 | ||||
-rw-r--r-- | lib/chef/cookbook_manifest.rb | 275 | ||||
-rw-r--r-- | lib/chef/cookbook_uploader.rb | 13 | ||||
-rw-r--r-- | lib/chef/cookbook_version.rb | 259 | ||||
-rw-r--r-- | spec/unit/cookbook_loader_spec.rb | 14 | ||||
-rw-r--r-- | spec/unit/cookbook_manifest_spec.rb | 609 | ||||
-rw-r--r-- | spec/unit/cookbook_uploader_spec.rb | 40 | ||||
-rw-r--r-- | spec/unit/cookbook_version_file_specificity_spec.rb | 554 | ||||
-rw-r--r-- | spec/unit/cookbook_version_spec.rb | 256 |
9 files changed, 1153 insertions, 870 deletions
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb index 9b4f7320b8..4084fb80d3 100644 --- a/lib/chef/chef_fs/chef_fs_data_store.rb +++ b/lib/chef/chef_fs/chef_fs_data_store.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/cookbook_manifest' require 'chef_zero/data_store/memory_store' require 'chef_zero/data_store/data_already_exists_error' require 'chef_zero/data_store/data_not_found_error' @@ -147,7 +148,7 @@ class Chef # get /cookbooks/NAME/version result = nil begin - result = entry.chef_object.to_hash + result = Chef::CookbookManifest.new(entry.chef_object).to_hash rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end diff --git a/lib/chef/cookbook_manifest.rb b/lib/chef/cookbook_manifest.rb new file mode 100644 index 0000000000..0d21e9725c --- /dev/null +++ b/lib/chef/cookbook_manifest.rb @@ -0,0 +1,275 @@ +# 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 + + # Duplicates the same constant in CookbookVersion. We cannot remove it + # there because it is treated by other code as part of CookbookVersion's + # public API (also used in some deprecated methods). + 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_delegator :@cookbook_version, :version + def_delegator :@cookbook_version, :frozen_version? + + # Create a new CookbookManifest object for the given `cookbook_version`. + # You can subsequently call #to_hash to get a Hash representation of the + # cookbook_version in the "manifest" format, or #to_json to get a JSON + # representation of the cookbook_version. + # + # The inferface for this behavior is expected to change as we implement new + # manifest formats. The entire class should be considered a private API for + # now. + # + # @api private + # @param policy_mode [Boolean] whether to convert cookbooks to Hash/JSON in + # the format used by the `cookbook_artifacts` endpoint (for policyfiles). + # Setting this option also changes the behavior of #save_url and + # #force_save_url such that CookbookVersions will be uploaded to the new + # `cookbook_artifacts` API. This endpoint is currently under active + # development and the format is expected to change frequently, therefore + # the result of #manifest, #to_hash, and #to_json will not be stable when + # `policy_mode` is enabled. + def initialize(cookbook_version, policy_mode: false) + @cookbook_version = cookbook_version + @policy_mode = !!policy_mode + + 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 + + def policy_mode? + @policy_mode + end + + def to_hash + result = manifest.dup + result['frozen?'] = frozen_version? + result['chef_type'] = 'cookbook_version' + result.to_hash + end + + def to_json(*a) + result = to_hash + result['json_class'] = "Chef::CookbookVersion" + Chef::JSONCompat.to_json(result, *a) + end + + # Return the URL to save (PUT) this object to the server via the + # REST api. If there is an existing document on the server and it + # is marked frozen, a PUT will result in a 409 Conflict. + def save_url + "#{cookbook_url_path}/#{name}/#{version}" + end + + # Adds the `force=true` parameter to the upload URL. This allows + # the user to overwrite a frozen cookbook (a PUT against the + # normal #save_url raises a 409 Conflict in this case). + def force_save_url + "#{save_url}?force=true" + end + + # Update this CookbookManifest from the contents of another manifest, and + # make the corresponding changes to the cookbook_version object. Required + # to provide backward compatibility with CookbookVersion#manifest= method. + 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) + + 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 cookbook_url_path + policy_mode? ? "cookbook_artifacts" : "cookbooks" + 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 + + 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 + + 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_uploader.rb b/lib/chef/cookbook_uploader.rb index 2d8bf5bc7e..f24ce2cd56 100644 --- a/lib/chef/cookbook_uploader.rb +++ b/lib/chef/cookbook_uploader.rb @@ -3,6 +3,7 @@ require 'set' require 'chef/exceptions' require 'chef/knife/cookbook_metadata' require 'chef/digester' +require 'chef/cookbook_manifest' require 'chef/cookbook_version' require 'chef/cookbook/syntax_check' require 'chef/cookbook/file_system_file_vendor' @@ -40,6 +41,7 @@ class Chef @cookbooks = Array(cookbooks) @rest = opts[:rest] || Chef::REST.new(Chef::Config[:chef_server_url]) @concurrency = opts[:concurrency] || 10 + @policy_mode = opts[:policy_mode] || false end def upload_cookbooks @@ -92,9 +94,12 @@ class Chef # files are uploaded, so save the manifest cookbooks.each do |cb| - save_url = opts[:force] ? cb.force_save_url : cb.save_url + + manifest = Chef::CookbookManifest.new(cb, policy_mode: policy_mode?) + + save_url = opts[:force] ? manifest.force_save_url : manifest.save_url begin - rest.put(save_url, cb) + rest.put(save_url, manifest) rescue Net::HTTPServerException => e case e.response.code when "409" @@ -143,5 +148,9 @@ class Chef end end + def policy_mode? + @policy_mode + end + end end diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 3bf19296c9..c51d5798c5 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -23,8 +23,8 @@ require 'chef/log' require 'chef/cookbook/file_vendor' require 'chef/cookbook/metadata' require 'chef/version_class' -require 'pathname' require 'chef/digester' +require 'chef/cookbook_manifest' class Chef @@ -33,10 +33,8 @@ class Chef # cookbook. Chef supports maintaining multiple versions of a cookbook on a # single server; each version is represented by a distinct instance of this # class. - #-- - # 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 + include Comparable COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] @@ -51,7 +49,16 @@ class Chef attr_accessor :root_filenames attr_accessor :name attr_accessor :metadata_filenames - attr_accessor :status + + def status=(new_status) + Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed") + @status = new_status + end + + def status + Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed") + @status + end # A Chef::Cookbook::Metadata object. It has a setter that fixes up the # metadata to add descriptions of the recipes contained in this @@ -95,6 +102,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 +114,9 @@ class Chef @provider_filenames = Array.new @metadata_filenames = Array.new @root_filenames = Array.new + + # deprecated @status = :ready - @manifest = nil @file_vendor = nil @metadata = Chef::Cookbook::Metadata.new end @@ -128,66 +137,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 @@ -208,6 +161,24 @@ class Chef alias :attribute_files :attribute_filenames alias :attribute_files= :attribute_filenames= + 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 + # Return recipe names in the form of cookbook_name::recipe_name def fully_qualified_recipe_names results = Array.new @@ -310,7 +281,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" @@ -471,19 +442,6 @@ class Chef end private :preferences_for_path - def to_hash - result = manifest.dup - result['frozen?'] = frozen_version? - result['chef_type'] = 'cookbook_version' - result.to_hash - end - - def to_json(*a) - result = to_hash - result['json_class'] = self.class.name - Chef::JSONCompat.to_json(result, *a) - end - def self.json_create(o) cookbook_version = new(o["cookbook_name"]) # We want the Chef::Cookbook::Metadata object to always be inflated @@ -497,7 +455,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.deprecation("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) @@ -510,6 +472,18 @@ class Chef rendered_manifest end + + def to_hash + # TODO: this should become deprecated when the API for CookbookManifest becomes stable + cookbook_manifest.to_hash + end + + def to_json(*a) + # TODO: this should become deprecated when the API for CookbookManifest becomes stable + cookbook_manifest.to_json + end + + def metadata_json_file File.join(root_paths[0], "metadata.json") end @@ -527,26 +501,23 @@ class Chef ## # REST API ## - def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + + def save_url + # TODO: this should become deprecated when the API for CookbookManifest becomes stable + cookbook_manifest.save_url end - def chef_server_rest - self.class.chef_server_rest + def force_save_url + # TODO: this should become deprecated when the API for CookbookManifest becomes stable + cookbook_manifest.force_save_url end - # Return the URL to save (PUT) this object to the server via the - # REST api. If there is an existing document on the server and it - # is marked frozen, a PUT will result in a 409 Conflict. - def save_url - "cookbooks/#{name}/#{version}" + def self.chef_server_rest + Chef::REST.new(Chef::Config[:chef_server_url]) end - # Adds the `force=true` parameter to the upload URL. This allows - # the user to overwrite a frozen cookbook (a PUT against the - # normal #save_url raises a 409 Conflict in this case). - def force_save_url - "cookbooks/#{name}/#{version}?force=true" + def chef_server_rest + self.class.chef_server_rest end def destroy @@ -602,15 +573,15 @@ class Chef private + def cookbook_manifest + @cookbook_manifest ||= CookbookManifest.new(self) + end + 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 +590,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 +597,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 diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb index 51532778e4..45a985bafd 100644 --- a/spec/unit/cookbook_loader_spec.rb +++ b/spec/unit/cookbook_loader_spec.rb @@ -190,6 +190,11 @@ describe Chef::CookbookLoader do end describe "loading only one cookbook" do + + let(:openldap_cookbook) { cookbook_loader["openldap"] } + + let(:cookbook_as_hash) { Chef::CookbookManifest.new(openldap_cookbook).to_hash } + before(:each) do cookbook_loader.load_cookbook("openldap") end @@ -205,12 +210,11 @@ describe Chef::CookbookLoader do it "should not duplicate keys when serialized to JSON" do # Chef JSON serialization will generate duplicate keys if given # a Hash containing matching string and symbol keys. See CHEF-4571. - aa = cookbook_loader["openldap"] - expect(aa.to_hash["metadata"].recipes.keys).not_to include(:openldap) - expect(aa.to_hash["metadata"].recipes.keys).to include("openldap") + expect(cookbook_as_hash["metadata"].recipes.keys).not_to include(:openldap) + expect(cookbook_as_hash["metadata"].recipes.keys).to include("openldap") expected_desc = "Main Open LDAP configuration" - expect(aa.to_hash["metadata"].recipes["openldap"]).to eq(expected_desc) - raw = Chef::JSONCompat.to_json(aa.to_hash["metadata"].recipes) + expect(cookbook_as_hash["metadata"].recipes["openldap"]).to eq(expected_desc) + raw = Chef::JSONCompat.to_json(cookbook_as_hash["metadata"].recipes) search_str = "\"openldap\":\"" key_idx = raw.index(search_str) expect(key_idx).to be > 0 diff --git a/spec/unit/cookbook_manifest_spec.rb b/spec/unit/cookbook_manifest_spec.rb index 8b50b040c2..938f72c743 100644 --- a/spec/unit/cookbook_manifest_spec.rb +++ b/spec/unit/cookbook_manifest_spec.rb @@ -1,7 +1,6 @@ # -# Author:: Tim Hinderliter (<tim@opscode.com>) -# Author:: Christopher Walters (<cw@opscode.com>) -# Copyright:: Copyright (c) 2010 Opscode, Inc. +# Author:: Daniel DeLeo (<dan@chef.io>) +# Copyright:: Copyright (c) 2015 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,540 +14,214 @@ # 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 'spec_helper' +require 'chef/cookbook_manifest' +require 'chef/digester' +require 'pathname' -describe "Chef::CookbookVersion manifest" do - before(:each) do - @cookbook = Chef::CookbookVersion.new "test-cookbook" - @cookbook.manifest = { - "files" => - [ - # afile.rb - { - :name => "afile.rb", - :path => "files/host-examplehost.example.org/afile.rb", - :checksum => "csum-host", - :specificity => "host-examplehost.example.org" - }, - { - :name => "afile.rb", - :path => "files/ubuntu-9.10/afile.rb", - :checksum => "csum-platver-full", - :specificity => "ubuntu-9.10" - }, - { - :name => "afile.rb", - :path => "files/newubuntu-9/afile.rb", - :checksum => "csum-platver-partial", - :specificity => "newubuntu-9" - }, - { - :name => "afile.rb", - :path => "files/ubuntu/afile.rb", - :checksum => "csum-plat", - :specificity => "ubuntu" - }, - { - :name => "afile.rb", - :path => "files/default/afile.rb", - :checksum => "csum-default", - :specificity => "default" - }, - - # for different/odd platform_versions - { - :name => "bfile.rb", - :path => "files/fakeos-2.0.rc.1/bfile.rb", - :checksum => "csum2-platver-full", - :specificity => "fakeos-2.0.rc.1" - }, - { - :name => "bfile.rb", - :path => "files/newfakeos-2.0.rc/bfile.rb", - :checksum => "csum2-platver-partial", - :specificity => "newfakeos-2.0.rc" - }, - { - :name => "bfile.rb", - :path => "files/fakeos-maple tree/bfile.rb", - :checksum => "csum3-platver-full", - :specificity => "maple tree" - }, - { - :name => "bfile.rb", - :path => "files/fakeos-1/bfile.rb", - :checksum => "csum4-platver-full", - :specificity => "fakeos-1" - }, - - # directory adirectory - { - :name => "anotherfile1.rb", - :path => "files/host-examplehost.example.org/adirectory/anotherfile1.rb.host", - :checksum => "csum-host-1", - :specificity => "host-examplehost.example.org" - }, - { - :name => "anotherfile2.rb", - :path => "files/host-examplehost.example.org/adirectory/anotherfile2.rb.host", - :checksum => "csum-host-2", - :specificity => "host-examplehost.example.org" - }, - - { - :name => "anotherfile1.rb", - :path => "files/ubuntu-9.10/adirectory/anotherfile1.rb.platform-full-version", - :checksum => "csum-platver-full-1", - :specificity => "ubuntu-9.10" - }, - { - :name => "anotherfile2.rb", - :path => "files/ubuntu-9.10/adirectory/anotherfile2.rb.platform-full-version", - :checksum => "csum-platver-full-2", - :specificity => "ubuntu-9.10" - }, - - { - :name => "anotherfile1.rb", - :path => "files/newubuntu-9/adirectory/anotherfile1.rb.platform-partial-version", - :checksum => "csum-platver-partial-1", - :specificity => "newubuntu-9" - }, - { - :name => "anotherfile2.rb", - :path => "files/newubuntu-9/adirectory/anotherfile2.rb.platform-partial-version", - :checksum => "csum-platver-partial-2", - :specificity => "nweubuntu-9" - }, - - { - :name => "anotherfile1.rb", - :path => "files/ubuntu/adirectory/anotherfile1.rb.platform", - :checksum => "csum-plat-1", - :specificity => "ubuntu" - }, - { - :name => "anotherfile2.rb", - :path => "files/ubuntu/adirectory/anotherfile2.rb.platform", - :checksum => "csum-plat-2", - :specificity => "ubuntu" - }, - - { - :name => "anotherfile1.rb", - :path => "files/default/adirectory/anotherfile1.rb.default", - :checksum => "csum-default-1", - :specificity => "default" - }, - { - :name => "anotherfile2.rb", - :path => "files/default/adirectory/anotherfile2.rb.default", - :checksum => "csum-default-2", - :specificity => "default" - }, - # for different/odd platform_versions - { - :name => "anotherfile1.rb", - :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-full-version", - :checksum => "csum2-platver-full-1", - :specificity => "fakeos-2.0.rc.1" - }, - { - :name => "anotherfile2.rb", - :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-full-version", - :checksum => "csum2-platver-full-2", - :specificity => "fakeos-2.0.rc.1" - }, - { - :name => "anotherfile1.rb", - :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-partial-version", - :checksum => "csum2-platver-partial-1", - :specificity => "newfakeos-2.0.rc" - }, - { - :name => "anotherfile2.rb", - :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-partial-version", - :checksum => "csum2-platver-partial-2", - :specificity => "newfakeos-2.0.rc" - }, - { - :name => "anotherfile1.rb", - :path => "files/fakeos-maple tree/adirectory/anotherfile1.rb.platform-full-version", - :checksum => "csum3-platver-full-1", - :specificity => "fakeos-maple tree" - }, - { - :name => "anotherfile2.rb", - :path => "files/fakeos-maple tree/adirectory/anotherfile2.rb.platform-full-version", - :checksum => "csum3-platver-full-2", - :specificity => "fakeos-maple tree" - }, - { - :name => "anotherfile1.rb", - :path => "files/fakeos-1/adirectory/anotherfile1.rb.platform-full-version", - :checksum => "csum4-platver-full-1", - :specificity => "fakeos-1" - }, - { - :name => "anotherfile2.rb", - :path => "files/fakeos-1/adirectory/anotherfile2.rb.platform-full-version", - :checksum => "csum4-platver-full-2", - :specificity => "fakeos-1" - }, - ] - } - - end - - - it "should return a manifest record based on priority preference: host" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "examplehost.example.org" - - manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum-host") - end - - it "should return a manifest record based on priority preference: platform & full version" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum-platver-full") - end - - it "should return a manifest record based on priority preference: platform & partial version" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "newubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum-platver-partial") - end +describe Chef::CookbookManifest do - it "should return a manifest record based on priority preference: platform only" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "1.0" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + let(:version) { "1.2.3" } - manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum-plat") - end - - it "should return a manifest record based on priority preference: default" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "notubuntu" - node.automatic_attrs[:platform_version] = "1.0" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum-default") + let(:metadata) do + Chef::Cookbook::Metadata.new.tap do |m| + m.version(version) + end end - it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "2.0.rc.1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + let(:cookbook_root) { '/tmp/blah' } - manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum2-platver-full") + let(:cookbook_version) do + Chef::CookbookVersion.new("tatft", cookbook_root).tap do |c| + c.metadata = metadata + end end - it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "newfakeos" - node.automatic_attrs[:platform_version] = "2.0.rc.1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + let(:policy_mode) { false } - manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum2-platver-partial") - end + subject(:cookbook_manifest) { Chef::CookbookManifest.new(cookbook_version, policy_mode: policy_mode) } - it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "maple tree" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + context "when policy mode is not specified" do - manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum3-platver-full") - end + subject(:cookbook_manifest) { Chef::CookbookManifest.new(cookbook_version) } - it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + it "defaults to policies disabled" do + expect(cookbook_manifest.policy_mode?).to be(false) + end - manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") - expect(manifest_record).not_to be_nil - expect(manifest_record[:checksum]).to eq("csum4-platver-full") end - describe "when fetching the contents of a directory by file specificity" do - - it "should return a directory of manifest records based on priority preference: host" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "examplehost.example.org" + describe "collecting cookbook data from the cookbook version object" do - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) - - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum-host-1", "csum-host-2"]) + it "delegates `name' to cookbook_version" do + expect(cookbook_manifest.name).to eq("tatft") end - it "should return a directory of manifest records based on priority preference: platform & full version" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) - - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum-platver-full-1", "csum-platver-full-2"]) + it "delegates `root_paths' to cookbook_version" do + expect(cookbook_manifest.root_paths).to eq(['/tmp/blah']) end - it "should return a directory of manifest records based on priority preference: platform & partial version" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "newubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) - - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum-platver-partial-1", "csum-platver-partial-2"]) + it "delegates `metadata' to cookbook_version" do + expect(cookbook_manifest.metadata).to eq(metadata) end - it "should return a directory of manifest records based on priority preference: platform only" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "1.0" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) - - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum-plat-1", "csum-plat-2"]) + it "delegates `full_name' to cookbook_version" do + expect(cookbook_manifest.full_name).to eq("tatft-1.2.3") end - it "should return a directory of manifest records based on priority preference: default" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "notubuntu" - node.automatic_attrs[:platform_version] = "1.0" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) - - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum-default-1", "csum-default-2"]) + it "delegates `version' to cookbook_version" do + expect(cookbook_manifest.version).to eq(version) end - it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "2.0.rc.1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) + it "delegates `frozen_version?' to cookbook_version" do + expect(cookbook_manifest.frozen_version?).to be(false) + end - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum2-platver-full-1", "csum2-platver-full-2"]) + it "delegates `segment_filenames' to cookbook_version" do + expect(cookbook_version).to receive(:segment_filenames).with(:recipes).and_return([]) + expect(cookbook_manifest.segment_filenames(:recipes)).to eq([]) end - it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "newfakeos" - node.automatic_attrs[:platform_version] = "2.0.rc.1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + end - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) + context "when given an empty cookbook" do - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum2-platver-partial-1", "csum2-platver-partial-2"]) - end + let(:expected_hash) do + { + "chef_type" => "cookbook_version", - it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "maple tree" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + "name" => "tatft-1.2.3", + "version" => "1.2.3", + "cookbook_name" => "tatft", + "metadata" => metadata, - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) + "frozen?" => false, - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum3-platver-full-1", "csum3-platver-full-2"]) + "recipes" =>[], + "definitions" =>[], + "libraries" =>[], + "attributes" =>[], + "files" =>[], + "templates" =>[], + "resources" =>[], + "providers" =>[], + "root_files" =>[], + } end - it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") - expect(manifest_records).not_to be_nil - expect(manifest_records.size).to eq(2) - - checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } - expect(checksums.sort).to eq(["csum4-platver-full-1", "csum4-platver-full-2"]) + it "converts the CookbookVersion to a ruby Hash representation" do + expect(cookbook_manifest.to_hash).to eq(expected_hash) end + end - ## Globbing the relative paths out of the manifest records ## + context "when given a cookbook with files" do - describe "when globbing for relative file paths based on filespecificity" do - it "should return a list of relative paths based on priority preference: host" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "examplehost.example.org" + let(:cookbook_root) { File.join(CHEF_SPEC_DATA, 'cb_version_cookbooks', 'tatft') } - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + let(:attribute_filenames) { Dir[File.join(cookbook_root, 'attributes', '**', '*.rb')] } + let(:definition_filenames) { Dir[File.join(cookbook_root, 'definitions', '**', '*.rb')] } + let(:file_filenames) { Dir[File.join(cookbook_root, 'files', '**', '*.tgz')] } + let(:recipe_filenames) { Dir[File.join(cookbook_root, 'recipes', '**', '*.rb')] } + let(:template_filenames) { Dir[File.join(cookbook_root, 'templates', '**', '*.erb')] } + let(:library_filenames) { Dir[File.join(cookbook_root, 'libraries', '**', '*.rb')] } + let(:resource_filenames) { Dir[File.join(cookbook_root, 'resources', '**', '*.rb')] } + let(:provider_filenames) { Dir[File.join(cookbook_root, 'providers', '**', '*.rb')] } + let(:root_filenames) { Array(File.join(cookbook_root, 'README.rdoc')) } + let(:metadata_filenames) { Array(File.join(cookbook_root, 'metadata.json')) } - expect(filenames.sort).to eq(['anotherfile1.rb.host', 'anotherfile2.rb.host']) - end + let(:match_md5) { /[0-9a-f]{32}/ } - it "should return a list of relative paths based on priority preference: platform & full version" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + def map_to_file_specs(paths) + paths.map do |path| - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + relative_path = Pathname.new(path).relative_path_from(Pathname.new(cookbook_root)).to_s - expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) + { + "name" => File.basename(path), + "path" => relative_path, + "checksum" => Chef::Digester.generate_md5_checksum_for_file(path), + "specificity" => "default", + } + end end - it "should return a list of relative paths based on priority preference: platform & partial version" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "newubuntu" - node.automatic_attrs[:platform_version] = "9.10" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + let(:expected_hash) do + { + "chef_type" => "cookbook_version", - expect(filenames.sort).to eq(['anotherfile1.rb.platform-partial-version', 'anotherfile2.rb.platform-partial-version']) - end - - it "should return a list of relative paths based on priority preference: platform only" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "ubuntu" - node.automatic_attrs[:platform_version] = "1.0" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + "name" => "tatft-1.2.3", + "version" => "1.2.3", + "cookbook_name" => "tatft", + "metadata" => metadata, - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + "frozen?" => false, - expect(filenames.sort).to eq(['anotherfile1.rb.platform', 'anotherfile2.rb.platform']) + "recipes" => map_to_file_specs(recipe_filenames), + "definitions" => map_to_file_specs(definition_filenames), + "libraries" => map_to_file_specs(library_filenames), + "attributes" => map_to_file_specs(attribute_filenames), + "files" => map_to_file_specs(file_filenames), + "templates" => map_to_file_specs(template_filenames), + "resources" => map_to_file_specs(resource_filenames), + "providers" => map_to_file_specs(provider_filenames), + "root_files" => map_to_file_specs(root_filenames), + } end - it "should return a list of relative paths based on priority preference: default" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "notubuntu" - node.automatic_attrs[:platform_version] = "1.0" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + before do + cookbook_version.attribute_filenames = attribute_filenames + cookbook_version.definition_filenames = definition_filenames + cookbook_version.file_filenames = file_filenames + cookbook_version.recipe_filenames = recipe_filenames + cookbook_version.template_filenames = template_filenames + cookbook_version.library_filenames = library_filenames + cookbook_version.resource_filenames = resource_filenames + cookbook_version.provider_filenames = provider_filenames + cookbook_version.root_filenames = root_filenames + cookbook_version.metadata_filenames = metadata_filenames + end - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + it "converts the CookbookVersion to a ruby Hash representation" do + cookbook_manifest_hash = cookbook_manifest.to_hash - expect(filenames.sort).to eq(['anotherfile1.rb.default', 'anotherfile2.rb.default']) + expect(cookbook_manifest_hash.keys).to match_array(expected_hash.keys) + cookbook_manifest_hash.each do |key, value| + expect(cookbook_manifest_hash[key]).to eq(expected_hash[key]) + end end - it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 1" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "2.0.rc.1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + end - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + describe "providing upstream URLs for save" do - expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) - end + context "and policy mode is disabled" do - it "should return a list of relative paths based on priority preference: platform & partial version - platform_version variant 1" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "newfakeos" - node.automatic_attrs[:platform_version] = "2.0.rc.1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + it "gives the save URL" do + expect(cookbook_manifest.save_url).to eq("cookbooks/tatft/1.2.3") + end - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + it "gives the force save URL" do + expect(cookbook_manifest.force_save_url).to eq("cookbooks/tatft/1.2.3?force=true") + end - expect(filenames.sort).to eq(['anotherfile1.rb.platform-partial-version', 'anotherfile2.rb.platform-partial-version']) end - it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 2" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "maple tree" - node.automatic_attrs[:fqdn] = "differenthost.example.org" - - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + context "and policy mode is enabled" do - expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) - end + let(:policy_mode) { true } - it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 3" do - node = Chef::Node.new - node.automatic_attrs[:platform] = "fakeos" - node.automatic_attrs[:platform_version] = "1" - node.automatic_attrs[:fqdn] = "differenthost.example.org" + it "gives the save URL" do + expect(cookbook_manifest.save_url).to eq("cookbook_artifacts/tatft/1.2.3") + end - filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") - expect(filenames).not_to be_nil - expect(filenames.size).to eq(2) + it "gives the force save URL" do + expect(cookbook_manifest.force_save_url).to eq("cookbook_artifacts/tatft/1.2.3?force=true") + end - expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) end end + end + diff --git a/spec/unit/cookbook_uploader_spec.rb b/spec/unit/cookbook_uploader_spec.rb index af25baff13..152e5373f0 100644 --- a/spec/unit/cookbook_uploader_spec.rb +++ b/spec/unit/cookbook_uploader_spec.rb @@ -45,7 +45,13 @@ describe Chef::CookbookUploader do let(:sandbox_commit_uri) { "https://chef.example.org/sandboxes/abc123" } - let(:uploader) { described_class.new(cookbooks_to_upload, :rest => http_client) } + let(:policy_mode) { false } + + let(:uploader) { described_class.new(cookbooks_to_upload, rest: http_client, policy_mode: policy_mode) } + + it "defaults to not enabling policy mode" do + expect(described_class.new(cookbooks_to_upload, rest: http_client).policy_mode?).to be(false) + end it "has a list of cookbooks to upload" do expect(uploader.cookbooks).to eq(cookbooks_to_upload) @@ -61,7 +67,7 @@ describe Chef::CookbookUploader do describe "uploading cookbooks" do def url_for(cksum) - "https://storage.example.com/#{cksum}" + "https://storage.example.com/#{cksum}" end let(:sandbox_response) do @@ -94,6 +100,10 @@ describe Chef::CookbookUploader do end end + def expected_save_url(cookbook) + "cookbooks/#{cookbook.name}/#{cookbook.version}" + end + def expect_sandbox_commit expect(http_client).to receive(:put).with(sandbox_commit_uri, {:is_completed => true}) end @@ -102,7 +112,7 @@ describe Chef::CookbookUploader do cookbooks_to_upload.each do |cookbook| expect(http_client).to receive(:put). - with(cookbook.save_url, cookbook) + with(expected_save_url(cookbook), cookbook) end end @@ -155,6 +165,30 @@ describe Chef::CookbookUploader do end end + + context "when policy_mode is specified" do + + let(:cksums_not_on_remote) do + checksums_of_cookbook_files.keys + end + + let(:policy_mode) { true } + + def expected_save_url(cookbook) + "cookbook_artifacts/#{cookbook.name}/#{cookbook.version}" + end + + it "uploads all files in a sandbox transaction, then creates cookbooks on the server using cookbook_artifacts API" do + expect_sandbox_create + expect_checksum_upload + expect_sandbox_commit + expect_cookbook_create + + uploader.upload_cookbooks + end + + + end end end diff --git a/spec/unit/cookbook_version_file_specificity_spec.rb b/spec/unit/cookbook_version_file_specificity_spec.rb new file mode 100644 index 0000000000..73b10899d4 --- /dev/null +++ b/spec/unit/cookbook_version_file_specificity_spec.rb @@ -0,0 +1,554 @@ +# +# Author:: Tim Hinderliter (<tim@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2010 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 'spec_helper' + +describe Chef::CookbookVersion, "file specificity" do + before(:each) do + @cookbook = Chef::CookbookVersion.new "test-cookbook" + @cookbook.manifest = { + "files" => + [ + # afile.rb + { + :name => "afile.rb", + :path => "files/host-examplehost.example.org/afile.rb", + :checksum => "csum-host", + :specificity => "host-examplehost.example.org" + }, + { + :name => "afile.rb", + :path => "files/ubuntu-9.10/afile.rb", + :checksum => "csum-platver-full", + :specificity => "ubuntu-9.10" + }, + { + :name => "afile.rb", + :path => "files/newubuntu-9/afile.rb", + :checksum => "csum-platver-partial", + :specificity => "newubuntu-9" + }, + { + :name => "afile.rb", + :path => "files/ubuntu/afile.rb", + :checksum => "csum-plat", + :specificity => "ubuntu" + }, + { + :name => "afile.rb", + :path => "files/default/afile.rb", + :checksum => "csum-default", + :specificity => "default" + }, + + # for different/odd platform_versions + { + :name => "bfile.rb", + :path => "files/fakeos-2.0.rc.1/bfile.rb", + :checksum => "csum2-platver-full", + :specificity => "fakeos-2.0.rc.1" + }, + { + :name => "bfile.rb", + :path => "files/newfakeos-2.0.rc/bfile.rb", + :checksum => "csum2-platver-partial", + :specificity => "newfakeos-2.0.rc" + }, + { + :name => "bfile.rb", + :path => "files/fakeos-maple tree/bfile.rb", + :checksum => "csum3-platver-full", + :specificity => "maple tree" + }, + { + :name => "bfile.rb", + :path => "files/fakeos-1/bfile.rb", + :checksum => "csum4-platver-full", + :specificity => "fakeos-1" + }, + + # directory adirectory + { + :name => "anotherfile1.rb", + :path => "files/host-examplehost.example.org/adirectory/anotherfile1.rb.host", + :checksum => "csum-host-1", + :specificity => "host-examplehost.example.org" + }, + { + :name => "anotherfile2.rb", + :path => "files/host-examplehost.example.org/adirectory/anotherfile2.rb.host", + :checksum => "csum-host-2", + :specificity => "host-examplehost.example.org" + }, + + { + :name => "anotherfile1.rb", + :path => "files/ubuntu-9.10/adirectory/anotherfile1.rb.platform-full-version", + :checksum => "csum-platver-full-1", + :specificity => "ubuntu-9.10" + }, + { + :name => "anotherfile2.rb", + :path => "files/ubuntu-9.10/adirectory/anotherfile2.rb.platform-full-version", + :checksum => "csum-platver-full-2", + :specificity => "ubuntu-9.10" + }, + + { + :name => "anotherfile1.rb", + :path => "files/newubuntu-9/adirectory/anotherfile1.rb.platform-partial-version", + :checksum => "csum-platver-partial-1", + :specificity => "newubuntu-9" + }, + { + :name => "anotherfile2.rb", + :path => "files/newubuntu-9/adirectory/anotherfile2.rb.platform-partial-version", + :checksum => "csum-platver-partial-2", + :specificity => "nweubuntu-9" + }, + + { + :name => "anotherfile1.rb", + :path => "files/ubuntu/adirectory/anotherfile1.rb.platform", + :checksum => "csum-plat-1", + :specificity => "ubuntu" + }, + { + :name => "anotherfile2.rb", + :path => "files/ubuntu/adirectory/anotherfile2.rb.platform", + :checksum => "csum-plat-2", + :specificity => "ubuntu" + }, + + { + :name => "anotherfile1.rb", + :path => "files/default/adirectory/anotherfile1.rb.default", + :checksum => "csum-default-1", + :specificity => "default" + }, + { + :name => "anotherfile2.rb", + :path => "files/default/adirectory/anotherfile2.rb.default", + :checksum => "csum-default-2", + :specificity => "default" + }, + # for different/odd platform_versions + { + :name => "anotherfile1.rb", + :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-full-version", + :checksum => "csum2-platver-full-1", + :specificity => "fakeos-2.0.rc.1" + }, + { + :name => "anotherfile2.rb", + :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-full-version", + :checksum => "csum2-platver-full-2", + :specificity => "fakeos-2.0.rc.1" + }, + { + :name => "anotherfile1.rb", + :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-partial-version", + :checksum => "csum2-platver-partial-1", + :specificity => "newfakeos-2.0.rc" + }, + { + :name => "anotherfile2.rb", + :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-partial-version", + :checksum => "csum2-platver-partial-2", + :specificity => "newfakeos-2.0.rc" + }, + { + :name => "anotherfile1.rb", + :path => "files/fakeos-maple tree/adirectory/anotherfile1.rb.platform-full-version", + :checksum => "csum3-platver-full-1", + :specificity => "fakeos-maple tree" + }, + { + :name => "anotherfile2.rb", + :path => "files/fakeos-maple tree/adirectory/anotherfile2.rb.platform-full-version", + :checksum => "csum3-platver-full-2", + :specificity => "fakeos-maple tree" + }, + { + :name => "anotherfile1.rb", + :path => "files/fakeos-1/adirectory/anotherfile1.rb.platform-full-version", + :checksum => "csum4-platver-full-1", + :specificity => "fakeos-1" + }, + { + :name => "anotherfile2.rb", + :path => "files/fakeos-1/adirectory/anotherfile2.rb.platform-full-version", + :checksum => "csum4-platver-full-2", + :specificity => "fakeos-1" + }, + ] + } + + end + + + it "should return a manifest record based on priority preference: host" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "examplehost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum-host") + end + + it "should return a manifest record based on priority preference: platform & full version" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum-platver-full") + end + + it "should return a manifest record based on priority preference: platform & partial version" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "newubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum-platver-partial") + end + + it "should return a manifest record based on priority preference: platform only" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "1.0" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum-plat") + end + + it "should return a manifest record based on priority preference: default" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "notubuntu" + node.automatic_attrs[:platform_version] = "1.0" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum-default") + end + + it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "2.0.rc.1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum2-platver-full") + end + + it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "newfakeos" + node.automatic_attrs[:platform_version] = "2.0.rc.1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum2-platver-partial") + end + + it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "maple tree" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum3-platver-full") + end + + it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") + expect(manifest_record).not_to be_nil + expect(manifest_record[:checksum]).to eq("csum4-platver-full") + end + + describe "when fetching the contents of a directory by file specificity" do + + it "should return a directory of manifest records based on priority preference: host" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "examplehost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum-host-1", "csum-host-2"]) + end + + it "should return a directory of manifest records based on priority preference: platform & full version" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum-platver-full-1", "csum-platver-full-2"]) + end + + it "should return a directory of manifest records based on priority preference: platform & partial version" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "newubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum-platver-partial-1", "csum-platver-partial-2"]) + end + + it "should return a directory of manifest records based on priority preference: platform only" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "1.0" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum-plat-1", "csum-plat-2"]) + end + + it "should return a directory of manifest records based on priority preference: default" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "notubuntu" + node.automatic_attrs[:platform_version] = "1.0" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum-default-1", "csum-default-2"]) + end + + it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "2.0.rc.1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum2-platver-full-1", "csum2-platver-full-2"]) + end + + it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "newfakeos" + node.automatic_attrs[:platform_version] = "2.0.rc.1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum2-platver-partial-1", "csum2-platver-partial-2"]) + end + + it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "maple tree" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum3-platver-full-1", "csum3-platver-full-2"]) + end + + it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") + expect(manifest_records).not_to be_nil + expect(manifest_records.size).to eq(2) + + checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] } + expect(checksums.sort).to eq(["csum4-platver-full-1", "csum4-platver-full-2"]) + end + end + + ## Globbing the relative paths out of the manifest records ## + + describe "when globbing for relative file paths based on filespecificity" do + it "should return a list of relative paths based on priority preference: host" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "examplehost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.host', 'anotherfile2.rb.host']) + end + + it "should return a list of relative paths based on priority preference: platform & full version" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) + end + + it "should return a list of relative paths based on priority preference: platform & partial version" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "newubuntu" + node.automatic_attrs[:platform_version] = "9.10" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.platform-partial-version', 'anotherfile2.rb.platform-partial-version']) + end + + it "should return a list of relative paths based on priority preference: platform only" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "ubuntu" + node.automatic_attrs[:platform_version] = "1.0" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.platform', 'anotherfile2.rb.platform']) + end + + it "should return a list of relative paths based on priority preference: default" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "notubuntu" + node.automatic_attrs[:platform_version] = "1.0" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.default', 'anotherfile2.rb.default']) + end + + it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 1" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "2.0.rc.1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) + end + + it "should return a list of relative paths based on priority preference: platform & partial version - platform_version variant 1" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "newfakeos" + node.automatic_attrs[:platform_version] = "2.0.rc.1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.platform-partial-version', 'anotherfile2.rb.platform-partial-version']) + end + + it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 2" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "maple tree" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) + end + + it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 3" do + node = Chef::Node.new + node.automatic_attrs[:platform] = "fakeos" + node.automatic_attrs[:platform_version] = "1" + node.automatic_attrs[:fqdn] = "differenthost.example.org" + + filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") + expect(filenames).not_to be_nil + expect(filenames.size).to eq(2) + + expect(filenames.sort).to eq(['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']) + end + end +end diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb index 6dd3429ffc..440dd9da6c 100644 --- a/spec/unit/cookbook_version_spec.rb +++ b/spec/unit/cookbook_version_spec.rb @@ -68,32 +68,10 @@ describe Chef::CookbookVersion do expect(@cookbook_version).to be_frozen_version end - it "is \"ready\"" do - # WTF is this? what are the valid states? and why aren't they set with encapsulating methods? - # [Dan 15-Jul-2010] - expect(@cookbook_version.status).to eq(:ready) - end - it "has empty metadata" do expect(@cookbook_version.metadata).to eq(Chef::Cookbook::Metadata.new) end - it "creates a manifest hash of its contents" do - expected = {"recipes"=>[], - "definitions"=>[], - "libraries"=>[], - "attributes"=>[], - "files"=>[], - "templates"=>[], - "resources"=>[], - "providers"=>[], - "root_files"=>[], - "cookbook_name"=>"tatft", - "metadata"=>Chef::Cookbook::Metadata.new, - "version"=>"0.0.0", - "name"=>"tatft-0.0.0"} - expect(@cookbook_version.manifest).to eq(expected) - end end describe "with a cookbook directory named tatft" do @@ -141,85 +119,6 @@ describe Chef::CookbookVersion do @node.name("testing") end - it "generates a manifest containing the cookbook's files" do - manifest = @cookbook_version.manifest - - expect(manifest["metadata"]).to eq(Chef::Cookbook::Metadata.new) - expect(manifest["cookbook_name"]).to eq("tatft") - - expect(manifest["recipes"].size).to eq(1) - - recipe = manifest["recipes"].first - expect(recipe["name"]).to eq("default.rb") - expect(recipe["path"]).to eq("recipes/default.rb") - expect(recipe["checksum"]).to match(MD5) - expect(recipe["specificity"]).to eq("default") - - expect(manifest["definitions"].size).to eq(1) - - definition = manifest["definitions"].first - expect(definition["name"]).to eq("runit_service.rb") - expect(definition["path"]).to eq("definitions/runit_service.rb") - expect(definition["checksum"]).to match(MD5) - expect(definition["specificity"]).to eq("default") - - expect(manifest["libraries"].size).to eq(1) - - library = manifest["libraries"].first - expect(library["name"]).to eq("ownage.rb") - expect(library["path"]).to eq("libraries/ownage.rb") - expect(library["checksum"]).to match(MD5) - expect(library["specificity"]).to eq("default") - - expect(manifest["attributes"].size).to eq(1) - - attribute_file = manifest["attributes"].first - expect(attribute_file["name"]).to eq("default.rb") - expect(attribute_file["path"]).to eq("attributes/default.rb") - expect(attribute_file["checksum"]).to match(MD5) - expect(attribute_file["specificity"]).to eq("default") - - expect(manifest["files"].size).to eq(1) - - cookbook_file = manifest["files"].first - expect(cookbook_file["name"]).to eq("giant_blob.tgz") - expect(cookbook_file["path"]).to eq("files/default/giant_blob.tgz") - expect(cookbook_file["checksum"]).to match(MD5) - expect(cookbook_file["specificity"]).to eq("default") - - expect(manifest["templates"].size).to eq(1) - - template = manifest["templates"].first - expect(template["name"]).to eq("configuration.erb") - expect(template["path"]).to eq("templates/default/configuration.erb") - expect(template["checksum"]).to match(MD5) - expect(template["specificity"]).to eq("default") - - expect(manifest["resources"].size).to eq(1) - - lwr = manifest["resources"].first - expect(lwr["name"]).to eq("lwr.rb") - expect(lwr["path"]).to eq("resources/lwr.rb") - expect(lwr["checksum"]).to match(MD5) - expect(lwr["specificity"]).to eq("default") - - expect(manifest["providers"].size).to eq(1) - - lwp = manifest["providers"].first - expect(lwp["name"]).to eq("lwp.rb") - expect(lwp["path"]).to eq("providers/lwp.rb") - expect(lwp["checksum"]).to match(MD5) - expect(lwp["specificity"]).to eq("default") - - expect(manifest["root_files"].size).to eq(1) - - readme = manifest["root_files"].first - expect(readme["name"]).to eq("README.rdoc") - expect(readme["path"]).to eq("README.rdoc") - expect(readme["checksum"]).to match(MD5) - expect(readme["specificity"]).to eq("default") - end - it "determines whether a template is available for a given node" do expect(@cookbook_version).to have_template_for_node(@node, "configuration.erb") expect(@cookbook_version).not_to have_template_for_node(@node, "missing.erb") @@ -253,102 +152,6 @@ describe Chef::CookbookVersion do end end - describe "and a cookbook_version with a different name" do - before do - # Currently the cookbook loader finds all the files then tells CookbookVersion - # where they are. - @cookbook_version = Chef::CookbookVersion.new("blarghle", @cookbook_root) - @cookbook_version.attribute_filenames = @cookbook[:attribute_filenames] - @cookbook_version.definition_filenames = @cookbook[:definition_filenames] - @cookbook_version.recipe_filenames = @cookbook[:recipe_filenames] - @cookbook_version.template_filenames = @cookbook[:template_filenames] - @cookbook_version.file_filenames = @cookbook[:file_filenames] - @cookbook_version.library_filenames = @cookbook[:library_filenames] - @cookbook_version.resource_filenames = @cookbook[:resource_filenames] - @cookbook_version.provider_filenames = @cookbook[:provider_filenames] - @cookbook_version.root_filenames = @cookbook[:root_filenames] - @cookbook_version.metadata_filenames = @cookbook[:metadata_filenames] - end - - it "generates a manifest containing the cookbook's files" do - manifest = @cookbook_version.manifest - - expect(manifest["metadata"]).to eq(Chef::Cookbook::Metadata.new) - expect(manifest["cookbook_name"]).to eq("blarghle") - - expect(manifest["recipes"].size).to eq(1) - - recipe = manifest["recipes"].first - expect(recipe["name"]).to eq("default.rb") - expect(recipe["path"]).to eq("recipes/default.rb") - expect(recipe["checksum"]).to match(MD5) - expect(recipe["specificity"]).to eq("default") - - expect(manifest["definitions"].size).to eq(1) - - definition = manifest["definitions"].first - expect(definition["name"]).to eq("runit_service.rb") - expect(definition["path"]).to eq("definitions/runit_service.rb") - expect(definition["checksum"]).to match(MD5) - expect(definition["specificity"]).to eq("default") - - expect(manifest["libraries"].size).to eq(1) - - library = manifest["libraries"].first - expect(library["name"]).to eq("ownage.rb") - expect(library["path"]).to eq("libraries/ownage.rb") - expect(library["checksum"]).to match(MD5) - expect(library["specificity"]).to eq("default") - - expect(manifest["attributes"].size).to eq(1) - - attribute_file = manifest["attributes"].first - expect(attribute_file["name"]).to eq("default.rb") - expect(attribute_file["path"]).to eq("attributes/default.rb") - expect(attribute_file["checksum"]).to match(MD5) - expect(attribute_file["specificity"]).to eq("default") - - expect(manifest["files"].size).to eq(1) - - cookbook_file = manifest["files"].first - expect(cookbook_file["name"]).to eq("giant_blob.tgz") - expect(cookbook_file["path"]).to eq("files/default/giant_blob.tgz") - expect(cookbook_file["checksum"]).to match(MD5) - expect(cookbook_file["specificity"]).to eq("default") - - expect(manifest["templates"].size).to eq(1) - - template = manifest["templates"].first - expect(template["name"]).to eq("configuration.erb") - expect(template["path"]).to eq("templates/default/configuration.erb") - expect(template["checksum"]).to match(MD5) - expect(template["specificity"]).to eq("default") - - expect(manifest["resources"].size).to eq(1) - - lwr = manifest["resources"].first - expect(lwr["name"]).to eq("lwr.rb") - expect(lwr["path"]).to eq("resources/lwr.rb") - expect(lwr["checksum"]).to match(MD5) - expect(lwr["specificity"]).to eq("default") - - expect(manifest["providers"].size).to eq(1) - - lwp = manifest["providers"].first - expect(lwp["name"]).to eq("lwp.rb") - expect(lwp["path"]).to eq("providers/lwp.rb") - expect(lwp["checksum"]).to match(MD5) - expect(lwp["specificity"]).to eq("default") - - expect(manifest["root_files"].size).to eq(1) - - readme = manifest["root_files"].first - expect(readme["name"]).to eq("README.rdoc") - expect(readme["path"]).to eq("README.rdoc") - expect(readme["checksum"]).to match(MD5) - expect(readme["specificity"]).to eq("default") - end - end end describe 'with a cookbook directory named cookbook2 that has unscoped files' do @@ -499,8 +302,63 @@ describe Chef::CookbookVersion do end - include_examples "to_json equalivent to Chef::JSONCompat.to_json" do - let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') } + describe "when deprecation warnings are errors" do + + subject(:cbv) { Chef::CookbookVersion.new("version validation", '/tmp/blah') } + + describe "HTTP Resource behaviors", pending: "will be deprected when CookbookManifest API is stablized" do + + it "errors on #save_url" do + expect { cbv.save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + it "errors on #force_save_url" do + expect { cbv.force_save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + it "errors on #to_hash" do + expect { cbv.to_hash }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + it "errors on #to_json" do + expect { cbv.to_json }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + + end + + it "errors on #status and #status=" do + expect { cbv.status = :wat }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + expect { cbv.status }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) + end + end + describe "deprecated features" do + + subject(:cbv) { Chef::CookbookVersion.new("tatft", '/tmp/blah').tap { |c| c.version = "1.2.3" } } + + before do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + end + + it "gives a save URL for the standard cookbook API" do + expect(cbv.save_url).to eq("cookbooks/tatft/1.2.3") + end + + it "gives a force save URL for the standard cookbook API" do + expect(cbv.force_save_url).to eq("cookbooks/tatft/1.2.3?force=true") + end + + it "is \"ready\"" do + # WTF is this? what are the valid states? and why aren't they set with encapsulating methods? + # [Dan 15-Jul-2010] + expect(cbv.status).to eq(:ready) + end + + + include_examples "to_json equalivent to Chef::JSONCompat.to_json" do + let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') } + end + + end end |