# encoding: UTF-8 # # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-2018, 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" require "webrick" module AptServer def enable_testing_apt_source File.open("/etc/apt/sources.list.d/chef-integration-test.list", "w+") do |f| f.puts "deb [trusted=yes] http://localhost:9000/ sid main" end # Magic to update apt cache for only our repo shell_out!("apt-get update " + '-o Dir::Etc::sourcelist="sources.list.d/chef-integration-test.list" ' + '-o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"') end def disable_testing_apt_source FileUtils.rm("/etc/apt/sources.list.d/chef-integration-test.list") rescue Errno::ENOENT puts("Attempted to remove integration test from /etc/apt/sources.list.d but it didn't exist") end def tcp_test_port(hostname, port) tcp_socket = TCPSocket.new(hostname, port) true rescue Errno::ETIMEDOUT false rescue Errno::ECONNREFUSED false ensure tcp_socket && tcp_socket.close end def apt_server @apt_server ||= WEBrick::HTTPServer.new( :Port => 9000, :DocumentRoot => apt_data_dir + "/var/www/apt", # Make WEBrick quiet, comment out for debug. :Logger => Logger.new(StringIO.new), :AccessLog => [ StringIO.new, WEBrick::AccessLog::COMMON_LOG_FORMAT ] ) end def run_apt_server apt_server.start end def start_apt_server @apt_server_thread = Thread.new do run_apt_server end until tcp_test_port("localhost", 9000) if @apt_server_thread.alive? sleep 1 else @apt_server_thread.join raise "apt server failed to start" end end end def stop_apt_server apt_server.shutdown @apt_server_thread.join end def apt_data_dir File.join(CHEF_SPEC_DATA, "apt") end end metadata = { :unix_only => true, :requires_root => true, :provider => { :package => Chef::Provider::Package::Apt }, :arch => "x86_64" # test packages are 64bit } describe Chef::Resource::AptPackage, metadata do include Chef::Mixin::ShellOut context "with a remote package source" do include AptServer before(:all) do # Disable mixlib-shellout live streams Chef::Log.level = :warn start_apt_server enable_testing_apt_source end after(:all) do stop_apt_server disable_testing_apt_source shell_out!("apt-get clean") end after do shell_out!("dpkg -r chef-integration-test") shell_out("dpkg --clear-avail") shell_out!("apt-get clean") end let(:node) do n = Chef::Node.new n.consume_external_attrs(OHAI_SYSTEM.data.dup, {}) n end let(:events) do Chef::EventDispatch::Dispatcher.new end # TODO: lots of duplication from client.rb; # All of this must be setup for preseed files to get found let(:cookbook_collection) do cookbook_path = File.join(CHEF_SPEC_DATA, "cookbooks") cl = Chef::CookbookLoader.new(cookbook_path) cl.load_cookbooks Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_path) Chef::CookbookCollection.new(cl) end let(:run_context) do Chef::RunContext.new(node, cookbook_collection, events) end def base_resource r = Chef::Resource::AptPackage.new("chef-integration-test", run_context) # The apt repository in the spec data is not gpg signed, so we need to # force apt to accept the package: r.options("--force-yes") r end let(:package_resource) do base_resource end context "when the package is not yet installed" do it "installs the package with action :install" do package_resource.run_action(:install) shell_out!("dpkg -l chef-integration-test") expect(package_resource).to be_updated_by_last_action end it "installs the package for action :upgrade" do package_resource.run_action(:upgrade) shell_out!("dpkg -l chef-integration-test") expect(package_resource).to be_updated_by_last_action end it "does nothing for action :remove" do package_resource.run_action(:remove) shell_out!("dpkg -l chef-integration-test", :returns => [1]) expect(package_resource).not_to be_updated_by_last_action end it "does nothing for action :purge" do package_resource.run_action(:purge) shell_out!("dpkg -l chef-integration-test", :returns => [1]) expect(package_resource).not_to be_updated_by_last_action end context "and a not-available package version is specified" do let(:package_resource) do r = base_resource r.version("2.0") r end it "raises a reasonable error for action :install" do expect do package_resource.run_action(:install) end.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end end describe "when preseeding the install" do let(:file_cache_path) { Dir.mktmpdir } before do Chef::Config[:file_cache_path] = file_cache_path debconf_reset = 'chef-integration-test chef-integration-test/sample-var string "INVALID"' shell_out!("echo #{debconf_reset} |debconf-set-selections") end after do FileUtils.rm_rf(file_cache_path) end context "with a preseed file" do let(:package_resource) do r = base_resource r.cookbook_name = "preseed" r.response_file("preseed-file.seed") r end it "preseeds the package, then installs it" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include('chef-integration-test/sample-var: "hello world"') expect(package_resource).to be_updated_by_last_action end context "and the preseed file exists and is up-to-date" do before do # Code here is duplicated from the implementation. Not great, but # it should at least fail if the code gets out of sync. source = File.join(CHEF_SPEC_DATA, "cookbooks/preseed/files/default/preseed-file.seed") file_cache_dir = Chef::FileCache.create_cache_path("preseed/preseed") dest = "#{file_cache_dir}/chef-integration-test-1.1-1.seed" FileUtils.cp(source, dest) end it "does not update the package configuration" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include("chef-integration-test/sample-var: INVALID") expect(package_resource).to be_updated_by_last_action end end end context "with a preseed template" do # NOTE: in the fixtures, there is also a cookbook_file named # "preseed-template.seed". This implicitly tests that templates are # preferred over cookbook_files when both are present. let(:package_resource) do r = base_resource r.cookbook_name = "preseed" r.response_file("preseed-template.seed") r end before do node.normal[:preseed_value] = "FROM TEMPLATE" end it "preseeds the package, then installs it" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include('chef-integration-test/sample-var: "FROM TEMPLATE"') expect(package_resource).to be_updated_by_last_action end context "with variables" do let(:package_resource) do r = base_resource r.cookbook_name = "preseed" r.response_file("preseed-template-variables.seed") r.response_file_variables({ :template_variable => "SUPPORTS VARIABLES" }) r end it "preseeds the package, then installs it" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include('chef-integration-test/sample-var: "SUPPORTS VARIABLES"') expect(package_resource).to be_updated_by_last_action end end end end # installing w/ preseed end # when package not installed context "and the desired version of the package is installed" do before do v_1_1_package = File.expand_path("apt/chef-integration-test_1.1-1_amd64.deb", CHEF_SPEC_DATA) shell_out!("dpkg -i #{v_1_1_package}") end it "does nothing for action :install" do package_resource.run_action(:install) shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(package_resource).not_to be_updated_by_last_action end it "does nothing for action :upgrade" do package_resource.run_action(:upgrade) shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(package_resource).not_to be_updated_by_last_action end # Verify that the package is removed by running `dpkg -l PACKAGE` # On Ubuntu 12.10 and newer, the command exits 1. # # On Ubuntu 12.04 and older, the `dpkg -l` command will exit 0 and # display a package status message like this: # # Desired=Unknown/Install/Remove/Purge/Hold # | Status=Not/Inst/Cfg-files/Unpacked/Failed-cfg/Half-inst/trig-aWait/Trig-pend # |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) # ||/ Name Version Description # +++-=================================-=========================================-============================================ # un chef-integration-test (no description available) def pkg_should_be_removed # will raise if exit code != 0,1 pkg_check = shell_out!("dpkg -l chef-integration-test", :returns => [0, 1]) if pkg_check.exitstatus == 0 expect(pkg_check.stdout).to match(/un[\s]+chef-integration-test/) end end it "removes the package for action :remove" do package_resource.run_action(:remove) pkg_should_be_removed expect(package_resource).to be_updated_by_last_action end it "removes the package for action :purge" do package_resource.run_action(:purge) pkg_should_be_removed expect(package_resource).to be_updated_by_last_action end end context "and an older version of the package is installed" do before do v_1_0_package = File.expand_path("apt/chef-integration-test_1.0-1_amd64.deb", CHEF_SPEC_DATA) shell_out!("dpkg -i #{v_1_0_package}") end it "does nothing for action :install" do package_resource.run_action(:install) shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(package_resource).not_to be_updated_by_last_action end it "upgrades the package for action :upgrade" do package_resource.run_action(:upgrade) dpkg_l = shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(dpkg_l.stdout).to match(/chef\-integration\-test[\s]+1\.1\-1/) expect(package_resource).to be_updated_by_last_action end context "and the resource specifies the new version" do let(:package_resource) do r = base_resource r.version("1.1-1") r end it "upgrades the package for action :install" do package_resource.run_action(:install) dpkg_l = shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(dpkg_l.stdout).to match(/chef\-integration\-test[\s]+1\.1\-1/) expect(package_resource).to be_updated_by_last_action end end end end end