diff options
author | Jay Mundrawala <jdmundrawala@gmail.com> | 2015-01-13 09:58:11 -0800 |
---|---|---|
committer | Jay Mundrawala <jdmundrawala@gmail.com> | 2015-01-13 09:58:11 -0800 |
commit | af5d79e766c2f8968ea29031eaf0e52154c79755 (patch) | |
tree | 007a0e1515faaeec46f6bfa0a9b063470c54b836 | |
parent | 65e3d0e51601c33684f7d761a391d0c019e96098 (diff) | |
parent | eff465b78989ebdd0acebf5e06373c9e82897c30 (diff) | |
download | chef-af5d79e766c2f8968ea29031eaf0e52154c79755.tar.gz |
Merge pull request #2748 from opscode/jdm/2345-11-backport
Allow knife to install cookbooks with metadata.json
-rw-r--r-- | lib/chef/exceptions.rb | 7 | ||||
-rw-r--r-- | lib/chef/knife/cookbook_site_install.rb | 44 | ||||
-rw-r--r-- | spec/unit/knife/cookbook_site_install_spec.rb | 273 |
3 files changed, 198 insertions, 126 deletions
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 2e7a92ab6f..08541441ba 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -134,6 +134,13 @@ class Chef # Version constraints are not allowed in chef-solo class IllegalVersionConstraint < NotImplementedError; end + class MetadataNotValid < StandardError; end + class MetadataNotFound < StandardError + def initialize + super "No metadata.rb or metadata.json!" + end + end + # File operation attempted but no permissions to perform it class InsufficientPermissions < RuntimeError; end diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb index 913d171b3c..edf8dd14f0 100644 --- a/lib/chef/knife/cookbook_site_install.rb +++ b/lib/chef/knife/cookbook_site_install.rb @@ -17,11 +17,11 @@ # require 'chef/knife' +require 'chef/exceptions' require 'shellwords' class Chef class Knife - class CookbookSiteInstall < Knife deps do @@ -107,11 +107,8 @@ class Chef end end - unless config[:no_deps] - md = Chef::Cookbook::Metadata.new - md.from_file(File.join(@install_path, @cookbook_name, "metadata.rb")) - md.dependencies.each do |cookbook, version_list| + preferred_metadata.dependencies.each do |cookbook, version_list| # Doesn't do versions.. yet nv = self.class.new nv.config = config @@ -144,6 +141,7 @@ class Chef def extract_cookbook(upstream_file, version) ui.info("Uncompressing #{@cookbook_name} version #{version}.") + # FIXME: Detect if we have the bad tar from git on Windows: https://github.com/opscode/chef/issues/1753 shell_out!("tar zxvf #{convert_path upstream_file}", :cwd => @install_path) end @@ -153,11 +151,37 @@ class Chef end def convert_path(upstream_file) - if ENV['MSYSTEM'] == 'MINGW32' - return upstream_file.sub(/^([[:alpha:]]):/, '/\1') - else - return Shellwords.escape upstream_file - end + # converts a Windows path (C:\foo) to a mingw path (/c/foo) + if ENV['MSYSTEM'] == 'MINGW32' + return upstream_file.sub(/^([[:alpha:]]):/, '/\1') + else + return Shellwords.escape upstream_file + end + end + + # Get the preferred metadata path on disk. Chef prefers the metadata.rb + # over the metadata.json. + # + # @raise if there is no metadata in the cookbook + # + # @return [Chef::Cookbook::Metadata] + def preferred_metadata + md = Chef::Cookbook::Metadata.new + + rb = File.join(@install_path, @cookbook_name, "metadata.rb") + if File.exist?(rb) + md.from_file(rb) + return md + end + + json = File.join(@install_path, @cookbook_name, "metadata.json") + if File.exist?(json) + json = IO.read(json) + md.from_json(json) + return md + end + + raise Chef::Exceptions::MetadataNotFound end end end diff --git a/spec/unit/knife/cookbook_site_install_spec.rb b/spec/unit/knife/cookbook_site_install_spec.rb index ff87a81b49..b3eef32b39 100644 --- a/spec/unit/knife/cookbook_site_install_spec.rb +++ b/spec/unit/knife/cookbook_site_install_spec.rb @@ -19,132 +19,173 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) describe Chef::Knife::CookbookSiteInstall do + let(:knife) { Chef::Knife::CookbookSiteInstall.new } + let(:stdout) { StringIO.new } + let(:stderr) { StringIO.new } + let(:downloader) { Hash.new } + let(:repo) { double(:sanity_check => true, :reset_to_default_state => true, + :prepare_to_import => true, :finalize_updates_to => true, + :merge_updates_from => true) } + let(:install_path) { if Chef::Platform.windows? + 'C:/tmp/chef' + else + '/var/tmp/chef' + end } + before(:each) do require 'chef/knife/core/cookbook_scm_repo' - @stdout = StringIO.new - @knife = Chef::Knife::CookbookSiteInstall.new - @knife.ui.stub(:stdout).and_return(@stdout) - @knife.config = {} - if Chef::Platform.windows? - @install_path = 'C:/tmp/chef' - else - @install_path = '/var/tmp/chef' - end - @knife.config[:cookbook_path] = [ @install_path ] - - @stdout = StringIO.new - @stderr = StringIO.new - @knife.stub(:stderr).and_return(@stdout) - @knife.stub(:stdout).and_return(@stdout) - - #Assume all external commands would have succeed. :( - File.stub(:unlink) - File.stub(:rmtree) - @knife.stub(:shell_out!).and_return(true) - - #CookbookSiteDownload Stup - @downloader = {} - @knife.stub(:download_cookbook_to).and_return(@downloader) - @downloader.stub(:version).and_return do - if @knife.name_args.size == 2 - @knife.name_args[1] + + allow(knife.ui).to receive(:stdout).and_return(stdout) + knife.config = {} + knife.config[:cookbook_path] = [ install_path ] + + allow(knife).to receive(:stderr).and_return(stderr) + allow(knife).to receive(:stdout).and_return(stdout) + + # Assume all external commands would have succeed. :( + allow(File).to receive(:unlink) + allow(File).to receive(:rmtree) + allow(knife).to receive(:shell_out!).and_return(true) + + # CookbookSiteDownload Stup + allow(knife).to receive(:download_cookbook_to).and_return(downloader) + allow(downloader).to receive(:version) do + if knife.name_args.size == 2 + knife.name_args[1] else "0.3.0" end end - #Stubs for CookbookSCMRepo - @repo = double(:sanity_check => true, :reset_to_default_state => true, - :prepare_to_import => true, :finalize_updates_to => true, - :merge_updates_from => true) - Chef::Knife::CookbookSCMRepo.stub(:new).and_return(@repo) + # Stubs for CookbookSCMRepo + allow(Chef::Knife::CookbookSCMRepo).to receive(:new).and_return(repo) end - describe "run" do - it "should return an error if a cookbook name is not provided" do - @knife.name_args = [] - @knife.ui.should_receive(:error).with("Please specify a cookbook to download and install.") - lambda { @knife.run }.should raise_error(SystemExit) - end - - it "should return an error if more than two arguments are given" do - @knife.name_args = ["foo", "bar", "baz"] - @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") - lambda { @knife.run }.should raise_error(SystemExit) - end - - it "should return an error if the second argument is not a version" do - @knife.name_args = ["getting-started", "1pass"] - @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") - lambda { @knife.run }.should raise_error(SystemExit) - end - - it "should return an error if the second argument is a four-digit version" do - @knife.name_args = ["getting-started", "0.0.0.1"] - @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") - lambda { @knife.run }.should raise_error(SystemExit) - end - - it "should return an error if the second argument is a one-digit version" do - @knife.name_args = ["getting-started", "1"] - @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") - lambda { @knife.run }.should raise_error(SystemExit) - end - - it "should install the specified version if second argument is a three-digit version" do - @knife.name_args = ["getting-started", "0.1.0"] - @knife.config[:no_deps] = true - upstream_file = File.join(@install_path, "getting-started.tar.gz") - @knife.should_receive(:download_cookbook_to).with(upstream_file) - @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1.0") - @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started")) - @repo.should_receive(:merge_updates_from).with("getting-started", "0.1.0") - @knife.run - end - - it "should install the specified version if second argument is a two-digit version" do - @knife.name_args = ["getting-started", "0.1"] - @knife.config[:no_deps] = true - upstream_file = File.join(@install_path, "getting-started.tar.gz") - @knife.should_receive(:download_cookbook_to).with(upstream_file) - @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1") - @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started")) - @repo.should_receive(:merge_updates_from).with("getting-started", "0.1") - @knife.run - end - - it "should install the latest version if only a cookbook name is given" do - @knife.name_args = ["getting-started"] - @knife.config[:no_deps] = true - upstream_file = File.join(@install_path, "getting-started.tar.gz") - @knife.should_receive(:download_cookbook_to).with(upstream_file) - @knife.should_receive(:extract_cookbook).with(upstream_file, "0.3.0") - @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started")) - @repo.should_receive(:merge_updates_from).with("getting-started", "0.3.0") - @knife.run - end - - it "should not create/reset git branches if use_current_branch is set" do - @knife.name_args = ["getting-started"] - @knife.config[:use_current_branch] = true - @knife.config[:no_deps] = true - upstream_file = File.join(@install_path, "getting-started.tar.gz") - @repo.should_not_receive(:prepare_to_import) - @repo.should_not_receive(:reset_to_default_state) - @knife.run - end - - it "should not raise an error if cookbook_path is a string" do - @knife.config[:cookbook_path] = @install_path - @knife.config[:no_deps] = true - @knife.name_args = ["getting-started"] - upstream_file = File.join(@install_path, "getting-started.tar.gz") - @knife.should_receive(:download_cookbook_to).with(upstream_file) - @knife.should_receive(:extract_cookbook).with(upstream_file, "0.3.0") - @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started")) - @repo.should_receive(:merge_updates_from).with("getting-started", "0.3.0") - lambda { @knife.run }.should_not raise_error + it "raises an error if a cookbook name is not provided" do + knife.name_args = [] + expect(knife.ui).to receive(:error).with("Please specify a cookbook to download and install.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if more than two arguments are given" do + knife.name_args = ["foo", "bar", "baz"] + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if the second argument is not a version" do + knife.name_args = ["getting-started", "1pass"] + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if the second argument is a four-digit version" do + knife.name_args = ["getting-started", "0.0.0.1"] + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "raises an error if the second argument is a one-digit version" do + knife.name_args = ["getting-started", "1"] + expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") + expect { knife.run }.to raise_error(SystemExit) + end + + it "installs the specified version if second argument is a three-digit version" do + knife.name_args = ["getting-started", "0.1.0"] + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1.0") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1.0") + knife.run + end + + it "installs the specified version if second argument is a two-digit version" do + knife.name_args = ["getting-started", "0.1"] + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1") + knife.run + end + + it "installs the latest version if only a cookbook name is given" do + knife.name_args = ["getting-started"] + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0") + knife.run + end + + it "does not create/reset git branches if use_current_branch is set" do + knife.name_args = ["getting-started"] + knife.config[:use_current_branch] = true + knife.config[:no_deps] = true + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(repo).not_to receive(:prepare_to_import) + expect(repo).not_to receive(:reset_to_default_state) + knife.run end + + it "does not raise an error if cookbook_path is a string" do + knife.config[:cookbook_path] = install_path + knife.config[:no_deps] = true + knife.name_args = ["getting-started"] + upstream_file = File.join(install_path, "getting-started.tar.gz") + expect(knife).to receive(:download_cookbook_to).with(upstream_file) + expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0") + expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) + expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0") + expect { knife.run }.not_to raise_error + end + end # end of run + + let(:metadata) { Chef::Cookbook::Metadata.new } + let(:rb_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.rb") } + let(:json_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.json") } + + describe "preferred_metadata" do + before do + allow(Chef::Cookbook::Metadata).to receive(:new).and_return(metadata) + allow(File).to receive(:exist?).and_return(false) + knife.instance_variable_set(:@cookbook_name, "post-punk-kitchen") + knife.instance_variable_set(:@install_path, install_path) + end + + it "returns a populated Metadata object if metadata.rb exists" do + allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true) + expect(metadata).to receive(:from_file).with(rb_metadata_path) + knife.preferred_metadata + end + + it "returns a populated Metadata object if metadata.json exists" do + allow(File).to receive(:exist?).with(json_metadata_path).and_return(true) + #expect(IO).to receive(:read).with(json_metadata_path) + allow(IO).to receive(:read) + expect(metadata).to receive(:from_json) + knife.preferred_metadata + end + + it "prefers metadata.rb over metadata.json" do + allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true) + allow(File).to receive(:exist?).with(json_metadata_path).and_return(true) + allow(IO).to receive(:read) + expect(metadata).to receive(:from_file).with(rb_metadata_path) + expect(metadata).not_to receive(:from_json) + knife.preferred_metadata + end + + it "rasies an error if it finds no metadata file" do + expect { knife.preferred_metadata }.to raise_error(Chef::Exceptions::MetadataNotFound) + end + end end |