# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # 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::Knife::CookbookDownload do before(:each) do @knife = Chef::Knife::CookbookDownload.new @stderr = StringIO.new allow(@knife.ui).to receive(:stderr).and_return(@stderr) end describe "run" do it "should print usage and exit when a cookbook name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal).with(/must specify a cookbook name/) expect { @knife.run }.to raise_error(SystemExit) end it "should exit with a fatal error when there is no cookbook on the server" do @knife.name_args = ["foobar", nil] expect(@knife).to receive(:determine_version).and_return(nil) expect(@knife.ui).to receive(:fatal).with("No such cookbook found") expect { @knife.run }.to raise_error(SystemExit) end describe "with a cookbook name" do before(:each) do @knife.name_args = ["foobar"] @knife.config[:download_directory] = "/var/tmp/chef" @rest_mock = double("rest") allow(@knife).to receive(:rest).and_return(@rest_mock) @manifest_data = { :recipes => [ {"path" => "recipes/foo.rb", "url" => "http://example.org/files/foo.rb"}, {"path" => "recipes/bar.rb", "url" => "http://example.org/files/bar.rb"}, ], :templates => [ {"path" => "templates/default/foo.erb", "url" => "http://example.org/files/foo.erb"}, {"path" => "templates/default/bar.erb", "url" => "http://example.org/files/bar.erb"}, ], :attributes => [ {"path" => "attributes/default.rb", "url" => "http://example.org/files/default.rb"}, ], } @cookbook_mock = double("cookbook") allow(@cookbook_mock).to receive(:version).and_return("1.0.0") allow(@cookbook_mock).to receive(:manifest).and_return(@manifest_data) expect(Chef::CookbookVersion).to receive(:load).with("foobar", "1.0.0"). and_return(@cookbook_mock) end it "should determine which version if one was not explicitly specified"do allow(@cookbook_mock).to receive(:manifest).and_return({}) expect(@knife).to receive(:determine_version).and_return("1.0.0") expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) allow(Chef::CookbookVersion).to receive(:COOKBOOK_SEGEMENTS).and_return([]) @knife.run end describe "and a version" do before(:each) do @knife.name_args << "1.0.0" @files = @manifest_data.values.map { |v| v.map { |i| i["path"] } }.flatten.uniq @files_mocks = {} @files.map { |f| File.basename(f) }.flatten.uniq.each do |f| @files_mocks[f] = double("#{f}_mock") allow(@files_mocks[f]).to receive(:path).and_return("/var/tmp/#{f}") end end it "should print an error and exit if the cookbook download directory already exists" do expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(true) expect(@knife.ui).to receive(:fatal).with(/\/var\/tmp\/chef\/foobar-1\.0\.0 exists/i) expect { @knife.run }.to raise_error(SystemExit) end describe "when downloading the cookbook" do before(:each) do @files.map { |f| File.dirname(f) }.flatten.uniq.each do |dir| expect(FileUtils).to receive(:mkdir_p).with("/var/tmp/chef/foobar-1.0.0/#{dir}"). at_least(:once) end @files_mocks.each_pair do |file, mock| expect(@rest_mock).to receive(:streaming_request).with("http://example.org/files/#{file}"). and_return(mock) end @files.each do |f| expect(FileUtils).to receive(:mv). with("/var/tmp/#{File.basename(f)}", "/var/tmp/chef/foobar-1.0.0/#{f}") end end it "should download the cookbook when the cookbook download directory doesn't exist" do expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) @knife.run ["attributes", "recipes", "templates"].each do |segment| expect(@stderr.string).to match /downloading #{segment}/im end expect(@stderr.string).to match /downloading foobar cookbook version 1\.0\.0/im expect(@stderr.string).to match /cookbook downloaded to \/var\/tmp\/chef\/foobar-1\.0\.0/im end describe "with -f or --force" do it "should remove the existing the cookbook download directory if it exists" do @knife.config[:force] = true expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(true) expect(FileUtils).to receive(:rm_rf).with("/var/tmp/chef/foobar-1.0.0") @knife.run end end end end end end describe "determine_version" do it "should return nil if there are no versions" do expect(@knife).to receive(:available_versions).and_return(nil) expect(@knife.determine_version).to eq(nil) expect(@knife.version).to eq(nil) end it "should return and set the version if there is only one version" do expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"]) expect(@knife.determine_version).to eq("1.0.0") expect(@knife.version).to eq("1.0.0") end it "should ask which version to download and return it if there is more than one" do expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "2.0.0"]) expect(@knife).to receive(:ask_which_version).and_return("1.0.0") expect(@knife.determine_version).to eq("1.0.0") end describe "with -N or --latest" do it "should return and set the version to the latest version" do @knife.config[:latest] = true expect(@knife).to receive(:available_versions).at_least(:once). and_return(["1.0.0", "1.1.0", "2.0.0"]) @knife.determine_version expect(@knife.version.to_s).to eq("2.0.0") end end end describe "available_versions" do before(:each) do @knife.cookbook_name = "foobar" end it "should return nil if there are no versions" do expect(Chef::CookbookVersion).to receive(:available_versions). with("foobar"). and_return(nil) expect(@knife.available_versions).to eq(nil) end it "should return the available versions" do expect(Chef::CookbookVersion).to receive(:available_versions). with("foobar"). and_return(["1.1.0", "2.0.0", "1.0.0"]) expect(@knife.available_versions).to eq([Chef::Version.new("1.0.0"), Chef::Version.new("1.1.0"), Chef::Version.new("2.0.0")]) end it "should avoid multiple API calls to the server" do expect(Chef::CookbookVersion).to receive(:available_versions). once. with("foobar"). and_return(["1.1.0", "2.0.0", "1.0.0"]) @knife.available_versions @knife.available_versions end end describe "ask_which_version" do before(:each) do @knife.cookbook_name = "foobar" allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"]) end it "should prompt the user to select a version" do prompt = /Which version do you want to download\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+/m expect(@knife).to receive(:ask_question).with(prompt).and_return("1") @knife.ask_which_version end it "should set the version to the user's selection" do expect(@knife).to receive(:ask_question).and_return("1") @knife.ask_which_version expect(@knife.version).to eq("1.0.0") end it "should print an error and exit if a version wasn't specified" do expect(@knife).to receive(:ask_question).and_return("") expect(@knife.ui).to receive(:error).with(/is not a valid value/i) expect { @knife.ask_which_version }.to raise_error(SystemExit) end it "should print an error if an invalid choice was selected" do expect(@knife).to receive(:ask_question).and_return("100") expect(@knife.ui).to receive(:error).with(/'100' is not a valid value/i) expect { @knife.ask_which_version }.to raise_error(SystemExit) end end end