# # Author:: Joshua Timberman () # Author:: Daniel DeLeo () # Copyright:: Copyright (c) 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::Provider::Package::Rpm do let(:provider) { Chef::Provider::Package::Rpm.new(new_resource, run_context) } let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:package_source) { "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } let(:package_name) { "ImageMagick-c++" } let(:new_resource) do Chef::Resource::RpmPackage.new(package_name).tap do |resource| resource.source(package_source) end end # `rpm -qp [stuff] $source` let(:rpm_qp_status) { instance_double("Mixlib::ShellOut", exitstatus: rpm_qp_exitstatus, stdout: rpm_qp_stdout) } # `rpm -q [stuff] $package_name` let(:rpm_q_status) { instance_double("Mixlib::ShellOut", exitstatus: rpm_q_exitstatus, stdout: rpm_q_stdout) } before(:each) do allow(::File).to receive(:exist?).with("PLEASE STUB File.exists? EXACTLY").and_return(true) # Ensure all shell out usage is stubbed with exact arguments allow(provider).to receive(:shell_out_compacted!).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil) allow(provider).to receive(:shell_out_compacted).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil) end describe "when the package source is not valid" do context "when source is not defined" do let(:new_resource) { Chef::Resource::RpmPackage.new("ImageMagick-c++") } it "should raise an exception when attempting any action" do expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end end context "when the source is a file that doesn't exist" do it "should raise an exception when attempting any action" do allow(::File).to receive(:exist?).with(package_source).and_return(false) expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end end context "when the source is an unsupported URI scheme" do let(:package_source) { "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } it "should raise an exception if an uri formed source is non-supported scheme" do allow(::File).to receive(:exist?).with(package_source).and_return(false) # verify let bindings are as we expect expect(new_resource.source).to eq("foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") expect(provider.load_current_resource).to be_nil expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end end end describe "when the package source is valid" do before do expect(provider).to receive(:shell_out_compacted!) .with("rpm", "-qp", "--queryformat", "%{NAME} %{VERSION}-%{RELEASE}\n", package_source, timeout: 900) .and_return(rpm_qp_status) expect(provider).to receive(:shell_out_compacted) .with("rpm", "-q", "--queryformat", "%{NAME} %{VERSION}-%{RELEASE}\n", package_name, timeout: 900) .and_return(rpm_q_status) end context "when rpm fails when querying package installed state" do before do allow(::File).to receive(:exist?).with(package_source).and_return(true) end let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } let(:rpm_q_stdout) { "" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { -1 } it "raises an exception when attempting any action" do expected_message = "Unable to determine current version due to RPM failure." expect { provider.run_action(:install) }.to raise_error do |error| expect(error).to be_a_kind_of(Chef::Exceptions::Package) expect(error.to_s).to include(expected_message) end end end context "when the package is installed" do let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } let(:rpm_q_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } let(:action) { :install } context "when the source is a file system path" do before do allow(::File).to receive(:exist?).with(package_source).and_return(true) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end it "should get the source package version from rpm if provided" do expect(provider.current_resource.package_name).to eq("ImageMagick-c++") expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5") end it "should return the current version installed if found by rpm" do expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5") end describe "action install" do context "when at the desired version already" do it "does nothing when the correct version is installed" do expect(provider).to_not receive(:shell_out_compacted!).with("rpm", "-i", "/tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_install end end context "when a newer version is desired" do let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" } it "runs rpm -u with the package source to upgrade" do expect(provider).to receive(:shell_out_compacted!).with("rpm", "-U", "--oldpackage", "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_install end end context "when an older version is desired" do let(:new_resource) do r = Chef::Resource::RpmPackage.new(package_name) r.source(package_source) r end let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" } it "should run rpm -u --oldpackage with the package source to downgrade" do expect(provider).to receive(:shell_out_compacted!).with("rpm", "-U", "--oldpackage", "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_install end it "if downgrades are not allowed it should not downgrade" do new_resource.allow_downgrade(false) expect(provider).not_to receive(:shell_out_compacted!) provider.action_install end end end describe "action upgrade" do let(:action) { :upgrade } context "when at the desired version already" do it "does nothing when the correct version is installed" do expect(provider).to_not receive(:shell_out_compacted!).with("rpm", "-i", "/tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_upgrade end end context "when a newer version is desired" do let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" } it "runs rpm -u with the package source to upgrade" do expect(provider).to receive(:shell_out_compacted!).with("rpm", "-U", "--oldpackage", "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_upgrade end end context "when an older version is desired" do let(:new_resource) do Chef::Resource::RpmPackage.new(package_name).tap do |r| r.source(package_source) r.allow_downgrade(true) end end let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" } it "should run rpm -u --oldpackage with the package source to downgrade" do expect(provider).to receive(:shell_out_compacted!).with("rpm", "-U", "--oldpackage", "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_upgrade end it "should run rpm -u --oldpackage with the package source to downgrade" do new_resource.allow_downgrade(false) expect(provider).not_to receive(:shell_out_compacted!).with("rpm", "-U", any_args) provider.action_upgrade end end end describe "action :remove" do let(:action) { :remove } it "should remove the package" do expect(provider).to receive(:shell_out_compacted!).with("rpm", "-e", "ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900) provider.action_remove end end context "when the package name contains a tilde (chef#3503)" do let(:package_name) { "supermarket" } let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" } let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } let(:rpm_q_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } it "should correctly determine the candidate version and installed version" do expect(provider.current_resource.package_name).to eq("supermarket") expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5") end end context "when the package name contains a plus symbol (chef#3671)" do let(:package_name) { "chef-server-core" } let(:package_source) { "/tmp/chef-server-core-12.2.0+20150713220422-1.el6.x86_64.rpm" } let(:rpm_qp_stdout) { "chef-server-core 12.2.0+20150713220422-1.el6" } let(:rpm_q_stdout) { "chef-server-core 12.2.0+20150713220422-1.el6" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } it "should correctly determine the candidate version and installed version" do expect(provider.current_resource.package_name).to eq("chef-server-core") expect(provider.new_resource.version).to eq("12.2.0+20150713220422-1.el6") end end end context "when the source is given as an URI" do before(:each) do allow(::File).to receive(:exist?).with(package_source).and_return(false) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end %w{http HTTP https HTTPS ftp FTP file FILE}.each do |scheme| context "when the source URI uses protocol scheme '#{scheme}'" do let(:package_source) { "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } it "should get the source package version from rpm if provided" do expect(provider.current_resource.package_name).to eq("ImageMagick-c++") expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5") end it "should return the current version installed if found by rpm" do expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5") end end end end end context "when the package is not installed" do let(:package_name) { "openssh-askpass" } let(:package_source) { "/tmp/openssh-askpass-1.2.3-4.el6_5.x86_64.rpm" } let(:rpm_qp_stdout) { "openssh-askpass 1.2.3-4.el6_5" } let(:rpm_q_stdout) { "package openssh-askpass is not installed" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } let(:action) { :install } before do allow(File).to receive(:exist?).with(package_source).and_return(true) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end it "should not detect the package name as version when not installed" do expect(provider.current_resource.version).to be_nil end context "when the package name contains a tilde (chef#3503)" do let(:package_name) { "supermarket" } let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" } let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } let(:rpm_q_stdout) { "package supermarket is not installed" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } it "should correctly determine the candidate version" do expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5") end end describe "managing the package" do describe "action install" do it "installs the package" do expect(provider).to receive(:shell_out_compacted!).with("rpm", "-i", package_source, timeout: 900) provider.action_install end context "when custom resource options are given" do it "installs with custom options specified in the resource" do new_resource.options("--dbpath /var/lib/rpm") expect(provider).to receive(:shell_out_compacted!).with("rpm", "--dbpath", "/var/lib/rpm", "-i", package_source, timeout: 900) provider.action_install end end end describe "action upgrade" do let(:action) { :upgrade } it "installs the package" do expect(provider).to receive(:shell_out_compacted!).with("rpm", "-i", package_source, timeout: 900) provider.action_upgrade end end describe "when removing the package" do let(:action) { :remove } it "should do nothing" do expect(provider).to_not receive(:shell_out_compacted!).with("rpm", "-e", "ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900) provider.action_remove end end end end end context "when the resource name is the path to the package" do let(:new_resource) do # When we pass a source in as the name, then #initialize in the # provider will call File.exists?. Because of the ordering in our # let() bindings and such, we have to set the stub here and not in a # before block. allow(::File).to receive(:exist?).with(package_source).and_return(true) Chef::Resource::RpmPackage.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") end let(:current_resource) { Chef::Resource::RpmPackage.new("ImageMagick-c++") } it "should install from a path when the package is a path and the source is nil" do expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") provider.current_resource = current_resource expect(provider).to receive(:shell_out_compacted!).with("rpm", "-i", "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") end it "should upgrade from a path when the package is a path and the source is nil" do expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") current_resource.version("21.4-19.el5") provider.current_resource = current_resource expect(provider).to receive(:shell_out_compacted!).with("rpm", "-U", "--oldpackage", "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") end end end