# # Author:: Daniel DeLeo () # Copyright:: Copyright (c) 2014 Chef Software, 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::Cookbook::CookbookVersionLoader do before do allow(Chef::Platform).to receive(:windows?) { false } end describe "loading a cookbook" do let(:chefignore) { nil } let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks/openldap") } let(:cookbook_loader) { Chef::Cookbook::CookbookVersionLoader.new(cookbook_path, chefignore) } let(:loaded_cookbook) do cookbook_loader.load! cookbook_loader.cookbook_version end def full_path(cookbook_relative_path) File.join(cookbook_path, cookbook_relative_path) end it "loads attribute files of the cookbook" do expect(loaded_cookbook.attribute_filenames).to include(full_path("/attributes/default.rb")) expect(loaded_cookbook.attribute_filenames).to include(full_path("/attributes/smokey.rb")) end it "loads definition files" do expect(loaded_cookbook.definition_filenames).to include(full_path("/definitions/client.rb")) expect(loaded_cookbook.definition_filenames).to include(full_path("/definitions/server.rb")) end it "loads recipes" do expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/default.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/gigantor.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/one.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/return.rb")) end it "loads libraries" do expect(loaded_cookbook.library_filenames).to include(full_path('/libraries/openldap.rb')) expect(loaded_cookbook.library_filenames).to include(full_path('/libraries/openldap/version.rb')) end it "loads static files in the files/ dir" do expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file1.txt")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file2.txt")) end it "loads files that start with a ." do expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/.dotfile")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/.ssh/id_rsa")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir")) end it "should load the metadata for the cookbook" do expect(loaded_cookbook.metadata.name.to_s).to eq("openldap") expect(loaded_cookbook.metadata).to be_a_kind_of(Chef::Cookbook::Metadata) end context "when a cookbook has ignored files" do let(:chefignore) { Chef::Cookbook::Chefignore.new(File.join(CHEF_SPEC_DATA, "cookbooks")) } let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "kitchen/openldap") } it "skips ignored files" do expect(loaded_cookbook.recipe_filenames).to include(full_path("recipes/gigantor.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("recipes/woot.rb")) expect(loaded_cookbook.recipe_filenames).to_not include(full_path("recipes/ignoreme.rb")) end end context "when the given path is not actually a cookbook" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks/NOTHING_HERE_FOLKS") } it "raises an error when loading with #load!" do expect { cookbook_loader.load! }.to raise_error(Chef::Exceptions::CookbookNotFoundInRepo) end it "skips the cookbook when called with #load" do expect { cookbook_loader.load }.to_not raise_error end end context "when a cookbook has a metadata name different than directory basename" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks/name-mismatch-versionnumber") } it "prefers the metadata name to the directory basename" do expect(loaded_cookbook.name).to eq(:"name-mismatch") end end context "when a cookbook has a metadata file with a ruby error [CHEF-2923]" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "invalid-metadata-chef-repo/invalid-metadata") } it "raises an error when loading with #load!" do expect { cookbook_loader.load! }.to raise_error("THIS METADATA HAS A BUG") end it "raises an error when called with #load" do expect { cookbook_loader.load }.to raise_error("THIS METADATA HAS A BUG") end it "doesn't raise an error when determining the cookbook name" do expect { cookbook_loader.cookbook_name }.to_not raise_error end it "doesn't raise an error when metadata is first generated" do expect { cookbook_loader.metadata }.to_not raise_error end it "sets an error flag containing error information" do cookbook_loader.metadata expect(cookbook_loader.metadata_error).to be_a(StandardError) expect(cookbook_loader.metadata_error.message).to eq("THIS METADATA HAS A BUG") end end context "when a cookbook has a metadata file with invalid metadata" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "incomplete-metadata-chef-repo/incomplete-metadata") } let(:error_message) do "Cookbook loaded at path(s) [#{cookbook_path}] has invalid metadata: The `name' attribute is required in cookbook metadata" end it "raises an error when loading with #load!" do expect { cookbook_loader.load! }.to raise_error(Chef::Exceptions::MetadataNotValid, error_message) end it "raises an error when called with #load" do expect { cookbook_loader.load }.to raise_error(Chef::Exceptions::MetadataNotValid, error_message) end it "uses the inferred cookbook name [CHEF-2923]" do # This behavior is intended to support the CHEF-2923 feature where # invalid metadata doesn't prevent you from uploading other cookbooks. # # The metadata is the definitive source of the cookbook name, but if # the metadata is incomplete/invalid, we can't read the name from it. # # The CookbookLoader stores CookbookVersionLoaders in a Hash with # cookbook names as the keys, and finds the loader in this Hash to call # #load on it when the user runs a command like `knife cookbook upload specific-cookbook` # # Most of the time this will work, but if the user tries to upload a # specific cookbook by name, has customized that cookbook's name (so it # doesn't match the inferred name), and that metadata file has a syntax # error, we might report a "cookbook not found" error instead of the # metadata syntax error that is the actual cause. expect(cookbook_loader.cookbook_name).to eq(:"incomplete-metadata") end end end end