diff options
author | Daniel Schauenberg <d@unwiredcouch.com> | 2021-01-07 09:26:12 +0100 |
---|---|---|
committer | Daniel Schauenberg <d@unwiredcouch.com> | 2021-01-07 09:27:09 +0100 |
commit | e50d6e907d6bebec70271e9bcca50459dad88548 (patch) | |
tree | 57bef79bd565b536404761031eea39ebc90babdd /spec/functional/resource | |
parent | d98533a4d677cfc5bf1a9a77ff9cdcc4e2543862 (diff) | |
parent | f2a9569d23af039bd69d2dd3adb1251f7da044e3 (diff) | |
download | chef-e50d6e907d6bebec70271e9bcca50459dad88548.tar.gz |
Merge remote-tracking branch 'origin/master' into mrtazz/pkgng-exit-code-fix
Signed-off-by: Daniel Schauenberg <d@unwiredcouch.com>
Diffstat (limited to 'spec/functional/resource')
55 files changed, 8160 insertions, 2678 deletions
diff --git a/spec/functional/resource/aix_service_spec.rb b/spec/functional/resource/aix_service_spec.rb index 5fff3e00d7..16d830b88a 100755 --- a/spec/functional/resource/aix_service_spec.rb +++ b/spec/functional/resource/aix_service_spec.rb @@ -1,7 +1,6 @@ -# encoding: UTF-8 # # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +17,6 @@ # require "spec_helper" -require "functional/resource/base" require "chef/mixin/shell_out" shared_examples "src service" do @@ -77,12 +75,21 @@ describe Chef::Resource::Service, :requires_root, :aix_only do include Chef::Mixin::ShellOut def get_user_id - shell_out("id -u #{ENV['USER']}").stdout.chomp + shell_out("id -u #{ENV["USER"]}").stdout.chomp + end + + let(:run_context) do + node = Chef::Node.new + node.default[:platform] = ohai[:platform] + node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) end describe "When service is a subsystem" do before(:all) do - script_dir = File.join(File.dirname(__FILE__), "/../assets/") + script_dir = File.join(__dir__, "/../assets/") shell_out!("mkssys -s ctestsys -p #{script_dir}/testchefsubsys -u #{get_user_id} -S -n 15 -f 9 -R -Q") end @@ -110,7 +117,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do # Cannot run this test on a WPAR describe "When service is a group", :not_wpar do before(:all) do - script_dir = File.join(File.dirname(__FILE__), "/../assets/") + script_dir = File.join(__dir__, "/../assets/") shell_out!("mkssys -s ctestsys -p #{script_dir}/testchefsubsys -u #{get_user_id} -S -n 15 -f 9 -R -Q -G ctestgrp") end diff --git a/spec/functional/resource/aixinit_service_spec.rb b/spec/functional/resource/aixinit_service_spec.rb index bf50046b03..c568d40a8d 100755 --- a/spec/functional/resource/aixinit_service_spec.rb +++ b/spec/functional/resource/aixinit_service_spec.rb @@ -1,7 +1,6 @@ -# encoding: UTF-8 # # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +17,6 @@ # require "spec_helper" -require "functional/resource/base" require "chef/mixin/shell_out" require "fileutils" @@ -29,18 +27,18 @@ describe Chef::Resource::Service, :requires_root, :aix_only do # Platform specific validation routines. def service_should_be_started(file_name) # The existence of this file indicates that the service was started. - expect(File.exists?("#{Dir.tmpdir}/#{file_name}")).to be_truthy + expect(File.exist?("#{Dir.tmpdir}/#{file_name}")).to be_truthy end def service_should_be_stopped(file_name) - expect(File.exists?("#{Dir.tmpdir}/#{file_name}")).to be_falsey + expect(File.exist?("#{Dir.tmpdir}/#{file_name}")).to be_falsey end def valide_symlinks(expected_output, run_level = nil, status = nil, priority = nil) directory = [] if priority.is_a? Hash priority.each do |level, o| - directory << "/etc/rc.d/rc#{level}.d/#{(o[0] == :start ? 'S' : 'K')}#{o[1]}#{new_resource.service_name}" + directory << "/etc/rc.d/rc#{level}.d/#{(o[0] == :start ? "S" : "K")}#{o[1]}#{new_resource.service_name}" end directory else @@ -57,9 +55,10 @@ describe Chef::Resource::Service, :requires_root, :aix_only do # Actual tests let(:new_resource) do + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) new_resource = Chef::Resource::Service.new("chefinittest", run_context) new_resource.provider Chef::Provider::Service::AixInit - new_resource.supports({ :status => true, :restart => true, :reload => true }) + new_resource.supports({ status: true, restart: true, reload: true }) new_resource end @@ -69,12 +68,12 @@ describe Chef::Resource::Service, :requires_root, :aix_only do end before(:all) do - File.delete("/etc/rc.d/init.d/chefinittest") if File.exists?("/etc/rc.d/init.d/chefinittest") - FileUtils.cp("#{File.join(File.dirname(__FILE__), "/../assets/chefinittest")}", "/etc/rc.d/init.d/chefinittest") + File.delete("/etc/rc.d/init.d/chefinittest") if File.exist?("/etc/rc.d/init.d/chefinittest") + FileUtils.cp((File.join(__dir__, "/../assets/chefinittest")).to_s, "/etc/rc.d/init.d/chefinittest") end after(:all) do - File.delete("/etc/rc.d/init.d/chefinittest") if File.exists?("/etc/rc.d/init.d/chefinittest") + File.delete("/etc/rc.d/init.d/chefinittest") if File.exist?("/etc/rc.d/init.d/chefinittest") end before(:each) do @@ -166,7 +165,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do end after do - File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") + File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exist?("/etc/rc.d/rc2.d/chefinittest") end it "creates symlink with status K" do @@ -182,7 +181,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do end after do - File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") + File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exist?("/etc/rc.d/rc2.d/chefinittest") end it "creates a symlink with status K and a priority" do @@ -199,7 +198,7 @@ describe Chef::Resource::Service, :requires_root, :aix_only do end after do - File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") + File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exist?("/etc/rc.d/rc2.d/chefinittest") end it "create symlink with status stop (K) and a priority " do diff --git a/spec/functional/resource/package_spec.rb b/spec/functional/resource/apt_package_spec.rb index 0f01a751ec..9f10e27731 100644 --- a/spec/functional/resource/package_spec.rb +++ b/spec/functional/resource/apt_package_spec.rb @@ -1,7 +1,6 @@ -# encoding: UTF-8 # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +22,7 @@ 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 http://localhost:9000/ sid main" + 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 " + @@ -40,9 +39,7 @@ module AptServer def tcp_test_port(hostname, port) tcp_socket = TCPSocket.new(hostname, port) true - rescue Errno::ETIMEDOUT - false - rescue Errno::ECONNREFUSED + rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED false ensure tcp_socket && tcp_socket.close @@ -50,11 +47,11 @@ module AptServer def apt_server @apt_server ||= WEBrick::HTTPServer.new( - :Port => 9000, - :DocumentRoot => apt_data_dir + "/var/www/apt", + 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 ] + Logger: Logger.new(StringIO.new), + AccessLog: [ StringIO.new, WEBrick::AccessLog::COMMON_LOG_FORMAT ] ) end @@ -86,13 +83,13 @@ module AptServer end end -metadata = { :unix_only => true, - :requires_root => true, - :provider => { :package => Chef::Provider::Package::Apt }, - :arch => "x86_64" # test packages are 64bit +metadata = { unix_only: true, + requires_root: true, + provider: { package: Chef::Provider::Package::Apt }, + arch: "x86_64", # test packages are 64bit } -describe Chef::Resource::Package, metadata do +describe Chef::Resource::AptPackage, metadata do include Chef::Mixin::ShellOut context "with a remote package source" do @@ -143,7 +140,7 @@ describe Chef::Resource::Package, metadata do end def base_resource - r = Chef::Resource::Package.new("chef-integration-test", run_context) + 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") @@ -169,13 +166,13 @@ describe Chef::Resource::Package, metadata do it "does nothing for action :remove" do package_resource.run_action(:remove) - shell_out!("dpkg -l chef-integration-test", :returns => [1]) + 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]) + shell_out!("dpkg -l chef-integration-test", returns: [1]) expect(package_resource).not_to be_updated_by_last_action end @@ -275,7 +272,7 @@ describe Chef::Resource::Package, metadata do r = base_resource r.cookbook_name = "preseed" r.response_file("preseed-template-variables.seed") - r.response_file_variables({ :template_variable => "SUPPORTS VARIABLES" }) + r.response_file_variables({ template_variable: "SUPPORTS VARIABLES" }) r end @@ -300,13 +297,13 @@ describe Chef::Resource::Package, metadata do it "does nothing for action :install" do package_resource.run_action(:install) - shell_out!("dpkg -l chef-integration-test", :returns => [0]) + 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]) + shell_out!("dpkg -l chef-integration-test", returns: [0]) expect(package_resource).not_to be_updated_by_last_action end @@ -324,10 +321,10 @@ describe Chef::Resource::Package, metadata do # un chef-integration-test <none> (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]) + 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/) + expect(pkg_check.stdout).to match(/un\s+chef-integration-test/) end end @@ -353,14 +350,14 @@ describe Chef::Resource::Package, metadata do it "does nothing for action :install" do package_resource.run_action(:install) - shell_out!("dpkg -l chef-integration-test", :returns => [0]) + 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/) + 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 @@ -373,8 +370,8 @@ describe Chef::Resource::Package, metadata do 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/) + 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 diff --git a/spec/functional/resource/base.rb b/spec/functional/resource/base.rb deleted file mode 100644 index 38175e81c0..0000000000 --- a/spec/functional/resource/base.rb +++ /dev/null @@ -1,28 +0,0 @@ -# -# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2013-2016, 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. -# - -def run_context - @run_context ||= begin - node = Chef::Node.new - node.default[:platform] = ohai[:platform] - node.default[:platform_version] = ohai[:platform_version] - node.default[:os] = ohai[:os] - events = Chef::EventDispatch::Dispatcher.new - Chef::RunContext.new(node, {}, events) - end -end diff --git a/spec/functional/resource/bash_spec.rb b/spec/functional/resource/bash_spec.rb index a2e174d557..abb88f499f 100644 --- a/spec/functional/resource/bash_spec.rb +++ b/spec/functional/resource/bash_spec.rb @@ -1,6 +1,6 @@ # # Author:: Serdar Sutay (<serdar@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,65 +17,32 @@ # require "spec_helper" -require "functional/resource/base" describe Chef::Resource::Bash, :unix_only do let(:code) { "echo hello" } let(:resource) do + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + resource = Chef::Resource::Bash.new("foo_resource", run_context) - resource.code(code) + resource.code(code) unless code.nil? resource end - describe "when setting the command attribute" do - let (:command) { "wizard racket" } - - # in Chef-12 the `command` attribute is largely useless, but does set the identity attribute - # so that notifications need to target the value of the command. it will not run the `command` - # and if it is given without a code block then it does nothing and always succeeds. - describe "in Chef-12", chef: "< 13" do - it "gets the commmand attribute from the name" do - expect(resource.command).to eql("foo_resource") - end - - it "sets the resource identity to the command name" do - resource.command command - expect(resource.identity).to eql(command) - end - - it "warns when the code is not present and a useless `command` is present" do - expect(Chef::Log).to receive(:warn).with(/coding error/) - expect(Chef::Log).to receive(:warn).with(/deprecated/) - resource.code nil - resource.command command - expect { resource.run_action(:run) }.not_to raise_error - end + describe "when setting the command property" do + let(:command) { "wizard racket" } - describe "when the code is not present" do - let(:code) { nil } - it "warns" do - expect(Chef::Log).to receive(:warn) - expect { resource.run_action(:run) }.not_to raise_error - end - end + it "should raise an exception when trying to set the command" do + expect { resource.command command }.to raise_error(Chef::Exceptions::Script) end - # in Chef-13 the `command` attribute needs to be for internal use only - describe "in Chef-13", chef: ">= 13" do - it "should raise an exception when trying to set the command" do - expect { resource.command command }.to raise_error # FIXME: add a real error in Chef-13 - end - - it "should initialize the command to nil" do - expect(resource.command).to be_nil - end + it "should initialize the command to nil" do + expect(resource.command).to be_nil + end - describe "when the code is not present" do - let(:code) { nil } - it "raises an exception" do - expect { resource.run_action(:run) }.to raise_error # FIXME: add a real error in Chef-13 - expect { resource.run_action(:run) }.not_to raise_error - end + describe "when the code is not present" do + let(:code) { nil } + it "raises an exception" do + expect { resource.run_action(:run) }.to raise_error(Chef::Exceptions::ValidationFailed) end end end diff --git a/spec/functional/resource/batch_spec.rb b/spec/functional/resource/batch_spec.rb index e4fc6420c7..9ec1385175 100644 --- a/spec/functional/resource/batch_spec.rb +++ b/spec/functional/resource/batch_spec.rb @@ -1,6 +1,6 @@ # # Author:: Adam Edwards (<adamed@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,11 @@ describe Chef::Resource::WindowsScript::Batch, :windows_only do let(:output_command) { " > " } - let (:architecture_command) { "@echo %PROCESSOR_ARCHITECTURE%" } + let(:architecture_command) { "@echo %PROCESSOR_ARCHITECTURE%" } + + let(:resource) do + Chef::Resource::WindowsScript::Batch.new("Batch resource functional test", @run_context) + end it_behaves_like "a Windows script running on Windows" diff --git a/spec/functional/resource/bff_spec.rb b/spec/functional/resource/bff_spec.rb index e7f7540e5a..cdcc086180 100644 --- a/spec/functional/resource/bff_spec.rb +++ b/spec/functional/resource/bff_spec.rb @@ -1,6 +1,6 @@ # # Author:: Prabhu Das (<prabhu.das@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,14 +16,14 @@ # limitations under the License. # -require "functional/resource/base" require "chef/mixin/shell_out" # Run the test only for AIX platform. -describe Chef::Resource::BffPackage, :requires_root, :external => ohai[:platform] != "aix" do +describe Chef::Resource::BffPackage, :requires_root, external: ohai[:platform] != "aix" do include Chef::Mixin::ShellOut let(:new_resource) do + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) new_resource = Chef::Resource::BffPackage.new(@pkg_name, run_context) new_resource.source @pkg_path new_resource @@ -31,12 +31,12 @@ describe Chef::Resource::BffPackage, :requires_root, :external => ohai[:platform def bff_pkg_should_be_installed(resource) expect(shell_out("lslpp -L #{resource.name}").exitstatus).to eq(0) - ::File.exists?("/usr/PkgA/bin/acommand") + ::File.exist?("/usr/PkgA/bin/acommand") end def bff_pkg_should_be_removed(resource) expect(shell_out("lslpp -L #{resource.name}").exitstatus).to eq(1) - !::File.exists?("/usr/PkgA/bin/acommand") + !::File.exist?("/usr/PkgA/bin/acommand") end before(:all) do @@ -62,7 +62,7 @@ describe Chef::Resource::BffPackage, :requires_root, :external => ohai[:platform context "package install action with options" do it "should install a package" do - new_resource.options("-e/tmp/installp.log") + new_resource.options("-e#{Dir.tmpdir}/installp.log") new_resource.run_action(:install) bff_pkg_should_be_installed(new_resource) end @@ -108,7 +108,7 @@ describe Chef::Resource::BffPackage, :requires_root, :external => ohai[:platform end it "should remove an installed package" do - new_resource.options("-e/tmp/installp.log") + new_resource.options("-e#{Dir.tmpdir}/installp.log") new_resource.run_action(:remove) bff_pkg_should_be_removed(new_resource) end diff --git a/spec/functional/resource/chocolatey_package_spec.rb b/spec/functional/resource/chocolatey_package_spec.rb index 7bb6698daf..e55c1a453c 100644 --- a/spec/functional/resource/chocolatey_package_spec.rb +++ b/spec/functional/resource/chocolatey_package_spec.rb @@ -1,6 +1,6 @@ # # Author:: Matt Wrock (<matt@mattwrock.com>) -# Copyright:: Copyright (c) 2016 Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,22 +16,19 @@ # limitations under the License. # require "spec_helper" -require "chef/mixin/powershell_out" +require "chef/mixin/shell_out" -describe Chef::Resource::ChocolateyPackage, :windows_only do - include Chef::Mixin::PowershellOut - - before(:all) do - powershell_out!("iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))") - unless ENV["PATH"] =~ /chocolatey\\bin/ - ENV["PATH"] = "C:\\ProgramData\\chocolatey\\bin;#{ENV["PATH"]}" - end - end +describe Chef::Resource::ChocolateyPackage, :windows_only, :choco_installed do + include Chef::Mixin::ShellOut let(:package_name) { "test-A" } - let(:package_list) { proc { powershell_out!("choco list -lo -r #{Array(package_name).join(' ')}").stdout.chomp } } + let(:package_list) { proc { shell_out!("choco list -lo -r #{Array(package_name).join(" ")}").stdout.chomp } } let(:package_source) { File.join(CHEF_SPEC_ASSETS, "chocolatey_feed") } + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + subject do new_resource = Chef::Resource::ChocolateyPackage.new("test choco package", run_context) new_resource.package_name package_name @@ -39,6 +36,11 @@ describe Chef::Resource::ChocolateyPackage, :windows_only do new_resource end + let(:provider) do + provider = subject.provider_for_action(subject.action) + provider + end + context "installing a package" do after { remove_package } @@ -70,7 +72,7 @@ describe Chef::Resource::ChocolateyPackage, :windows_only do end context "installing multiple packages" do - let(:package_name) { [ "test-A", "test-B" ] } + let(:package_name) { %w{test-A test-B} } it "installs both packages" do subject.run_action(:install) @@ -82,6 +84,48 @@ describe Chef::Resource::ChocolateyPackage, :windows_only do subject.package_name "blah" expect { subject.run_action(:install) }.to raise_error Chef::Exceptions::Package end + + it "installs with an option as a string" do + subject.options "--force --confirm" + subject.run_action(:install) + expect(package_list.call).to eq("#{package_name}|2.0") + end + + it "installs with multiple options as a string" do + subject.options "--force --confirm" + subject.run_action(:install) + expect(package_list.call).to eq("#{package_name}|2.0") + end + + context "when multiple options passed as string" do + before do + subject.options "--force --confirm" + subject.source nil + end + + it "splits a string into an array of options" do + expect(provider.send(:cmd_args)).to eq(["--force", "--confirm"]) + end + + it "calls command_line_to_argv_w_helper method" do + expect(provider).to receive(:command_line_to_argv_w_helper).with(subject.options).and_return(["--force", "--confirm"]) + provider.send(:cmd_args) + end + end + + context "when multiple options passed as array" do + it "Does not call command_line_to_argv_w_helper method" do + subject.options [ "--force", "--confirm" ] + expect(provider).not_to receive(:command_line_to_argv_w_helper) + provider.send(:cmd_args) + end + end + + it "installs with multiple options as an array" do + subject.options [ "--force", "--confirm" ] + subject.run_action(:install) + expect(package_list.call).to eq("#{package_name}|2.0") + end end context "upgrading a package" do diff --git a/spec/functional/resource/cookbook_file_spec.rb b/spec/functional/resource/cookbook_file_spec.rb index d127413c73..8dbf22d611 100644 --- a/spec/functional/resource/cookbook_file_spec.rb +++ b/spec/functional/resource/cookbook_file_spec.rb @@ -1,6 +1,6 @@ # # Author:: Tim Hinderliter (<tim@chef.io>) -# Copyright:: Copyright 2012-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,9 +25,7 @@ describe Chef::Resource::CookbookFile do let(:source) { "java.response" } let(:cookbook_name) { "java" } let(:expected_content) do - content = File.open(File.join(CHEF_SPEC_DATA, "cookbooks", "java", "files", "default", "java.response"), "rb") do |f| - f.read - end + content = File.open(File.join(CHEF_SPEC_DATA, "cookbooks", "java", "files", "default", "java.response"), "rb", &:read) content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding) content end @@ -70,11 +68,11 @@ describe Chef::Resource::CookbookFile do let(:path) { File.join(windows_non_temp_dir, make_tmpname(file_base)) } before do - FileUtils.mkdir_p(windows_non_temp_dir) if Chef::Platform.windows? + FileUtils.mkdir_p(windows_non_temp_dir) if ChefUtils.windows? end after do - FileUtils.rm_r(windows_non_temp_dir) if Chef::Platform.windows? && File.exists?(windows_non_temp_dir) + FileUtils.rm_r(windows_non_temp_dir) if ChefUtils.windows? && File.exist?(windows_non_temp_dir) end end diff --git a/spec/functional/resource/cron_spec.rb b/spec/functional/resource/cron_spec.rb index f5948191c5..fa53eb08a1 100644 --- a/spec/functional/resource/cron_spec.rb +++ b/spec/functional/resource/cron_spec.rb @@ -1,7 +1,6 @@ -# encoding: UTF-8 # # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +17,6 @@ # require "spec_helper" -require "functional/resource/base" require "chef/mixin/shell_out" describe Chef::Resource::Cron, :requires_root, :unix_only do @@ -28,7 +26,7 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do # Platform specific validation routines. def cron_should_exists(cron_name, command) case ohai[:platform] - when "aix", "solaris", "opensolaris", "solaris2", "omnios" + when "aix", "solaris2", "omnios" expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(0) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").stdout.lines.to_a.size).to eq(1) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{command}\"").exitstatus).to eq(0) @@ -43,7 +41,7 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do def cron_should_not_exists(cron_name) case ohai[:platform] - when "aix", "solaris", "opensolaris", "solaris2", "omnios" + when "aix", "solaris2", "omnios" expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(1) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{new_resource.command}\"").stdout.lines.to_a.size).to eq(0) else @@ -53,19 +51,20 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do end # Actual tests + + let(:run_context) do + node = Chef::Node.new + node.default[:platform] = ohai[:platform] + node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + end + let(:new_resource) do new_resource = Chef::Resource::Cron.new("Chef functional test cron", run_context) - new_resource.user "root" - # @hourly is not supported on solaris, aix - if ohai[:platform] == "solaris" || ohai[:platform] == "solaris2" || ohai[:platform] == "aix" - new_resource.minute "0 * * * *" - else - new_resource.minute "@hourly" - end - new_resource.hour "" - new_resource.day "" - new_resource.month "" - new_resource.weekday "" + new_resource.user "root" + new_resource.minute "0" new_resource.command "/bin/true" new_resource end @@ -89,6 +88,16 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do 5.times { new_resource.run_action(:create) } cron_should_exists(new_resource.name, new_resource.command) end + + # Test cron for day of week + weekdays = { Mon: 1, tuesday: 2, '3': 3, 'thursday': 4, 'Fri': 5, 6 => 6 } + weekdays.each do |key, value| + it "should create crontab entry and set #{value} for #{key} as weekday" do + new_resource.weekday key + expect { new_resource.run_action(:create) }.not_to raise_error + cron_should_exists(new_resource.name, new_resource.command) + end + end end describe "delete action" do @@ -104,9 +113,9 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do end end - exclude_solaris = %w{solaris opensolaris solaris2 omnios}.include?(ohai[:platform]) - describe "create action with various attributes", :external => exclude_solaris do - def create_and_validate_with_attribute(resource, attribute, value) + exclude_solaris = %w{solaris solaris2 omnios}.include?(ohai[:platform]) + describe "create action with various attributes", external: exclude_solaris do + def create_and_validate_with_property(resource, attribute, value) if ohai[:platform] == "aix" expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron, /Aix cron entry does not support environment variables. Please set them in script and use script in cron./) else @@ -118,6 +127,7 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do def cron_attribute_should_exists(cron_name, attribute, value) return if %w{aix solaris}.include?(ohai[:platform]) + # Test if the attribute exists on newly created cron cron_should_exists(cron_name, "") expect(shell_out("crontab -l -u #{new_resource.user} | grep '#{attribute.upcase}=\"#{value}\"'").exitstatus).to eq(0) @@ -129,28 +139,28 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do it "should create a crontab entry for mailto attribute" do new_resource.mailto "cheftest@example.com" - create_and_validate_with_attribute(new_resource, "mailto", "cheftest@example.com") + create_and_validate_with_property(new_resource, "mailto", "cheftest@example.com") end it "should create a crontab entry for path attribute" do new_resource.path "/usr/local/bin" - create_and_validate_with_attribute(new_resource, "path", "/usr/local/bin") + create_and_validate_with_property(new_resource, "path", "/usr/local/bin") end it "should create a crontab entry for shell attribute" do new_resource.shell "/bin/bash" - create_and_validate_with_attribute(new_resource, "shell", "/bin/bash") + create_and_validate_with_property(new_resource, "shell", "/bin/bash") end it "should create a crontab entry for home attribute" do new_resource.home "/home/opscode" - create_and_validate_with_attribute(new_resource, "home", "/home/opscode") + create_and_validate_with_property(new_resource, "home", "/home/opscode") end %i{ home mailto path shell }.each do |attr| it "supports an empty string for #{attr} attribute" do new_resource.send(attr, "") - create_and_validate_with_attribute(new_resource, attr.to_s, "") + create_and_validate_with_property(new_resource, attr.to_s, "") end end end @@ -160,20 +170,10 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do new_resource.run_action(:delete) end - def cron_create_should_raise_exception - expect { new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron, /Error updating state of #{new_resource.name}, exit: 1/) - cron_should_not_exists(new_resource.name) - end - - it "should not create cron with invalid minute" do - new_resource.minute "invalid" - cron_create_should_raise_exception - end - it "should not create cron with invalid user" do new_resource.user "1-really-really-invalid-user-name" - cron_create_should_raise_exception + expect { new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron) + cron_should_not_exists(new_resource.name) end - end end diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb deleted file mode 100644 index 572609d8ff..0000000000 --- a/spec/functional/resource/deploy_revision_spec.rb +++ /dev/null @@ -1,881 +0,0 @@ -# -# Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2012-2016, 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 "tmpdir" - -# Deploy relies heavily on symlinks, so it doesn't work on windows. -describe Chef::Resource::DeployRevision, :unix_only => true, :requires_git => true do - - let(:file_cache_path) { Dir.mktmpdir } - let(:deploy_directory) { Dir.mktmpdir } - - # By making restart or other operations write to this file, we can externally - # track the order in which those operations happened. - let(:observe_order_file) { Tempfile.new("deploy-resource-observe-operations") } - - before do - Chef::Log.level = :info - @old_file_cache_path = Chef::Config[:file_cache_path] - Chef::Config[:file_cache_path] = file_cache_path - end - - after do - Chef::Config[:file_cache_path] = @old_file_cache_path - FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory) - FileUtils.remove_entry_secure file_cache_path - observe_order_file.close - FileUtils.remove_entry_secure observe_order_file.path - end - - before(:all) do - @ohai = Ohai::System.new - @ohai.all_plugins(%w{platform os}) - end - - let(:node) do - Chef::Node.new.tap do |n| - n.name "rspec-test" - n.consume_external_attrs(@ohai.data, {}) - end - end - - let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } - let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } - - # These tests use git's bundle feature, which is a way to export an entire - # git repo (or subset of commits) as a single file. - # - # Generally you can treat a git bundle as a regular git remote. - # - # See also: http://git-scm.com/2010/03/10/bundles.html - let(:git_bundle_repo) { File.expand_path("git_bundles/sinatra-test-app.gitbundle", CHEF_SPEC_DATA) } - - let(:git_bundle_with_in_repo_callbacks) { File.expand_path("git_bundles/sinatra-test-app-with-callback-files.gitbundle", CHEF_SPEC_DATA) } - - let(:git_bundle_with_in_repo_symlinks) { File.expand_path("git_bundles/sinatra-test-app-with-symlinks.gitbundle", CHEF_SPEC_DATA) } - - # This is the fourth version - let(:latest_rev) { "3eb5ca6c353c83d9179dd3b29347539829b401f3" } - - # This is the third version - let(:previous_rev) { "6d19a6dbecc8e37f5b2277345885c0c783eb8fb1" } - - # This is the second version - let(:second_rev) { "0827e1b0e5043608ac0a824da5c558e252154ad0" } - - # This is the sixth version, it is on the "with-deploy-scripts" branch - let(:rev_with_in_repo_callbacks) { "2404d015882659754bdb93ad6e4b4d3d02691a82" } - - # This is the fifth version in the "with-symlinks" branch - let(:rev_with_in_repo_symlinks) { "5a4748c52aaea8250b4346a9b8ede95ee3755e28" } - - # Read values from the +observe_order_file+ and split each line. This way you - # can see in which order things really happened. - def actual_operations_order - IO.read(observe_order_file.path).split("\n").map(&:strip) - end - - # 1. touch `restart.txt` in cwd so we know that the command is run with the - # right cwd. - # 2. Append +tag+ to the `observe_order_file` so we can check the order in - # which operations happen later in the test. - def shell_restart_command(tag) - "touch restart.txt && echo '#{tag}' >> #{observe_order_file.path}" - end - - let(:basic_deploy_resource) do - Chef::Resource::DeployRevision.new(deploy_directory, run_context).tap do |r| - r.name "deploy-revision-unit-test" - r.repo git_bundle_repo - r.symlink_before_migrate({}) - r.symlinks({}) - end - end - - let(:deploy_to_latest_rev) do - basic_deploy_resource.dup.tap do |r| - r.revision(latest_rev) - r.restart_command shell_restart_command(:deploy_to_latest_rev) - end - end - - let(:deploy_to_previous_rev) do - basic_deploy_resource.dup.tap do |r| - r.revision(previous_rev) - r.restart_command shell_restart_command(:deploy_to_previous_rev) - end - end - - let(:deploy_to_latest_rev_again) do - basic_deploy_resource.dup.tap do |r| - r.revision(latest_rev) - r.restart_command shell_restart_command(:deploy_to_latest_rev_again) - end - end - - let(:deploy_to_previous_rev_again) do - basic_deploy_resource.dup.tap do |r| - r.revision(previous_rev) - r.restart_command shell_restart_command(:deploy_to_previous_rev_again) - end - end - - let(:deploy_to_second_rev) do - basic_deploy_resource.dup.tap do |r| - r.revision(second_rev) - r.restart_command shell_restart_command(:deploy_to_second_rev) - end - end - - let(:deploy_to_second_rev_again) do - basic_deploy_resource.dup.tap do |r| - r.revision(second_rev) - r.restart_command shell_restart_command(:deploy_to_second_rev_again) - end - end - - let(:deploy_to_second_rev_again_again) do - basic_deploy_resource.dup.tap do |r| - r.revision(second_rev) - r.restart_command shell_restart_command(:deploy_to_second_rev_again_again) - end - end - - # Computes the full path for +path+ relative to the deploy directory - def rel_path(path) - File.expand_path(path, deploy_directory) - end - - def actual_current_rev - Dir.chdir(rel_path("current")) do - `git rev-parse HEAD`.strip - end - end - - def self.the_app_is_deployed_at_revision(target_rev_spec) - it "deploys the app to the target revision (#{target_rev_spec})" do - target_rev = send(target_rev_spec) - - expect(File).to exist(rel_path("current")) - - expect(actual_current_rev).to eq(target_rev) - - # Is the app code actually there? - expect(File).to exist(rel_path("current/app/app.rb")) - end - end - - context "when deploying a simple app" do - describe "for the first time, with the required directory layout precreated" do - before do - FileUtils.mkdir_p(rel_path("releases")) - FileUtils.mkdir_p(rel_path("shared")) - deploy_to_latest_rev.run_action(:deploy) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "restarts the application" do - expect(File).to exist(rel_path("current/restart.txt")) - expect(actual_operations_order).to eq(%w{deploy_to_latest_rev}) - end - - it "is marked as updated" do - expect(deploy_to_latest_rev).to be_updated_by_last_action - end - end - - describe "back to a previously deployed revision, with the directory structure precreated" do - before do - FileUtils.mkdir_p(rel_path("releases")) - FileUtils.mkdir_p(rel_path("shared")) - - deploy_to_latest_rev.run_action(:deploy) - deploy_to_previous_rev.run_action(:deploy) - deploy_to_latest_rev_again.run_action(:deploy) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "restarts the application after rolling back" do - expect(actual_operations_order).to eq(%w{deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again}) - end - - it "is marked updated" do - expect(deploy_to_latest_rev_again).to be_updated_by_last_action - end - - it "deploys the right code" do - expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the fourth version of the app") - end - end - - describe "for the first time, with no existing directory layout" do - before do - deploy_to_latest_rev.run_action(:deploy) - end - - it "creates the required directory tree" do - expect(File).to be_directory(rel_path("releases")) - expect(File).to be_directory(rel_path("shared")) - expect(File).to be_directory(rel_path("releases/#{latest_rev}")) - - expect(File).to be_directory(rel_path("current/tmp")) - expect(File).to be_directory(rel_path("current/config")) - expect(File).to be_directory(rel_path("current/public")) - - expect(File).to be_symlink(rel_path("current")) - expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "restarts the application" do - expect(File).to exist(rel_path("current/restart.txt")) - expect(actual_operations_order).to eq(%w{deploy_to_latest_rev}) - end - - it "is marked as updated" do - expect(deploy_to_latest_rev).to be_updated_by_last_action - end - end - - describe "again to the current revision" do - before do - deploy_to_latest_rev.run_action(:deploy) - deploy_to_latest_rev.run_action(:deploy) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "does not restart the app" do - expect(actual_operations_order).to eq(%w{deploy_to_latest_rev}) - end - - it "is not marked updated" do - expect(deploy_to_latest_rev).not_to be_updated_by_last_action - end - - end - - describe "again with force_deploy" do - before do - deploy_to_latest_rev.run_action(:force_deploy) - deploy_to_latest_rev_again.run_action(:force_deploy) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "restarts the app" do - expect(actual_operations_order).to eq(%w{deploy_to_latest_rev deploy_to_latest_rev_again}) - end - - it "is marked updated" do - expect(deploy_to_latest_rev).to be_updated_by_last_action - end - - end - - describe "again to a new revision" do - before do - deploy_to_previous_rev.run_action(:deploy) - deploy_to_latest_rev.run_action(:deploy) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "restarts the application after the new deploy" do - expect(actual_operations_order).to eq(%w{deploy_to_previous_rev deploy_to_latest_rev}) - end - - it "is marked updated" do - expect(deploy_to_previous_rev).to be_updated_by_last_action - end - - it "leaves the old copy of the app around for rollback" do - expect(File).to exist(File.join(deploy_directory, "releases", previous_rev)) - end - - end - - describe "back to a previously deployed revision (implicit rollback)" do - before do - deploy_to_latest_rev.run_action(:deploy) - deploy_to_previous_rev.run_action(:deploy) - deploy_to_latest_rev_again.run_action(:deploy) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "restarts the application after rolling back" do - expect(actual_operations_order).to eq(%w{deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again}) - end - - it "is marked updated" do - expect(deploy_to_latest_rev_again).to be_updated_by_last_action - end - - it "deploys the right code" do - expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the fourth version of the app") - end - end - - describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do - before do - deploy_to_previous_rev.run_action(:deploy) - @previous_rev_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases - deploy_to_latest_rev.run_action(:deploy) - @latest_rev_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases - deploy_to_latest_rev_again.run_action(:rollback) - @previous_rev_again_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases - end - - the_app_is_deployed_at_revision(:previous_rev) - - it "restarts the application after rolling back" do - expect(actual_operations_order).to eq(%w{deploy_to_previous_rev deploy_to_latest_rev deploy_to_latest_rev_again}) - end - - it "is marked updated" do - expect(deploy_to_latest_rev_again).to be_updated_by_last_action - end - - it "deploys the right code" do - expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the third version of the app") - end - - it "all_releases after first deploy should have one entry" do - expect(@previous_rev_all_releases.length).to eq(1) - end - - it "all_releases after second deploy should have two entries" do - expect(@latest_rev_all_releases.length).to eq(2) - end - - it "all_releases after rollback should have one entry" do - expect(@previous_rev_again_all_releases.length).to eq(1) - end - - it "all_releases after rollback should be the same as after the first deploy" do - expect(@previous_rev_again_all_releases).to eq(@previous_rev_all_releases) - end - - end - - describe "back to a previously deployed revision where resource rev == previous revision (explicit rollback)" do - before do - deploy_to_previous_rev.run_action(:deploy) - @previous_rev_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases - deploy_to_latest_rev.run_action(:deploy) - @latest_rev_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases - deploy_to_previous_rev_again.run_action(:rollback) - # FIXME: only difference with previous test is using latest_rev_again insetad of previous_rev_again - @previous_rev_again_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases - end - - the_app_is_deployed_at_revision(:previous_rev) - - it "restarts the application after rolling back" do - expect(actual_operations_order).to eq(%w{deploy_to_previous_rev deploy_to_latest_rev deploy_to_previous_rev_again}) - end - - it "is marked updated" do - expect(deploy_to_previous_rev_again).to be_updated_by_last_action - end - - it "deploys the right code" do - expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the third version of the app") - end - - it "all_releases after first deploy should have one entry" do - expect(@previous_rev_all_releases.length).to eq(1) - end - - it "all_releases after second deploy should have two entries" do - expect(@latest_rev_all_releases.length).to eq(2) - end - - it "all_releases after rollback should have one entry" do - expect(@previous_rev_again_all_releases.length).to eq(1) - end - - it "all_releases after rollback should be the same as after the first deploy" do - expect(@previous_rev_again_all_releases).to eq(@previous_rev_all_releases) - end - end - - describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do - before do - deploy_to_second_rev.run_action(:deploy) - @first_deploy_all_releases = deploy_to_second_rev.provider_for_action(:deploy).all_releases - deploy_to_previous_rev.run_action(:deploy) - @second_deploy_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases - deploy_to_previous_rev_again.run_action(:rollback) - @third_deploy_all_releases = deploy_to_previous_rev_again.provider_for_action(:deploy).all_releases - deploy_to_latest_rev.run_action(:deploy) - @fourth_deploy_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases - deploy_to_latest_rev_again.run_action(:rollback) - @fifth_deploy_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases - end - - the_app_is_deployed_at_revision(:second_rev) - - it "restarts the application after rolling back" do - expect(actual_operations_order).to eq(%w{deploy_to_second_rev deploy_to_previous_rev deploy_to_previous_rev_again deploy_to_latest_rev deploy_to_latest_rev_again}) - end - - it "is marked updated" do - expect(deploy_to_latest_rev_again).to be_updated_by_last_action - end - - it "deploys the right code" do - expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the second version of the app") - end - - it "all_releases after rollback should have one entry" do - expect(@fifth_deploy_all_releases.length).to eq(1) - end - - it "all_releases after rollback should be the same as after the first deploy" do - expect(@fifth_deploy_all_releases).to eq(@first_deploy_all_releases) - end - end - - describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do - before do - deploy_to_second_rev.run_action(:deploy) - @first_deploy_all_releases = deploy_to_second_rev.provider_for_action(:deploy).all_releases - deploy_to_previous_rev.run_action(:deploy) - @second_deploy_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases - deploy_to_second_rev_again.run_action(:rollback) - @third_deploy_all_releases = deploy_to_second_rev_again.provider_for_action(:deploy).all_releases - deploy_to_latest_rev.run_action(:deploy) - @fourth_deploy_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases - deploy_to_second_rev_again_again.run_action(:rollback) - @fifth_deploy_all_releases = deploy_to_second_rev_again_again.provider_for_action(:deploy).all_releases - end - - the_app_is_deployed_at_revision(:second_rev) - - it "restarts the application after rolling back" do - expect(actual_operations_order).to eq(%w{deploy_to_second_rev deploy_to_previous_rev deploy_to_second_rev_again deploy_to_latest_rev deploy_to_second_rev_again_again}) - end - - it "is marked updated" do - expect(deploy_to_second_rev_again_again).to be_updated_by_last_action - end - - it "deploys the right code" do - expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the second version of the app") - end - - it "all_releases after rollback should have one entry" do - expect(@fifth_deploy_all_releases.length).to eq(1) - end - - it "all_releases after rollback should be the same as after the first deploy" do - expect(@fifth_deploy_all_releases).to eq(@first_deploy_all_releases) - end - - end - - # CHEF-3435 - describe "to a deploy_to path that does not yet exist" do - - let(:top_level_tmpdir) { Dir.mktmpdir } - - # override top level deploy_directory let block with one that is two - # directories deeper - let(:deploy_directory) { File.expand_path("nested/deeper", top_level_tmpdir) } - - after do - FileUtils.remove_entry_secure top_level_tmpdir - end - - before do - expect(File).not_to exist(deploy_directory) - deploy_to_latest_rev.run_action(:deploy) - end - - it "creates the required directory tree" do - expect(File).to be_directory(rel_path("releases")) - expect(File).to be_directory(rel_path("shared")) - expect(File).to be_directory(rel_path("releases/#{latest_rev}")) - - expect(File).to be_directory(rel_path("current/tmp")) - expect(File).to be_directory(rel_path("current/config")) - expect(File).to be_directory(rel_path("current/public")) - - expect(File).to be_symlink(rel_path("current")) - expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) - end - - the_app_is_deployed_at_revision(:latest_rev) - - end - end - - context "when deploying an app with inline recipe callbacks" do - - # Use closures to capture and mutate this variable. This allows us to track - # ordering of operations. - callback_order = [] - - let(:deploy_to_latest_with_inline_recipes) do - deploy_to_latest_rev.dup.tap do |r| - r.symlink_before_migrate "config/config.ru" => "config.ru" - r.before_migrate do - callback_order << :before_migrate - - file "#{release_path}/before_migrate.txt" do - # The content here isn't relevant, but it gets printed when running - # the tests. Could be handy for debugging. - content callback_order.inspect - end - end - r.before_symlink do - callback_order << :before_symlink - - current_release_path = release_path - ruby_block "ensure before symlink" do - block do - if ::File.exist?(::File.join(current_release_path, "/tmp")) - raise "Ordering issue with provider, expected symlinks to not have been created" - end - end - end - - file "#{release_path}/before_symlink.txt" do - content callback_order.inspect - end - end - r.before_restart do - callback_order << :before_restart - - current_release_path = release_path - ruby_block "ensure after symlink" do - block do - unless ::File.exist?(::File.join(current_release_path, "/tmp")) - raise "Ordering issue with provider, expected symlinks to have been created" - end - end - end - - file "#{release_path}/tmp/before_restart.txt" do - content callback_order.inspect - end - end - r.after_restart do - callback_order << :after_restart - file "#{release_path}/tmp/after_restart.txt" do - content callback_order.inspect - end - end - end - end - - before do - callback_order.clear # callback_order variable is global for this context group - deploy_to_latest_with_inline_recipes.run_action(:deploy) - end - - the_app_is_deployed_at_revision(:latest_rev) - - it "is marked updated" do - expect(deploy_to_latest_with_inline_recipes).to be_updated_by_last_action - end - - it "calls the callbacks in order" do - expect(callback_order).to eq([:before_migrate, :before_symlink, :before_restart, :after_restart]) - end - - it "runs chef resources in the callbacks" do - expect(File).to exist(rel_path("current/before_migrate.txt")) - expect(File).to exist(rel_path("current/before_symlink.txt")) - expect(File).to exist(rel_path("current/tmp/before_restart.txt")) - expect(File).to exist(rel_path("current/tmp/after_restart.txt")) - end - end - - context "when deploying an app with in-repo callback scripts" do - let(:deploy_with_in_repo_callbacks) do - basic_deploy_resource.dup.tap do |r| - r.repo git_bundle_with_in_repo_callbacks - r.revision rev_with_in_repo_callbacks - end - end - - before do - deploy_with_in_repo_callbacks.run_action(:deploy) - end - - the_app_is_deployed_at_revision(:rev_with_in_repo_callbacks) - - it "runs chef resources in the callbacks" do - expect(File).to exist(rel_path("current/before_migrate.txt")) - expect(File).to exist(rel_path("current/before_symlink.txt")) - expect(File).to exist(rel_path("current/tmp/before_restart.txt")) - expect(File).to exist(rel_path("current/tmp/after_restart.txt")) - end - - end - - context "when deploying an app with migrations" do - let(:deploy_with_migration) do - basic_deploy_resource.dup.tap do |r| - - # Need this so we can call methods from this test inside the inline - # recipe callbacks - spec_context = self - - r.revision latest_rev - - # enable migrations - r.migrate true - # abuse `shell_restart_command` so we can observe order of when the - # miration command gets run - r.migration_command shell_restart_command("migration") - r.before_migrate do - - # inline recipe callbacks don't cwd, so you have to get the release - # directory as a local and "capture" it in the closure. - current_release = release_path - execute spec_context.shell_restart_command("before_migrate") do - cwd current_release - end - end - r.before_symlink do - current_release = release_path - execute spec_context.shell_restart_command("before_symlink") do - cwd current_release - end - end - - r.before_restart do - current_release = release_path - execute spec_context.shell_restart_command("before_restart") do - cwd current_release - end - end - - r.after_restart do - current_release = release_path - execute spec_context.shell_restart_command("after_restart") do - cwd current_release - end - end - - end - end - - before do - deploy_with_migration.run_action(:deploy) - end - - it "runs migrations in between the before_migrate and before_symlink steps" do - expect(actual_operations_order).to eq(%w{before_migrate migration before_symlink before_restart after_restart}) - end - end - - context "when deploying an app with in-repo symlinks" do - let(:deploy_with_in_repo_symlinks) do - basic_deploy_resource.dup.tap do |r| - r.repo git_bundle_with_in_repo_symlinks - r.revision rev_with_in_repo_symlinks - end - end - - it "should not raise an exception calling File.utime on symlinks" do - expect { deploy_with_in_repo_symlinks.run_action(:deploy) }.not_to raise_error - end - end - - context "when a previously deployed application has been nuked" do - - shared_examples_for "a redeployed application" do - - it "should redeploy the application" do - expect(File).to be_directory(rel_path("releases")) - expect(File).to be_directory(rel_path("shared")) - expect(File).to be_directory(rel_path("releases/#{latest_rev}")) - - expect(File).to be_directory(rel_path("current/tmp")) - expect(File).to be_directory(rel_path("current/config")) - expect(File).to be_directory(rel_path("current/public")) - - expect(File).to be_symlink(rel_path("current")) - expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) - end - end - - # background: If a deployment is hosed and the user decides to rm -rf the - # deployment dir, deploy resource should detect that and nullify its cache. - - context "by removing the entire deploy directory" do - - before do - deploy_to_latest_rev.dup.run_action(:deploy) - FileUtils.rm_rf(deploy_directory) - deploy_to_latest_rev.dup.run_action(:deploy) - end - - include_examples "a redeployed application" - - end - - context "by removing the current/ directory" do - - before do - deploy_to_latest_rev.dup.run_action(:deploy) - FileUtils.rm(rel_path("current")) - deploy_to_latest_rev.dup.run_action(:deploy) - end - - include_examples "a redeployed application" - - end - end - - context "when a deployment fails" do - - shared_examples_for "a recovered deployment" do - - it "should redeploy the application" do - expect(File).to be_directory(rel_path("releases")) - expect(File).to be_directory(rel_path("shared")) - expect(File).to be_directory(rel_path("releases/#{latest_rev}")) - - expect(File).to be_directory(rel_path("current/tmp")) - expect(File).to be_directory(rel_path("current/config")) - expect(File).to be_directory(rel_path("current/public")) - - expect(File).to be_symlink(rel_path("current")) - expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) - - # if callbacks ran, we know the app was deployed and not merely rolled - # back to a (busted) prior deployment. - expect(callback_order).to eq([:before_migrate, - :before_symlink, - :before_restart, - :after_restart ]) - end - end - - let!(:callback_order) { [] } - - let(:deploy_to_latest_with_callback_tracking) do - resource = deploy_to_latest_rev.dup - tracker = callback_order - resource.before_migrate { tracker << :before_migrate } - resource.before_symlink { tracker << :before_symlink } - resource.before_restart { tracker << :before_restart } - resource.after_restart { tracker << :after_restart } - resource - end - - [:before_migrate, :before_symlink, :before_restart, :after_restart].each do |callback| - - context "in the `#{callback}' callback" do - before do - expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Exception, %r{I am a failed deploy}) - deploy_to_latest_with_callback_tracking.run_action(:deploy) - end - - let(:deploy_that_fails) do - resource = deploy_to_latest_rev.dup - errant_callback = lambda { |x| raise Exception, "I am a failed deploy" } - resource.send(callback, &errant_callback) - resource - end - - include_examples "a recovered deployment" - - end - - end - - context "in the service restart step" do - - let(:deploy_that_fails) do - resource = deploy_to_latest_rev.dup - resource.restart_command("RUBYOPT=\"\" ruby -e 'exit 1'") - resource - end - - before do - expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) - deploy_to_latest_with_callback_tracking.run_action(:deploy) - end - - include_examples "a recovered deployment" - end - - context "when cloning the app code" do - - class BadTimeScmProvider - def initialize(new_resource, run_context) - end - - def load_current_resource - end - - def revision_slug - "5" - end - - def run_action(action) - raise "network error" - end - end - - let(:deploy_that_fails) do - resource = deploy_to_latest_rev.dup - resource.scm_provider(BadTimeScmProvider) - resource - end - - before do - expect { deploy_that_fails.run_action(:deploy) }.to raise_error(RuntimeError, /network error/) - deploy_to_latest_with_callback_tracking.run_action(:deploy) - end - - include_examples "a recovered deployment" - end - - context "and then is deployed to a different revision" do - - let(:deploy_that_fails) do - resource = deploy_to_previous_rev.dup - resource.after_restart { |x| raise Exception, "I am a failed deploy" } - resource - end - - before do - expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Exception, %r{I am a failed deploy}) - deploy_to_latest_rev.run_action(:deploy) - end - - it "removes the unsuccessful deploy after a later successful deploy" do - expect(::File).not_to exist(File.join(deploy_directory, "releases", previous_rev)) - end - - end - - end -end diff --git a/spec/functional/resource/directory_spec.rb b/spec/functional/resource/directory_spec.rb index 0c1345d57f..b4791226f8 100644 --- a/spec/functional/resource/directory_spec.rb +++ b/spec/functional/resource/directory_spec.rb @@ -1,6 +1,6 @@ # # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/functional/resource/dnf_package_spec.rb b/spec/functional/resource/dnf_package_spec.rb new file mode 100644 index 0000000000..e0a69da4f9 --- /dev/null +++ b/spec/functional/resource/dnf_package_spec.rb @@ -0,0 +1,1277 @@ +# +# 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" +require "chef/mixin/shell_out" + +# run this test only for following platforms. +exclude_test = !(%w{rhel fedora amazon}.include?(ohai[:platform_family]) && File.exist?("/usr/bin/dnf")) +describe Chef::Resource::DnfPackage, :requires_root, external: exclude_test do + include Chef::Mixin::ShellOut + + # NOTE: every single test here either needs to explicitly call flush_cache or needs to explicitly + # call preinstall (which explicitly calls flush_cache). It is your responsibility to do one or the + # other in order to minimize calling flush_cache a half dozen times per test. + + def flush_cache + # needed on at least fc23/fc24 sometimes to deal with the dnf cache getting out of sync with the rpm db + FileUtils.rm_f "/var/cache/dnf/@System.solv" + Chef::Resource::DnfPackage.new("shouldnt-matter", run_context).run_action(:flush_cache) + end + + def preinstall(*rpms) + rpms.each do |rpm| + shell_out!("rpm -ivh #{CHEF_SPEC_ASSETS}/yumrepo/#{rpm}") + end + flush_cache + end + + before(:all) do + shell_out!("dnf -y install dnf-plugins-core") + end + + before(:each) do + File.open("/etc/yum.repos.d/chef-dnf-localtesting.repo", "w+") do |f| + f.write <<~EOF + [chef-dnf-localtesting] + name=Chef DNF spec testing repo + baseurl=file://#{CHEF_SPEC_ASSETS}/yumrepo + enable=1 + gpgcheck=0 + EOF + end + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | grep chef_rpm | xargs -r rpm -e") + # next line is useful cleanup if you happen to have been testing both yum + dnf func tests on the same box and + # have some yum garbage left around + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" + end + + after(:all) do + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | grep chef_rpm | xargs -r rpm -e") + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + end + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + + let(:package_name) { "chef_rpm" } + let(:dnf_package) { Chef::Resource::DnfPackage.new(package_name, run_context) } + + def pkg_arch + ohai[:kernel][:machine] + end + + describe ":install" do + context "vanilla use case" do + let(:package_name) { "chef_rpm" } + + it "installs if the package is not installed" do + flush_cache + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "does not install twice" do + flush_cache + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "does not install if the i686 package is installed", :intel_64bit do + skip "FIXME: do nothing, or install the #{pkg_arch} version?" + preinstall("chef_rpm-1.10-1.i686.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + end + + it "does not install if the prior version i686 package is installed", :intel_64bit do + skip "FIXME: do nothing, or install the #{pkg_arch} version?" + preinstall("chef_rpm-1.2-1.i686.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$") + end + end + + context "expanded idempotency checks with version variants" do + %w{1.10 1* 1.10-1 1*-1 1.10-* 1*-* 0:1.10 0:1* 0:1.10-1 0:1*-1 *:1.10-* *:1*-*}.each do |vstring| + it "installs the rpm when #{vstring} is in the package_name" do + flush_cache + dnf_package.package_name("chef_rpm-#{vstring}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + end + + it "is idempotent when #{vstring} is in the package_name" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-#{vstring}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs the rpm when #{vstring} is in the version property" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version(vstring) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + end + + it "is idempotent when #{vstring} is in the version property" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version(vstring) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "upgrades the rpm when #{vstring} is in the package_name" do + flush_cache + dnf_package.package_name("chef_rpm-#{vstring}") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + end + + it "is idempotent when #{vstring} is in the package_name" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-#{vstring}") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "upgrades the rpm when #{vstring} is in the version property" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version(vstring) + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + end + + it "is idempotent when #{vstring} is in the version property" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version(vstring) + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + %w{1.2 1* 1.2-1 1*-1 1.2-* 1*-* 0:1.2 0:1* 0:1.2-1 0:1*-1 *:1.2-* *:1*-*}.each do |vstring| + it "is idempotent when #{vstring} is in the version property and there is a candidate version" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version(vstring) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + %w{1.2 1.2-1 1.2-* 0:1.2 0:1.2-1 *:1.2-*}.each do |vstring| + it "is idempotent when #{vstring} is in the version property on upgrade and it doesn't match the candidate version" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version(vstring) + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + %w{1* 1*-1 1*-* 0:1* 0:1*-1 *:1*-*}.each do |vstring| + it "upgrades when #{vstring} is in the version property on upgrade and it matches the candidate version" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version(vstring) + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + end + end + + context "with versions or globs in the name" do + it "works with a version" do + flush_cache + dnf_package.package_name("chef_rpm-1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "works with an older version" do + flush_cache + dnf_package.package_name("chef_rpm-1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "works with an evra" do + flush_cache + dnf_package.package_name("chef_rpm-0:1.2-1.#{pkg_arch}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "works with version and release" do + flush_cache + dnf_package.package_name("chef_rpm-1.2-1") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "works with a version glob" do + flush_cache + dnf_package.package_name("chef_rpm-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "works with a name glob + version glob" do + flush_cache + dnf_package.package_name("chef_rp*-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "upgrades when the installed version does not match the version string" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "downgrades when the installed version is higher than the package_name version" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + # version only matches the actual dnf version, does not work with epoch or release or combined evr + context "with version property" do + it "matches the full version" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "matches with a glob" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "matches the vr" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10-1") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "matches the evr" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("0:1.10-1") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "matches with a vr glob", :rhel_gte_8 do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "matches with an evr glob", :rhel_gte_8 do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.version("0:1.10-1*") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "downgrades" do + it "downgrades the package when allow_downgrade" do + flush_cache + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version("1.2-1") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "with arches", :intel_64bit do + it "installs with 64-bit arch in the name" do + flush_cache + dnf_package.package_name("chef_rpm.#{pkg_arch}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs with 32-bit arch in the name" do + flush_cache + dnf_package.package_name("chef_rpm.i686") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs with 64-bit arch in the property" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.arch((pkg_arch).to_s) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs with 32-bit arch in the property" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.arch("i686") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs when the 32-bit arch is in the name and the version is in the property" do + flush_cache + dnf_package.package_name("chef_rpm.i686") + dnf_package.version("1.10-1") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs when the 64-bit arch is in the name and the version is in the property" do + flush_cache + dnf_package.package_name("chef_rpm.#{pkg_arch}") + dnf_package.version("1.10-1") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "with constraints" do + it "with nothing installed, it installs the latest version" do + flush_cache + dnf_package.package_name("chef_rpm >= 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm >= 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm >= 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with nothing installed, it installs the latest version" do + flush_cache + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "when it is not met by an installed rpm, it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with an equality constraint, when it is not met by an installed rpm, it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm = 1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with an equality constraint, when it is met by an installed rpm, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm = 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "when it is met by an installed rpm, it does nothing" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "when there is no solution to the contraint" do + flush_cache + dnf_package.package_name("chef_rpm > 2.0") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "when there is no solution to the contraint but an rpm is preinstalled" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm > 2.0") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "with a less than constraint, when nothing is installed, it installs" do + flush_cache + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a less than constraint, when the install version matches, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a less than constraint, when the install version fails, it should downgrade" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "with source arguments" do + it "raises an exception when the package does not exist" do + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "does not raise a hard exception in why-run mode when the package does not exist" do + Chef::Config[:why_run] = true + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") + dnf_package.run_action(:install) + expect { dnf_package.run_action(:install) }.not_to raise_error + end + + it "installs the package when using the source argument" do + flush_cache + dnf_package.name "something" + dnf_package.package_name "somethingelse" + dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs the package when the name is a path to a file" do + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "downgrade on a local file with allow_downgrade true works" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.version "1.2-1" + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "does not downgrade the package with :install" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not upgrade the package with :install" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed and there is a version string" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.version "1.2-1" + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with a local source" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "multipackage with arches", :intel_64bit do + it "installs two rpms" do + flush_cache + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "does nothing if both are installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") + flush_cache + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs the second rpm if the first is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs the first rpm if the second is installed" do + preinstall("chef_rpm-1.10-1.i686.rpm") + dnf_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs two rpms with multi-arch" do + flush_cache + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch([pkg_arch, "i686"]) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs the second rpm if the first is installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch([pkg_arch, "i686"]) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs the first rpm if the second is installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch([pkg_arch, "i686"]) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "does nothing if both are installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch([pkg_arch, "i686"]) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "repo controls" do + it "should fail with the repo disabled" do + flush_cache + dnf_package.options("--disablerepo=chef-dnf-localtesting") + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "should work with disablerepo first" do + flush_cache + dnf_package.options(["--disablerepo=*", "--enablerepo=chef-dnf-localtesting"]) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "should work to enable a disabled repo" do + shell_out!("dnf config-manager --set-disabled chef-dnf-localtesting") + flush_cache + expect { dnf_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + flush_cache + dnf_package.options("--enablerepo=chef-dnf-localtesting") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "when an idempotent install action is run, does not leave repos disabled" do + flush_cache + # this is a bit tricky -- we need this action to be idempotent, so that it doesn't recycle any + # caches, but need it to hit whatavailable with the repo disabled. using :upgrade like this + # accomplishes both those goals (it would be easier if we had other rpms in this repo, but with + # one rpm we need to do this). + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.options("--disablerepo=chef-dnf-localtesting") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + # now we're still using the same cache in the dnf_helper.py cache and we test to see if the + # repo that we temporarily disabled is enabled on this pass. + dnf_package.package_name("chef_rpm-1.10-1.#{pkg_arch}") + dnf_package.options(nil) + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + end + + describe ":upgrade" do + context "downgrades" do + it "just work with DNF" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.version("1.2") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "throws a deprecation warning with allow_downgrade" do + Chef::Config[:treat_deprecation_warnings_as_errors] = false + expect(Chef).to receive(:deprecated).with(:dnf_package_allow_downgrade, /^the allow_downgrade property on the dnf_package provider is not used/) + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.version("1.2") + dnf_package.allow_downgrade true + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "with source arguments" do + it "installs the package when using the source argument" do + flush_cache + dnf_package.name "something" + dnf_package.package_name "somethingelse" + dnf_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "installs the package when the name is a path to a file" do + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "downgrades the package when allow_downgrade is true" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "upgrades the package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with a local source" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + flush_cache + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "version pinning" do + it "with a full version pin it installs a later package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10-1") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a full version pin in the name it downgrades the package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version("1.2-1") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a partial (no release) version pin it installs a later package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version("1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a partial (no release) version pin in the name it downgrades the package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm") + dnf_package.version("1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a full version pin it installs a later package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.10-1") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a full version pin in the name it downgrades the package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.2-1") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a partial (no release) version pin it installs a later package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a partial (no release) version pin in the name it downgrades the package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm-1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a prco equality pin in the name it upgrades a prior package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm = 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a prco equality pin in the name it downgrades a later package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm = 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a > pin in the name and no rpm installed it installs" do + flush_cache + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a < pin in the name and no rpm installed it installs" do + flush_cache + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a > pin in the name and matching rpm installed it does nothing" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a < pin in the name and no rpm installed it installs" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a > pin in the name and non-matching rpm installed it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm > 1.2") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "with a < pin in the name and non-matching rpm installed it downgrades" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.package_name("chef_rpm < 1.10") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + end + end + end + + describe ":remove" do + context "vanilla use case" do + let(:package_name) { "chef_rpm" } + it "does nothing if the package is not installed" do + flush_cache + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "does not remove the package twice" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "removes the package if the i686 package is installed", :intel_64bit do + skip "FIXME: should this be fixed or is the current behavior correct?" + preinstall("chef_rpm-1.10-1.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "removes the package if the prior version i686 package is installed", :intel_64bit do + skip "FIXME: should this be fixed or is the current behavior correct?" + preinstall("chef_rpm-1.2-1.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "with 64-bit arch", :intel_64bit do + let(:package_name) { "chef_rpm.#{pkg_arch}" } + it "does nothing if the package is not installed" do + flush_cache + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "removes the package if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "does nothing if the i686 package is installed" do + preinstall("chef_rpm-1.10-1.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + end + + it "does nothing if the prior version i686 package is installed" do + preinstall("chef_rpm-1.2-1.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$") + end + end + + context "with 32-bit arch", :intel_64bit do + let(:package_name) { "chef_rpm.i686" } + it "removes only the 32-bit arch if both are installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + end + end + end + + describe ":lock and :unlock" do + before(:all) do + shell_out("dnf -y install python3-dnf-plugin-versionlock") + end + + before(:each) do + shell_out("dnf versionlock delete 'chef_rpm-*'") # will exit with error when nothing is locked, we don't care + end + + it "locks an rpm" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "does not lock if its already locked" do + flush_cache + shell_out!("dnf versionlock add chef_rpm") + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:") + end + + it "unlocks an rpm" do + flush_cache + shell_out!("dnf versionlock add chef_rpm") + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "does not unlock an already locked rpm" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:") + end + + it "check that we can lock based on provides" do + flush_cache + dnf_package.package_name("chef_rpm_provides") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).to match("^chef_rpm-0:") + dnf_package.run_action(:lock) + expect(dnf_package.updated_by_last_action?).to be false + end + + it "check that we can unlock based on provides" do + flush_cache + shell_out!("dnf versionlock add chef_rpm") + dnf_package.package_name("chef_rpm_provides") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("dnf versionlock list").stdout.chomp).not_to match("^chef_rpm-0:") + dnf_package.run_action(:unlock) + expect(dnf_package.updated_by_last_action?).to be false + end + end +end diff --git a/spec/functional/resource/dpkg_package_spec.rb b/spec/functional/resource/dpkg_package_spec.rb index 1988fd0c7d..0a8202127c 100644 --- a/spec/functional/resource/dpkg_package_spec.rb +++ b/spec/functional/resource/dpkg_package_spec.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -273,7 +273,7 @@ describe Chef::Resource::DpkgPackage, :requires_root, :debian_family_only, arch: it "should remove both packages when called with two" do shell_out!("dpkg -i #{test1_0} #{test2_0}") - set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] + set_dpkg_package_name %w{chef-integration-test chef-integration-test2} dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") @@ -282,7 +282,7 @@ describe Chef::Resource::DpkgPackage, :requires_root, :debian_family_only, arch: it "should remove a package when only the first one is installed" do shell_out!("dpkg -i #{test1_0}") - set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] + set_dpkg_package_name %w{chef-integration-test chef-integration-test2} dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") @@ -291,7 +291,7 @@ describe Chef::Resource::DpkgPackage, :requires_root, :debian_family_only, arch: it "should remove a package when only the second one is installed" do shell_out!("dpkg -i #{test2_0}") - set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] + set_dpkg_package_name %w{chef-integration-test chef-integration-test2} dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") @@ -299,7 +299,7 @@ describe Chef::Resource::DpkgPackage, :requires_root, :debian_family_only, arch: end it "should do nothing when both packages are not installed" do - set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] + set_dpkg_package_name %w{chef-integration-test chef-integration-test2} dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") diff --git a/spec/functional/resource/dsc_resource_spec.rb b/spec/functional/resource/dsc_resource_spec.rb index bb3cf2157d..227811a5ef 100644 --- a/spec/functional/resource/dsc_resource_spec.rb +++ b/spec/functional/resource/dsc_resource_spec.rb @@ -1,6 +1,6 @@ # # Author:: Jay Mundrawala (<jdm@chef.io>) -# Copyright:: Copyright 2015-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,11 +33,11 @@ describe Chef::Resource::DscResource, :windows_powershell_dsc_only do Chef::Resource::DscResource.new("dsc_resource_test", run_context) end - context "when Powershell does not support Invoke-DscResource" - context "when Powershell supports Invoke-DscResource" do + context "when PowerShell does not support Invoke-DscResource" + context "when PowerShell supports Invoke-DscResource" do before do if !Chef::Platform.supports_dsc_invoke_resource?(node) - skip "Requires Powershell >= 5.0.10018.0" + skip "Requires PowerShell >= 5.0.10018.0" elsif !Chef::Platform.supports_refresh_mode_enabled?(node) && !Chef::Platform.dsc_refresh_mode_disabled?(node) skip "Requires LCM RefreshMode is Disabled" end @@ -46,7 +46,8 @@ describe Chef::Resource::DscResource, :windows_powershell_dsc_only do it "raises an exception if the resource is not found" do new_resource.resource "thisdoesnotexist" expect { new_resource.run_action(:run) }.to raise_error( - Chef::Exceptions::ResourceNotFound) + Chef::Exceptions::ResourceNotFound + ) end end @@ -61,7 +62,7 @@ describe Chef::Resource::DscResource, :windows_powershell_dsc_only do end after do - File.delete(tmp_file_name) if File.exists? tmp_file_name + File.delete(tmp_file_name) if File.exist? tmp_file_name end it "converges the resource if it is not converged" do diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb index e2b58f6432..b22599266b 100644 --- a/spec/functional/resource/dsc_script_spec.rb +++ b/spec/functional/resource/dsc_script_spec.rb @@ -1,6 +1,6 @@ # # Author:: Adam Edwards (<adamed@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,13 @@ # require "spec_helper" -require "chef/mixin/powershell_out" -require "chef/mixin/shell_out" +require "chef/mixin/powershell_exec" require "chef/mixin/windows_architecture_helper" require "support/shared/integration/integration_helper" -describe Chef::Resource::DscScript, :windows_powershell_dsc_only do +describe Chef::Resource::DscScript, :windows_powershell_dsc_only, :ruby64_only do include Chef::Mixin::WindowsArchitectureHelper - include Chef::Mixin::PowershellOut + include Chef::Mixin::PowershellExec before(:all) do @temp_dir = ::Dir.mktmpdir("dsc-functional-test") # enable the HTTP listener if it is not already enabled needed by underlying DSC engine @@ -34,7 +33,7 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do winrm create winrm/config/Listener?Address=*+Transport=HTTP } CODE - powershell_out!(winrm_code) + powershell_exec!(winrm_code) end after(:all) do @@ -65,10 +64,8 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do end def delete_user(target_user) - begin - shell_out!("net user #{target_user} /delete") - rescue Mixlib::ShellOut::ShellCommandFailed - end + shell_out!("net user #{target_user} /delete") + rescue Mixlib::ShellOut::ShellCommandFailed end let(:dsc_env_variable) { "chefenvtest" } @@ -76,10 +73,11 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do let(:env_value2) { "value2" } let(:dsc_test_run_context) do node = Chef::Node.new + node.consume_external_attrs(OHAI_SYSTEM.data, {}) # node[:languages][:powershell][:version] + node.automatic["os"] = "windows" node.automatic["platform"] = "windows" node.automatic["platform_version"] = "6.1" node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported - node.automatic[:languages][:powershell][:version] = "4.0" empty_events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, empty_events) end @@ -104,7 +102,7 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do ValueData = '#{test_registry_data}' Ensure = 'Present' } -EOH + EOH end let(:dsc_code) { dsc_reg_code } @@ -112,7 +110,7 @@ EOH <<-EOH param($testregkeyname, $testregvaluename) #{dsc_reg_code} -EOH + EOH end let(:dsc_user_prefix) { "dsc" } @@ -139,7 +137,7 @@ EOH $#{dsc_user_prefix_param_name}, $#{dsc_user_suffix_param_name} ) -EOH + EOH end let(:config_param_section) { "" } @@ -148,59 +146,59 @@ EOH let(:dsc_user_suffix_code) { dsc_user_suffix } let(:dsc_script_environment_attribute) { nil } let(:dsc_user_resources_code) do - <<-EOH - #{config_param_section} -node localhost -{ -$testuser = #{dsc_user_code} -$testpassword = ConvertTo-SecureString -String "jf9a8m49jrajf4#" -AsPlainText -Force -$testcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $testuser, $testpassword - -User dsctestusercreate -{ - UserName = $testuser - Password = $testcred - Description = "DSC test user" - Ensure = "Present" - Disabled = $false - PasswordNeverExpires = $true - PasswordChangeRequired = $false -} -} -EOH + <<~EOH + #{config_param_section} + node localhost + { + $testuser = #{dsc_user_code} + $testpassword = ConvertTo-SecureString -String "jf9a8m49jrajf4#" -AsPlainText -Force + $testcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $testuser, $testpassword + + User dsctestusercreate + { + UserName = $testuser + Password = $testcred + Description = "DSC test user" + Ensure = "Present" + Disabled = $false + PasswordNeverExpires = $true + PasswordChangeRequired = $false + } + } + EOH end let(:dsc_user_config_data) do - <<-EOH -@{ - AllNodes = @( - @{ - NodeName = "localhost"; - PSDscAllowPlainTextPassword = $true - } - ) -} + <<~EOH + @{ + AllNodes = @( + @{ + NodeName = "localhost"; + PSDscAllowPlainTextPassword = $true + } + ) + } -EOH + EOH end let(:dsc_environment_env_var_name) { "dsc_test_cwd" } - let(:dsc_environment_no_fail_not_etc_directory) { "#{ENV['systemroot']}\\system32" } - let(:dsc_environment_fail_etc_directory) { "#{ENV['systemroot']}\\system32\\drivers\\etc" } + let(:dsc_environment_no_fail_not_etc_directory) { "#{ENV["systemroot"]}\\system32" } + let(:dsc_environment_fail_etc_directory) { "#{ENV["systemroot"]}\\system32\\drivers\\etc" } let(:exception_message_signature) { "LL927-LL928" } let(:dsc_environment_config) do - <<-EOH -if (($pwd.path -eq '#{dsc_environment_fail_etc_directory}') -and (test-path('#{dsc_environment_fail_etc_directory}'))) -{ - throw 'Signature #{exception_message_signature}: Purposefully failing because cwd == #{dsc_environment_fail_etc_directory}' -} -environment "whatsmydir" -{ - Name = '#{dsc_environment_env_var_name}' - Value = $pwd.path - Ensure = 'Present' -} -EOH + <<~EOH + if (($pwd.path -eq '#{dsc_environment_fail_etc_directory}') -and (test-path('#{dsc_environment_fail_etc_directory}'))) + { + throw 'Signature #{exception_message_signature}: Purposefully failing because cwd == #{dsc_environment_fail_etc_directory}' + } + environment "whatsmydir" + { + Name = '#{dsc_environment_env_var_name}' + Value = $pwd.path + Ensure = 'Present' + } + EOH end let(:dsc_config_name) do @@ -235,7 +233,7 @@ EOH expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(false) dsc_test_resource.run_action(:run) expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(true) - expect(dsc_test_resource.registry_value_exists?(test_registry_key, { :name => test_registry_value, :type => :string, :data => test_registry_data })).to eq(true) + expect(dsc_test_resource.registry_value_exists?(test_registry_key, { name: test_registry_value, type: :string, data: test_registry_data })).to eq(true) end it_should_behave_like "a dsc_script resource with configuration affected by cwd" @@ -244,13 +242,13 @@ EOH shared_examples_for "a dsc_script resource with configuration affected by cwd" do after(:each) do removal_resource = Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) - removal_resource.code <<-EOH -environment 'removethis' -{ - Name = '#{dsc_environment_env_var_name}' - Ensure = 'Absent' -} -EOH + removal_resource.code <<~EOH + environment 'removethis' + { + Name = '#{dsc_environment_env_var_name}' + Ensure = 'Absent' + } + EOH removal_resource.run_action(:run) end @@ -263,12 +261,9 @@ EOH it "should raise an exception if the cwd is etc" do dsc_test_resource.cwd(dsc_environment_fail_etc_directory) - expect { dsc_test_resource.run_action(:run) }.to raise_error(Chef::Exceptions::PowershellCmdletException) - begin + expect { dsc_test_resource.run_action(:run) - rescue Chef::Exceptions::PowershellCmdletException => e - expect(e.message).to match(exception_message_signature) - end + }.to raise_error(Chef::PowerShell::CommandFailed, /#{exception_message_signature}/) end end end @@ -311,11 +306,11 @@ EOH it "should set a registry key according to parameters passed to the configuration" do dsc_test_resource.configuration_name(config_name_value) - dsc_test_resource.flags({ :"#{reg_key_name_param_name}" => test_registry_key, :"#{reg_key_value_param_name}" => test_registry_value }) + dsc_test_resource.flags({ "#{reg_key_name_param_name}": test_registry_key, "#{reg_key_value_param_name}": test_registry_value }) expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(false) dsc_test_resource.run_action(:run) expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(true) - expect(dsc_test_resource.registry_value_exists?(test_registry_key, { :name => test_registry_value, :type => :string, :data => test_registry_data })).to eq(true) + expect(dsc_test_resource.registry_value_exists?(test_registry_key, { name: test_registry_value, type: :string, data: test_registry_data })).to eq(true) end end end @@ -348,11 +343,9 @@ EOH shared_examples_for "a dsc_script with configuration data that takes parameters" do let(:dsc_user_code) { dsc_user_param_code } let(:config_param_section) { config_params } - let(:config_flags) { { :"#{dsc_user_prefix_param_name}" => "#{dsc_user_prefix}", :"#{dsc_user_suffix_param_name}" => "#{dsc_user_suffix}" } } + let(:config_flags) { { "#{dsc_user_prefix_param_name}": (dsc_user_prefix).to_s, "#{dsc_user_suffix_param_name}": (dsc_user_suffix).to_s } } it "does not directly contain the user name" do - configuration_script_content = ::File.open(dsc_test_resource.command) do |file| - file.read - end + configuration_script_content = ::File.open(dsc_test_resource.command, &:read) expect(configuration_script_content.include?(dsc_user)).to be(false) end it_behaves_like "a dsc_script with configuration data" @@ -362,9 +355,7 @@ EOH let(:dsc_user_code) { dsc_user_env_code } it "does not directly contain the user name" do - configuration_script_content = ::File.open(dsc_test_resource.command) do |file| - file.read - end + configuration_script_content = ::File.open(dsc_test_resource.command, &:read) expect(configuration_script_content.include?(dsc_user)).to be(false) end it_behaves_like "a dsc_script with configuration data" @@ -410,46 +401,46 @@ EOH end let(:dsc_configuration_script) do - <<-MYCODE -cd c:\\ -configuration LCM -{ - param ($thumbprint) - localconfigurationmanager - { - RebootNodeIfNeeded = $false - ConfigurationMode = 'ApplyOnly' - CertificateID = $thumbprint - } -} -$cert = ls Cert:\\LocalMachine\\My\\ | - Where-Object {$_.Subject -match "ChefTest"} | - Select -first 1 - -if($cert -eq $null) { - $pfxpath = '#{self_signed_cert_path}' - $password = '' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxpath, $password, ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset)) - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "My", ([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) - $store.Add($cert) - $store.Close() -} - -lcm -thumbprint $cert.thumbprint -set-dsclocalconfigurationmanager -path ./LCM -$ConfigurationData = @" -@{ -AllNodes = @( - @{ - NodeName = "localhost"; - CertificateID = '$($cert.thumbprint)'; - }; -); -} -"@ -$ConfigurationData | out-file '#{configuration_data_path}' -force - MYCODE + <<~MYCODE + cd c:\\ + configuration LCM + { + param ($thumbprint) + localconfigurationmanager + { + RebootNodeIfNeeded = $false + ConfigurationMode = 'ApplyOnly' + CertificateID = $thumbprint + } + } + $cert = ls Cert:\\LocalMachine\\My\\ | + Where-Object {$_.Subject -match "ChefTest"} | + Select -first 1 + + if($cert -eq $null) { + $pfxpath = '#{self_signed_cert_path}' + $password = '' + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxpath, $password, ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset)) + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "My", ([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + $store.Add($cert) + $store.Close() + } + + lcm -thumbprint $cert.thumbprint + set-dsclocalconfigurationmanager -path ./LCM + $ConfigurationData = @" + @{ + AllNodes = @( + @{ + NodeName = "localhost"; + CertificateID = '$($cert.thumbprint)'; + }; + ); + } + "@ + $ConfigurationData | out-file '#{configuration_data_path}' -force + MYCODE end let(:powershell_script_resource) do @@ -461,14 +452,14 @@ $ConfigurationData | out-file '#{configuration_data_path}' -force let(:dsc_script_resource) do dsc_test_resource_base.tap do |r| - r.code <<-EOF -User dsctestusercreate -{ - UserName = '#{dsc_user}' - Password = #{r.ps_credential('jf9a8m49jrajf4#')} - Ensure = "Present" -} -EOF + r.code <<~EOF + User dsctestusercreate + { + UserName = '#{dsc_user}' + Password = #{r.ps_credential("jf9a8m49jrajf4#")} + Ensure = "Present" + } + EOF r.configuration_data_script(configuration_data_path) end end diff --git a/spec/functional/resource/env_spec.rb b/spec/functional/resource/env_spec.rb deleted file mode 100755 index 4b0ff70c0b..0000000000 --- a/spec/functional/resource/env_spec.rb +++ /dev/null @@ -1,192 +0,0 @@ -# -# Author:: Adam Edwards (<adamed@chef.io>) -# Copyright:: Copyright 2014-2016, 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::Resource::Env, :windows_only do - context "when running on Windows" do - let(:chef_env_test_lower_case) { "chefenvtest" } - let(:chef_env_test_mixed_case) { "chefENVtest" } - let(:env_dne_key) { "env_dne_key" } - let(:env_value1) { "value1" } - let(:env_value2) { "value2" } - - let(:env_value_expandable) { "%SystemRoot%" } - let(:test_run_context) do - node = Chef::Node.new - node.default["os"] = "windows" - node.default["platform"] = "windows" - node.default["platform_version"] = "6.1" - empty_events = Chef::EventDispatch::Dispatcher.new - Chef::RunContext.new(node, {}, empty_events) - end - let(:test_resource) do - Chef::Resource::Env.new("unknown", test_run_context) - end - - before(:each) do - resource_lower = Chef::Resource::Env.new(chef_env_test_lower_case, test_run_context) - resource_lower.run_action(:delete) - resource_mixed = Chef::Resource::Env.new(chef_env_test_mixed_case, test_run_context) - resource_mixed.run_action(:delete) - end - - context "when the create action is invoked" do - it "should create an environment variable for action create" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - end - - it "should modify an existing variable's value to a new value" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.value(env_value2) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.key_name(chef_env_test_mixed_case) - test_resource.value(env_value2) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - it "should not expand environment variables if the variable is not PATH" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value_expandable) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) - end - end - - context "when the modify action is invoked" do - it "should raise an exception for modify if the variable doesn't exist" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - expect { test_resource.run_action(:modify) }.to raise_error(Chef::Exceptions::Env) - end - - it "should modify an existing variable's value to a new value" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.value(env_value2) - test_resource.run_action(:modify) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - # This examlpe covers Chef Issue #1754 - it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.key_name(chef_env_test_mixed_case) - test_resource.value(env_value2) - test_resource.run_action(:modify) - expect(ENV[chef_env_test_lower_case]).to eq(env_value2) - end - - it "should not expand environment variables if the variable is not PATH" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.value(env_value_expandable) - test_resource.run_action(:modify) - expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) - end - - context "when using PATH" do - let(:random_name) { Time.now.to_i } - let(:env_val) { "#{env_value_expandable}_#{random_name}" } - let!(:path_before) { test_resource.provider_for_action(test_resource.action).env_value("PATH") || "" } - let!(:env_path_before) { ENV["PATH"] } - - it "should expand PATH" do - expect(path_before).not_to include(env_val) - test_resource.key_name("PATH") - test_resource.value("#{path_before};#{env_val}") - test_resource.run_action(:create) - expect(ENV["PATH"]).not_to include(env_val) - expect(ENV["PATH"]).to include("#{random_name}") - end - - after(:each) do - # cleanup so we don't flood the path - test_resource.key_name("PATH") - test_resource.value(path_before) - test_resource.run_action(:create) - ENV["PATH"] = env_path_before - end - end - - end - - context "when the delete action is invoked" do - it "should delete an environment variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.run_action(:delete) - expect(ENV[chef_env_test_lower_case]).to eq(nil) - end - - it "should not raise an exception when a non-existent environment variable is deleted" do - expect(ENV[chef_env_test_lower_case]).to eq(nil) - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - expect { test_resource.run_action(:delete) }.not_to raise_error - expect(ENV[chef_env_test_lower_case]).to eq(nil) - end - - it "should delete an existing variable's value to a new value if the specified variable name case differs from the existing variable" do - test_resource.key_name(chef_env_test_lower_case) - test_resource.value(env_value1) - test_resource.run_action(:create) - expect(ENV[chef_env_test_lower_case]).to eq(env_value1) - test_resource.key_name(chef_env_test_mixed_case) - test_resource.run_action(:delete) - expect(ENV[chef_env_test_lower_case]).to eq(nil) - expect(ENV[chef_env_test_mixed_case]).to eq(nil) - end - - it "should delete a value from the current process even if it is not in the registry" do - expect(ENV[env_dne_key]).to eq(nil) - ENV[env_dne_key] = env_value1 - test_resource.key_name(env_dne_key) - test_resource.run_action(:delete) - expect(ENV[env_dne_key]).to eq(nil) - end - end - end -end diff --git a/spec/functional/resource/execute_spec.rb b/spec/functional/resource/execute_spec.rb index 3c31537ebe..3d7e185e17 100644 --- a/spec/functional/resource/execute_spec.rb +++ b/spec/functional/resource/execute_spec.rb @@ -1,6 +1,6 @@ # # Author:: Serdar Sutay (<serdar@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,11 @@ # require "spec_helper" -require "functional/resource/base" require "timeout" describe Chef::Resource::Execute do let(:resource) do + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) resource = Chef::Resource::Execute.new("foo_resource", run_context) resource.command("echo hello") resource @@ -87,8 +87,8 @@ describe Chef::Resource::Execute do describe "when parent resource sets :environment" do before do resource.environment({ - "SAWS_SECRET" => "supersecret", - "SAWS_KEY" => "qwerty", + "SAWS_SECRET" => "supersecret", + "SAWS_KEY" => "qwerty", }) end @@ -106,7 +106,7 @@ describe Chef::Resource::Execute do it "guard adds additional values in its :environment and runs" do resource.only_if %{ruby -e 'exit 1 if ENV["SGCE_SECRET"] != "regularsecret"'}, { - :environment => { "SGCE_SECRET" => "regularsecret" }, + environment: { "SGCE_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).to be_updated_by_last_action @@ -114,7 +114,7 @@ describe Chef::Resource::Execute do it "guard adds additional values in its :environment and does not run" do resource.only_if %{ruby -e 'exit 1 if ENV["SGCE_SECRET"] == "regularsecret"'}, { - :environment => { "SGCE_SECRET" => "regularsecret" }, + environment: { "SGCE_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).not_to be_updated_by_last_action @@ -122,7 +122,7 @@ describe Chef::Resource::Execute do it "guard overwrites value with its :environment and runs" do resource.only_if %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] != "regularsecret"'}, { - :environment => { "SAWS_SECRET" => "regularsecret" }, + environment: { "SAWS_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).to be_updated_by_last_action @@ -130,13 +130,25 @@ describe Chef::Resource::Execute do it "guard overwrites value with its :environment and does not runs" do resource.only_if %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] == "regularsecret"'}, { - :environment => { "SAWS_SECRET" => "regularsecret" }, + environment: { "SAWS_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).not_to be_updated_by_last_action end end + describe "when a guard is specified" do + describe "when using the default guard interpreter" do + let(:guard_interpreter_resource) { nil } + it_behaves_like "a resource with a guard specifying an alternate user identity" + end + + describe "when using the execute resource as the guard interpreter" do + let(:guard_interpreter_resource) { :execute } + it_behaves_like "a resource with a guard specifying an alternate user identity" + end + end + # Ensure that CommandTimeout is raised, and is caused by resource.timeout really expiring. # https://github.com/chef/chef/issues/2985 # @@ -151,4 +163,9 @@ describe Chef::Resource::Execute do expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::CommandTimeout) end end + + describe "when running with an alternate user identity" do + let(:resource_command_property) { :command } + it_behaves_like "an execute resource that supports alternate user identity" + end end diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb index 0fa1317032..c0d68e1762 100644 --- a/spec/functional/resource/file_spec.rb +++ b/spec/functional/resource/file_spec.rb @@ -1,6 +1,6 @@ # # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,7 +51,7 @@ describe Chef::Resource::File do end let(:resource_with_relative_path) do - create_resource(:use_relative_path => true) + create_resource(use_relative_path: true) end let(:unmanaged_content) do diff --git a/spec/functional/resource/git_spec.rb b/spec/functional/resource/git_spec.rb index 6808898c29..ab05947d29 100644 --- a/spec/functional/resource/git_spec.rb +++ b/spec/functional/resource/git_spec.rb @@ -1,6 +1,6 @@ # # Author:: Seth Falcon (<seth@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,30 +17,18 @@ # require "spec_helper" -require "chef/mixin/shell_out" require "tmpdir" -require "shellwords" # Deploy relies heavily on symlinks, so it doesn't work on windows. -describe Chef::Resource::Git, :requires_git => true do - include Chef::Mixin::ShellOut - let(:file_cache_path) { Dir.mktmpdir } +describe Chef::Resource::Git do + include RecipeDSLHelper + # Some versions of git complains when the deploy directory is # already created. Here we intentionally don't create the deploy # directory beforehand. let(:base_dir_path) { Dir.mktmpdir } let(:deploy_directory) { File.join(base_dir_path, make_tmpname("git_base")) } - let(:node) do - Chef::Node.new.tap do |n| - n.name "rspec-test" - n.consume_external_attrs(@ohai.data, {}) - end - end - - let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } - let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } - # These tests use git's bundle feature, which is a way to export an entire # git repo (or subset of commits) as a single file. # @@ -64,33 +52,31 @@ describe Chef::Resource::Git, :requires_git => true do let(:rev_testing) { "972d153654503bccec29f630c5dd369854a561e8" } let(:rev_head) { "d294fbfd05aa7709ad9a9b8ef6343b17d355bf5f" } - let(:git_user_config) do - <<-E -[user] - name = frodoTbaggins - email = frodo@shire.org -E - end - before(:each) do - Chef::Log.level = :warn # silence git command live streams - @old_file_cache_path = Chef::Config[:file_cache_path] - shell_out!("git clone \"#{git_bundle_repo}\" example", :cwd => origin_repo_dir) - File.open("#{origin_repo}/.git/config", "a+") { |f| f.print(git_user_config) } - Chef::Config[:file_cache_path] = file_cache_path + shell_out!("git", "clone", git_bundle_repo, "example", cwd: origin_repo_dir) + File.open("#{origin_repo}/.git/config", "a+") do |f| + f.print <<~EOF + [user] + name = frodoTbaggins + email = frodo@shire.org + EOF + end end after(:each) do - Chef::Config[:file_cache_path] = @old_file_cache_path FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory) FileUtils.remove_entry_secure base_dir_path - FileUtils.remove_entry_secure file_cache_path FileUtils.remove_entry_secure origin_repo_dir end - before(:all) do - @ohai = Ohai::System.new - @ohai.all_plugins(%w{platform os}) + def expect_revision_to_be(revision, version) + rev_ver = shell_out!("git", "rev-parse", revision, cwd: deploy_directory).stdout.strip + expect(rev_ver).to eq(version) + end + + def expect_branch_to_be(branch) + head_branch = shell_out!("git name-rev --name-only HEAD", cwd: deploy_directory).stdout.strip + expect(head_branch).to eq(branch) end context "working with pathes with special characters" do @@ -102,156 +88,242 @@ E end it "clones a repository with a space in the path" do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository "#{path_with_spaces}/example-repo.gitbundle" - end.run_action(:sync) + repo = "#{path_with_spaces}/example-repo.gitbundle" + git(deploy_directory) do + repository repo + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) end end context "when deploying from an annotated tag" do - let(:basic_git_resource) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository origin_repo - r.revision "v1.0.0" - end - end - - # We create a copy of the basic_git_resource so that we can run - # the resource again and verify that it doesn't update. - let(:copy_git_resource) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository origin_repo - r.revision "v1.0.0" - end - end - it "checks out the revision pointed to by the tag commit, not the tag commit itself" do - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(head_rev).to eq(v1_commit) + git deploy_directory do + repository origin_repo + revision "v1.0.0" + end.should_be_updated + expect_revision_to_be("HEAD", v1_commit) + expect_branch_to_be("tags/v1.0.0^0") # detached # also verify the tag commit itself is what we expect as an extra sanity check - rev = shell_out!("git rev-parse v1.0.0", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(rev).to eq(v1_tag) + expect_revision_to_be("v1.0.0", v1_tag) end it "doesn't update if up-to-date" do - # this used to fail because we didn't resolve the annotated tag - # properly to the pointed to commit. - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(head_rev).to eq(v1_commit) - - copy_git_resource.run_action(:sync) - expect(copy_git_resource).not_to be_updated + git deploy_directory do + repository origin_repo + revision "v1.0.0" + end.should_be_updated + git deploy_directory do + repository origin_repo + revision "v1.0.0" + expect_branch_to_be("tags/v1.0.0^0") # detached + end.should_not_be_updated end end context "when deploying from a SHA revision" do - let(:basic_git_resource) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository git_bundle_repo - end - end - - # We create a copy of the basic_git_resource so that we can run - # the resource again and verify that it doesn't update. - let(:copy_git_resource) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository origin_repo - end + it "checks out the expected revision ed18" do + git deploy_directory do + repository git_bundle_repo + revision rev_foo + end.should_be_updated + expect_revision_to_be("HEAD", rev_foo) + expect_branch_to_be("master~1") # detached end - it "checks out the expected revision ed18" do - basic_git_resource.revision rev_foo - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(head_rev).to eq(rev_foo) + it "checks out the expected revision ed18 to a local branch" do + git deploy_directory do + repository git_bundle_repo + revision rev_foo + checkout_branch "deploy" + end.should_be_updated + expect_revision_to_be("HEAD", rev_foo) + expect_branch_to_be("deploy") # detached end it "doesn't update if up-to-date" do - basic_git_resource.revision rev_foo - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(head_rev).to eq(rev_foo) - - copy_git_resource.revision rev_foo - copy_git_resource.run_action(:sync) - expect(copy_git_resource).not_to be_updated + git deploy_directory do + repository git_bundle_repo + revision rev_foo + end.should_be_updated + expect_revision_to_be("HEAD", rev_foo) + + git deploy_directory do + repository origin_repo + revision rev_foo + end.should_not_be_updated + expect_branch_to_be("master~1") # detached end it "checks out the expected revision 972d" do - basic_git_resource.revision rev_testing - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(head_rev).to eq(rev_testing) + git deploy_directory do + repository git_bundle_repo + revision rev_testing + end.should_be_updated + expect_revision_to_be("HEAD", rev_testing) + expect_branch_to_be("master~2") # detached + end + + it "checks out the expected revision 972d to a local branch" do + git deploy_directory do + repository git_bundle_repo + revision rev_testing + checkout_branch "deploy" + end.should_be_updated + expect_revision_to_be("HEAD", rev_testing) + expect_branch_to_be("deploy") end end context "when deploying from a revision named 'HEAD'" do - let(:basic_git_resource) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository origin_repo - r.revision "HEAD" - end + it "checks out the expected revision" do + git deploy_directory do + repository origin_repo + revision "HEAD" + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") end - it "checks out the expected revision" do - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(head_rev).to eq(rev_head) + it "checks out the expected revision, and is idempotent" do + git deploy_directory do + repository origin_repo + revision "HEAD" + end.should_be_updated + git deploy_directory do + repository origin_repo + revision "HEAD" + end.should_not_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") + end + + it "checks out the expected revision to a local branch" do + git deploy_directory do + repository origin_repo + revision "HEAD" + checkout_branch "deploy" + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("deploy") end end context "when deploying from the default revision" do - let(:basic_git_resource) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository origin_repo - # use default - end + it "checks out HEAD as the default revision" do + git deploy_directory do + repository origin_repo + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") end - it "checks out HEAD as the default revision" do - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip - expect(head_rev).to eq(rev_head) + it "checks out HEAD as the default revision, and is idempotent" do + git deploy_directory do + repository origin_repo + end.should_be_updated + git deploy_directory do + repository origin_repo + end.should_not_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") + end + + it "checks out HEAD as the default revision to a local branch" do + git deploy_directory do + repository origin_repo + checkout_branch "deploy" + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("deploy") + end + end + + context "when updating a branch that's already checked out out" do + it "checks out master, commits to the repo, and checks out the latest changes" do + git deploy_directory do + repository origin_repo + revision "master" + action :sync + end.should_be_updated + + # We don't have a way to test a commit in the git bundle + # Revert to a previous commit in the same branch and make sure we can still sync. + shell_out!("git", "reset", "--hard", rev_foo, cwd: deploy_directory) + + git deploy_directory do + repository origin_repo + revision "master" + action :sync + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") end end context "when dealing with a repo with a degenerate tag named 'HEAD'" do before do - shell_out!("git tag -m\"degenerate tag\" HEAD ed181b3419b6f489bedab282348162a110d6d3a1", - :cwd => origin_repo) + shell_out!("git", "tag", "-m\"degenerate tag\"", "HEAD", "ed181b3419b6f489bedab282348162a110d6d3a1", cwd: origin_repo) end - let(:basic_git_resource) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository origin_repo - r.revision "HEAD" - end + it "checks out the (master) HEAD revision and ignores the tag" do + git deploy_directory do + repository origin_repo + revision "HEAD" + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") end - let(:git_resource_default_rev) do - Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| - r.repository origin_repo - # use default of revision - end + it "checks out the (master) HEAD revision and ignores the tag, and is idempotent" do + git deploy_directory do + repository origin_repo + revision "HEAD" + end.should_be_updated + git deploy_directory do + repository origin_repo + revision "HEAD" + end.should_not_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") end - it "checks out the (master) HEAD revision and ignores the tag" do - basic_git_resource.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", - :cwd => deploy_directory, - :returns => [0]).stdout.strip - expect(head_rev).to eq(rev_head) + it "checks out the (master) HEAD revision and ignores the tag to a local branch" do + git deploy_directory do + repository origin_repo + revision "HEAD" + checkout_branch "deploy" + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("deploy") end it "checks out the (master) HEAD revision when no revision is specified (ignores tag)" do - git_resource_default_rev.run_action(:sync) - head_rev = shell_out!("git rev-parse HEAD", - :cwd => deploy_directory, - :returns => [0]).stdout.strip - expect(head_rev).to eq(rev_head) + git deploy_directory do + repository origin_repo + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") + end + + it "checks out the (master) HEAD revision when no revision is specified (ignores tag), and is idempotent" do + git deploy_directory do + repository origin_repo + end.should_be_updated + git deploy_directory do + repository origin_repo + end.should_not_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("master") end + it "checks out the (master) HEAD revision when no revision is specified (ignores tag) to a local branch" do + git deploy_directory do + repository origin_repo + checkout_branch "deploy" + end.should_be_updated + expect_revision_to_be("HEAD", rev_head) + expect_branch_to_be("deploy") + end end end diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb index aa5a29f92c..a682e9c0c7 100644 --- a/spec/functional/resource/group_spec.rb +++ b/spec/functional/resource/group_spec.rb @@ -1,7 +1,7 @@ # # Author:: Chirag Jog (<chirag@clogeny.com>) # Author:: Siddheshwar More (<siddheshwar.more@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,19 +18,14 @@ # require "spec_helper" -require "functional/resource/base" require "chef/mixin/shell_out" -# Chef::Resource::Group are turned off on Mac OS X 10.6 due to caching -# issues around Etc.getgrnam() not picking up the group membership -# changes that are done on the system. Etc.endgrent is not functioning -# correctly on certain 10.6 boxes. -describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supported_on_mac_osx_106 do +describe Chef::Resource::Group, :requires_root_or_running_windows do include Chef::Mixin::ShellOut def group_should_exist(group) - case ohai[:platform_family] - when "debian", "fedora", "rhel", "suse", "gentoo", "slackware", "arch" + case ohai[:os] + when "linux" expect { Etc.getgrnam(group) }.not_to raise_error expect(group).to eq(Etc.getgrnam(group).name) when "windows" @@ -54,8 +49,8 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte end def group_should_not_exist(group) - case ohai[:platform_family] - when "debian", "fedora", "rhel", "suse", "gentoo", "slackware", "arch" + case ohai[:os] + when "linux" expect { Etc.getgrnam(group) }.to raise_error(ArgumentError, "can't find group for #{group}") when "windows" expect { Chef::Util::Windows::NetGroup.new(group).local_get_members }.to raise_error(ArgumentError, /The group name could not be found./) @@ -81,7 +76,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte if user && domain != "." computer_name = ENV["computername"] - !domain.casecmp(computer_name.downcase).zero? + !domain.casecmp(computer_name.downcase) == 0 end end @@ -99,13 +94,17 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte usr end - def create_user(username) - user(username).run_action(:create) if ! windows_domain_user?(username) + def create_user(username, uid = nil) + unless windows_domain_user?(username) + user_to_create = user(username) + user_to_create.uid(uid) if uid + user_to_create.run_action(:create) + end # TODO: User should exist end def remove_user(username) - if ! windows_domain_user?(username) + unless windows_domain_user?(username) u = user(username) u.manage_home false # jekins hosts throw mail spool file not owned by user if we use manage_home true u.run_action(:remove) @@ -113,6 +112,15 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte # TODO: User shouldn't exist end + let(:run_context) do + node = Chef::Node.new + node.default[:platform] = ohai[:platform] + node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + end + shared_examples_for "correct group management" do def add_members_to_group(members) temp_resource = group_resource.dup @@ -146,13 +154,15 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte end # dscl doesn't perform any error checking and will let you add users that don't exist. - describe "when no users exist", :not_supported_on_mac_osx do + describe "when no users exist", :not_supported_on_macos do describe "when append is not set" do # excluded_members can only be used when append is set. It is ignored otherwise. let(:excluded_members) { [] } + let(:expected_error_class) { windows? ? ArgumentError : Mixlib::ShellOut::ShellCommandFailed } + it "should raise an error" do - expect { group_resource.run_action(tested_action) }.to raise_error() + expect { group_resource.run_action(tested_action) }.to raise_error(expected_error_class) end end @@ -161,16 +171,21 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte group_resource.append(true) end + let(:expected_error_class) { windows? ? Chef::Exceptions::Win32APIError : Mixlib::ShellOut::ShellCommandFailed } + it "should raise an error" do - expect { group_resource.run_action(tested_action) }.to raise_error() + expect { group_resource.run_action(tested_action) }.to raise_error(expected_error_class) end end end describe "when the users exist" do before do + high_uid = 30000 (spec_members).each do |member| - create_user(member) + remove_user(member) + create_user(member, high_uid) + high_uid += 1 end end @@ -289,13 +304,27 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte end end - let(:group_name) { "group#{SecureRandom.random_number(9999)}" } - let(:included_members) { nil } - let(:excluded_members) { nil } + let(:number) do + # Loop until we pick a gid that is not in use. + loop do + + gid = rand(2000..9999) # avoid low group numbers + return nil if Etc.getgrgid(gid).nil? # returns nil on windows + rescue ArgumentError # group does not exist + return gid + + end + end + + let(:group_name) { "grp#{number}" } # group name should be 8 characters or less for Solaris, and possibly others + # https://community.aegirproject.org/developing/architecture/unix-group-limitations/index.html#Group_name_length_limits + let(:included_members) { [] } + let(:excluded_members) { [] } let(:group_resource) do group = Chef::Resource::Group.new(group_name, run_context) group.members(included_members) group.excluded_members(excluded_members) + group.gid(number) unless ohai[:platform_family] == "mac_os_x" group end @@ -316,10 +345,11 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte describe "when group name is length 256", :windows_only do let!(:group_name) do - "theoldmanwalkingdownthestreetalwayshadagood\ -smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface\ -theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\ -downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree" end + "theoldmanwalkingdownthestreetalwayshadagood"\ + "smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface"\ + "theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking"\ + "downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree" + end it "should create a group" do group_resource.run_action(:create) @@ -327,19 +357,6 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree" end end end - describe "when group name length is more than 256", :windows_only do - let!(:group_name) do - "theoldmanwalkingdownthestreetalwayshadagood\ -smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface\ -theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\ -downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" end - - it "should not create a group" do - expect { group_resource.run_action(:create) }.to raise_error(ArgumentError) - group_should_not_exist(group_name) - end - end - # not_supported_on_solaris because of the use of excluded_members describe "should raise an error when same member is included in the members and excluded_members", :not_supported_on_solaris do it "should raise an error" do @@ -351,6 +368,26 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" end end end + # Note:This testcase is written separately from the `group create action` defined above because + # for group name > 256, Windows 2016 returns "The parameter is incorrect" + context "group create action: when group name length is more than 256", :windows_only do + let!(:group_name) do + "theoldmanwalkingdownthestreetalwayshadagood"\ + "smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface"\ + "theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking"\ + "downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" + end + + it "should not create a group" do + expect { group_resource.run_action(:create) }.to raise_error(ArgumentError) + if windows_gte_10? + expect { Chef::Util::Windows::NetGroup.new(group_name).local_get_members }.to raise_error(ArgumentError, /The parameter is incorrect./) + else + group_should_not_exist(group_name) + end + end + end + describe "group remove action" do describe "when there is a group" do before do @@ -400,7 +437,7 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" end end end - describe "group manage action", :not_supported_on_solaris do + describe "group manage action" do let(:spec_members) { %w{mnou5sdz htulrvwq x4c3g1lu} } let(:included_members) { [spec_members[0], spec_members[1]] } let(:excluded_members) { [spec_members[2]] } @@ -417,6 +454,7 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" end end it "does not raise an error on manage" do + allow(Etc).to receive(:getpwnam).and_return(double("User")) expect { group_resource.run_action(:manage) }.not_to raise_error end end @@ -437,37 +475,4 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" end end end - describe "group resource with Usermod provider", :solaris_only do - describe "when excluded_members is set" do - let(:excluded_members) { ["x4c3g1lu"] } - - it ":manage should raise an error" do - expect { group_resource.run_action(:manage) }.to raise_error - end - - it ":modify should raise an error" do - expect { group_resource.run_action(:modify) }.to raise_error - end - - it ":create should raise an error" do - expect { group_resource.run_action(:create) }.to raise_error - end - end - - describe "when append is not set" do - let(:included_members) { %w{gordon eric} } - - before(:each) do - group_resource.append(false) - end - - it ":manage should raise an error" do - expect { group_resource.run_action(:manage) }.to raise_error - end - - it ":modify should raise an error" do - expect { group_resource.run_action(:modify) }.to raise_error - end - end - end end diff --git a/spec/functional/resource/ifconfig_spec.rb b/spec/functional/resource/ifconfig_spec.rb index a30dcff641..127e4e6331 100644 --- a/spec/functional/resource/ifconfig_spec.rb +++ b/spec/functional/resource/ifconfig_spec.rb @@ -1,6 +1,6 @@ # # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,17 +16,24 @@ # limitations under the License. # -require "functional/resource/base" +require "spec_helper" require "chef/mixin/shell_out" # run this test only for following platforms. -include_flag = !(%w{ubuntu centos aix}.include?(ohai[:platform])) - -describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => include_flag do - # This test does not work in travis because there is no eth0 +include_flag = !(%w{amazon debian aix}.include?(ohai[:platform_family]) || (ohai[:platform_family] == "rhel" && ohai[:platform_version].to_i < 7)) +describe Chef::Resource::Ifconfig, :requires_root, :requires_ifconfig, external: include_flag do include Chef::Mixin::ShellOut + let(:run_context) do + node = Chef::Node.new + node.default[:platform] = ohai[:platform] + node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + end + let(:new_resource) do new_resource = Chef::Resource::Ifconfig.new("10.10.0.1", run_context) new_resource @@ -51,11 +58,17 @@ describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => in end end + def fetch_first_interface_name + shell_out("ip link list |grep UP|grep -vi loop|head -1|cut -d':' -f 2 |cut -d'@' -f 1").stdout.strip + end + # **Caution: any updates to core interfaces can be risky. def en0_interface_for_test case ohai[:platform] when "aix" "en0" + when "ubuntu" + fetch_first_interface_name else "eth0" end @@ -115,7 +128,7 @@ describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => in end exclude_test = ohai[:platform] != "ubuntu" - describe "#action_add", :external => exclude_test do + describe "#action_add", external: exclude_test do after do new_resource.run_action(:delete) end @@ -127,7 +140,7 @@ describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => in end end - describe "#action_enable", :external => exclude_test do + describe "#action_enable", external: exclude_test do after do new_resource.run_action(:disable) end @@ -138,7 +151,7 @@ describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => in end end - describe "#action_disable", :external => exclude_test do + describe "#action_disable", external: exclude_test do before do setup_enable_interface(new_resource) new_resource.run_action(:enable) @@ -150,7 +163,7 @@ describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => in end end - describe "#action_delete", :external => exclude_test do + describe "#action_delete", external: exclude_test do before do setup_add_interface(new_resource) new_resource.run_action(:add) diff --git a/spec/functional/resource/insserv_spec.rb b/spec/functional/resource/insserv_spec.rb new file mode 100644 index 0000000000..e5c74c68ac --- /dev/null +++ b/spec/functional/resource/insserv_spec.rb @@ -0,0 +1,206 @@ +# +# Author:: Dheeraj Dubey (<dheeraj.dubey@msystechnologies.com>) +# 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" +require "chef/mixin/shell_out" +require "fileutils" + +describe Chef::Resource::Service, :requires_root, :opensuse do + + include Chef::Mixin::ShellOut + + def service_should_be_enabled + expect(shell_out!("/sbin/insserv -r -f #{new_resource.service_name}").exitstatus).to eq(0) + expect(shell_out!("/sbin/insserv -d -f #{new_resource.service_name}").exitstatus).to eq(0) + !Dir.glob("/etc/rc*/**/S*#{service_name}").empty? + end + + def service_should_be_disabled + expect(shell_out!("/sbin/insserv -r -f #{new_resource.service_name}").exitstatus).to eq(0) + Dir.glob("/etc/rc*/**/S*#{service_name}").empty? + end + + # Platform specific validation routines. + def service_should_be_started(file_name) + # The existence of this file indicates that the service was started. + expect(File.exist?("#{Dir.tmpdir}/#{file_name}")).to be_truthy + end + + def service_should_be_stopped(file_name) + expect(File.exist?("#{Dir.tmpdir}/#{file_name}")).to be_falsey + end + + def delete_test_files + files = Dir.glob("#{Dir.tmpdir}/init[a-z_]*.txt") + File.delete(*files) + end + + # Actual tests + let(:new_resource) do + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + + new_resource = Chef::Resource::Service.new("inittest", run_context) + new_resource.provider Chef::Provider::Service::Insserv + new_resource.supports({ status: true, restart: true, reload: true }) + new_resource + end + + let(:provider) do + provider = new_resource.provider_for_action(new_resource.action) + provider + end + + let(:service_name) { "Chef::Util::PathHelper.escape_glob_dir(current_resource.service_name)" } + + let(:current_resource) do + provider.load_current_resource + provider.current_resource + end + + before(:all) do + File.delete("/etc/init.d/inittest") if File.exist?("/etc/init.d/inittest") + FileUtils.cp((File.join(__dir__, "/../assets/inittest")).to_s, "/etc/init.d/inittest") + FileUtils.chmod(0755, "/etc/init.d/inittest") + end + + after(:all) do + File.delete("/etc/init.d/inittest") if File.exist?("/etc/init.d/inittest") + end + + before(:each) do + delete_test_files + end + + after(:each) do + delete_test_files + end + + describe "start service" do + it "should start the service" do + new_resource.run_action(:start) + service_should_be_started("inittest.txt") + expect(new_resource).to be_updated_by_last_action + end + + it "should be idempotent" do + new_resource.run_action(:start) + service_should_be_started("inittest.txt") + expect(new_resource).to be_updated_by_last_action + new_resource.run_action(:start) + service_should_be_started("inittest.txt") + expect(new_resource).not_to be_updated_by_last_action + end + end + + describe "stop service" do + before do + new_resource.run_action(:start) + end + + it "should stop the service" do + new_resource.run_action(:stop) + service_should_be_stopped("inittest.txt") + expect(new_resource).to be_updated_by_last_action + end + + it "should be idempotent" do + new_resource.run_action(:stop) + service_should_be_stopped("inittest.txt") + expect(new_resource).to be_updated_by_last_action + new_resource.run_action(:stop) + service_should_be_stopped("inittest.txt") + expect(new_resource).not_to be_updated_by_last_action + end + end + + describe "restart service" do + before do + new_resource.run_action(:start) + end + + it "should restart the service" do + new_resource.run_action(:restart) + service_should_be_started("inittest_restart.txt") + expect(new_resource).to be_updated_by_last_action + end + + it "should be idempotent" do + skip "FIXME: restart is not idempotent" + new_resource.run_action(:restart) + service_should_be_disabled + expect(new_resource).to be_updated_by_last_action + new_resource.run_action(:restart) + service_should_be_disabled + expect(new_resource).not_to be_updated_by_last_action + end + end + + describe "reload service" do + before do + new_resource.run_action(:start) + end + + it "should reload the service" do + new_resource.run_action(:reload) + service_should_be_started("inittest_reload.txt") + expect(new_resource).to be_updated_by_last_action + end + + it "should be idempotent" do + skip "FIXME: reload is not idempotent" + new_resource.run_action(:reload) + service_should_be_disabled + expect(new_resource).to be_updated_by_last_action + new_resource.run_action(:reload) + service_should_be_disabled + expect(new_resource).not_to be_updated_by_last_action + end + end + + describe "enable service" do + it "should enable the service" do + new_resource.run_action(:enable) + service_should_be_enabled + expect(new_resource).to be_updated_by_last_action + end + + it "should be idempotent" do + new_resource.run_action(:enable) + service_should_be_enabled + new_resource.run_action(:enable) + service_should_be_enabled + expect(new_resource).not_to be_updated_by_last_action + end + end + + describe "disable_service" do + it "should disable the service" do + new_resource.run_action(:disable) + service_should_be_disabled + expect(new_resource).to be_updated_by_last_action + end + + it "should be idempotent" do + new_resource.run_action(:disable) + service_should_be_disabled + new_resource.run_action(:disable) + service_should_be_disabled + expect(new_resource).not_to be_updated_by_last_action + end + end +end diff --git a/spec/functional/resource/launchd_spec.rb b/spec/functional/resource/launchd_spec.rb new file mode 100644 index 0000000000..70399d310d --- /dev/null +++ b/spec/functional/resource/launchd_spec.rb @@ -0,0 +1,232 @@ +# +# 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" + +# Deploy relies heavily on symlinks, so it doesn't work on windows. +describe Chef::Resource::Launchd, :macos_only, requires_root: true do + include RecipeDSLHelper + + before(:each) do + shell_out("launchctl unload -wF /Library/LaunchDaemons/io.chef.testing.fake.plist") + FileUtils.rm_f "/Library/LaunchDaemons/io.chef.testing.fake.plist" + end + + after(:each) do + shell_out("launchctl unload -wF /Library/LaunchDaemons/io.chef.testing.fake.plist") + FileUtils.rm_f "/Library/LaunchDaemons/io.chef.testing.fake.plist" + end + + context ":enable" do + it "enables a service" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :enable + end.should_be_updated + expect(File.exist?("/Library/LaunchDaemons/io.chef.testing.fake.plist")).to be true + expect(shell_out!("launchctl list io.chef.testing.fake").stdout).to match('"PID" = \d+') + expect(shell_out!("launchctl list io.chef.testing.fake").stdout).not_to match('"PID" = 0') + end + + it "should be idempotent" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :enable + end.should_be_updated + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :enable + end.should_not_be_updated + end + end + + context ":create" do + it "creates a service" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :create + end.should_be_updated + expect(File.exist?("/Library/LaunchDaemons/io.chef.testing.fake.plist")).to be true + expect(shell_out("launchctl list io.chef.testing.fake").exitstatus).not_to eql(0) + end + + it "should be idempotent" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :create + end.should_be_updated + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :create + end.should_not_be_updated + end + end + + context ":create_if_missing" do + it "creates a service if it is missing" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :create_if_missing + end.should_be_updated + expect(File.exist?("/Library/LaunchDaemons/io.chef.testing.fake.plist")).to be true + expect(shell_out("launchctl list io.chef.testing.fake").exitstatus).not_to eql(0) + end + it "is idempotent" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :create_if_missing + end.should_be_updated + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :create_if_missing + end.should_not_be_updated + end + end + + context ":delete" do + it "deletes a service" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :enable + end + launchd "io.chef.testing.fake" do + type "daemon" + action :delete + end.should_be_updated + expect(File.exist?("/Library/LaunchDaemons/io.chef.testing.fake.plist")).to be false + expect(shell_out("launchctl list io.chef.testing.fake").exitstatus).not_to eql(0) + end + it "is idempotent" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "60", + ] + run_at_load true + type "daemon" + action :enable + end + launchd "io.chef.testing.fake" do + type "daemon" + action :delete + end.should_be_updated + launchd "io.chef.testing.fake" do + type "daemon" + action :delete + end.should_not_be_updated + end + it "works if the file does not exist" do + launchd "io.chef.testing.fake" do + type "daemon" + action :delete + end.should_not_be_updated + end + end + + context ":disable" do + it "deletes a service" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "1", + ] + type "daemon" + action :enable + end + launchd "io.chef.testing.fake" do + type "daemon" + action :disable + end.should_be_updated + expect(File.exist?("/Library/LaunchDaemons/io.chef.testing.fake.plist")).to be true + expect(shell_out("launchctl list io.chef.testing.fake").exitstatus).not_to eql(0) + end + it "is idempotent" do + launchd "io.chef.testing.fake" do + program_arguments [ + "/bin/sleep", + "1", + ] + type "daemon" + action :enable + end + launchd "io.chef.testing.fake" do + type "daemon" + action :disable + end.should_be_updated + launchd "io.chef.testing.fake" do + type "daemon" + action :disable + end.should_not_be_updated + end + it "should work if the plist does not exist" do + launchd "io.chef.testing.fake" do + type "daemon" + action :disable + end.should_not_be_updated + end + end +end diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb index de6976448e..c1de3bf99d 100644 --- a/spec/functional/resource/link_spec.rb +++ b/spec/functional/resource/link_spec.rb @@ -1,6 +1,6 @@ # # Author:: John Keiser (<jkeiser@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +19,15 @@ require "spec_helper" if windows? - require "chef/win32/file" #probably need this in spec_helper + require "chef/win32/file" # probably need this in spec_helper + require "chef/win32/security" end describe Chef::Resource::Link do let(:file_base) { "file_spec" } let(:expect_updated?) { true } + let(:logger) { double("Mixlib::Log::Child").as_null_object } # We create the files in a different directory than tmp to exercise # different file deployment strategies more completely. @@ -53,13 +55,22 @@ describe Chef::Resource::Link do end after(:each) do - begin - cleanup_link(to) if File.exists?(to) - cleanup_link(target_file) if File.exists?(target_file) - cleanup_link(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH) - rescue - puts "Could not remove a file: #{$!}" - end + + cleanup_link(to) if File.exist?(to) + cleanup_link(target_file) if File.exist?(target_file) + cleanup_link(CHEF_SPEC_BACKUP_PATH) if File.exist?(CHEF_SPEC_BACKUP_PATH) + rescue + puts "Could not remove a file: #{$!}" + + end + + def user(user) + node = Chef::Node.new + node.consume_external_attrs(ohai.data, {}) + run_context = Chef::RunContext.new(node, {}, Chef::EventDispatch::Dispatcher.new) + usr = Chef::Resource.resource_for_node(:user, node).new(user, run_context) + usr.password("ComplexPass11!") if windows? + usr end def cleanup_link(path) @@ -108,12 +119,49 @@ describe Chef::Resource::Link do end end + let(:test_user) { windows? ? nil : ENV["USER"] } + + def expected_owner + if windows? + get_sid(test_user) + else + test_user + end + end + + def get_sid(value) + if value.is_a?(String) + Chef::ReservedNames::Win32::Security::SID.from_account(value) + elsif value.is_a?(Chef::ReservedNames::Win32::Security::SID) + value + else + raise "Must specify username or SID: #{value}" + end + end + + def chown(file, user) + if windows? + Chef::ReservedNames::Win32::Security::SecurableObject.new(file).owner = get_sid(user) + else + File.lchown(Etc.getpwnam(user).uid, Etc.getpwnam(user).gid, file) + end + end + + def owner(file) + if windows? + Chef::ReservedNames::Win32::Security::SecurableObject.new(file).security_descriptor.owner + else + Etc.getpwuid(File.lstat(file).uid).name + end + end + def create_resource node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) cookbook_collection = Chef::CookbookCollection.new(Chef::CookbookLoader.new(cookbook_repo)) run_context = Chef::RunContext.new(node, cookbook_collection, events) + allow(run_context).to receive(:logger).and_return(logger) resource = Chef::Resource::Link.new(target_file, run_context) resource.to(to) resource @@ -123,7 +171,7 @@ describe Chef::Resource::Link do create_resource end - describe "when supported on platform", :not_supported_on_win2k3 do + describe "when supported on platform" do shared_examples_for "delete errors out" do it "delete errors out" do expect { resource.run_action(:delete) }.to raise_error(Chef::Exceptions::Link) @@ -135,7 +183,7 @@ describe Chef::Resource::Link do describe "the :delete action" do before(:each) do @info = [] - allow(Chef::Log).to receive(:info) { |msg| @info << msg } + allow(logger).to receive(:info) { |msg| @info << msg } resource.run_action(:delete) end @@ -156,7 +204,7 @@ describe Chef::Resource::Link do describe "the :delete action" do before(:each) do @info = [] - allow(Chef::Log).to receive(:info) { |msg| @info << msg } + allow(logger).to receive(:info) { |msg| @info << msg } resource.run_action(:delete) end @@ -177,13 +225,14 @@ describe Chef::Resource::Link do describe "the :create action" do before(:each) do @info = [] - allow(Chef::Log).to receive(:info) { |msg| @info << msg } + allow(logger).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "links to the target file" do expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(to)) + expect(owner(target_file)).to eq(expected_owner) unless test_user.nil? end it "marks the resource updated" do expect(resource).to be_updated @@ -198,13 +247,14 @@ describe Chef::Resource::Link do describe "the :create action" do before(:each) do @info = [] - allow(Chef::Log).to receive(:info) { |msg| @info << msg } + allow(logger).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "leaves the file linked" do expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(to)) + expect(owner(target_file)).to eq(expected_owner) unless test_user.nil? end it "does not mark the resource updated" do expect(resource).not_to be_updated @@ -219,11 +269,11 @@ describe Chef::Resource::Link do describe "the :create action" do before(:each) do @info = [] - allow(Chef::Log).to receive(:info) { |msg| @info << msg } + allow(logger).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "preserves the hard link" do - expect(File.exists?(target_file)).to be_truthy + expect(File.exist?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey # Writing to one hardlinked file should cause both # to have the new value. @@ -244,11 +294,11 @@ describe Chef::Resource::Link do describe "the :create action" do before(:each) do @info = [] - allow(Chef::Log).to receive(:info) { |msg| @info << msg } + allow(logger).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "links to the target file" do - expect(File.exists?(target_file)).to be_truthy + expect(File.exist?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey # Writing to one hardlinked file should cause both # to have the new value. @@ -288,16 +338,26 @@ describe Chef::Resource::Link do include_context "delete succeeds" it "the :delete action does not delete the target file" do resource.run_action(:delete) - expect(File.exists?(to)).to be_truthy + expect(File.exist?(to)).to be_truthy end end - context "pointing somewhere else" do + context "pointing somewhere else", :requires_root_or_running_windows do + let(:test_user) { "test-link-user" } + before do + user(test_user).run_action(:create) + end + after do + user(test_user).run_action(:remove) + end before(:each) do + resource.owner(test_user) @other_target = File.join(test_file_dir, make_tmpname("other_spec")) File.open(@other_target, "w") { |file| file.write("eek") } symlink(@other_target, target_file) + chown(target_file, test_user) expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(@other_target)) + expect(owner(target_file)).to eq(expected_owner) end after(:each) do File.delete(@other_target) @@ -306,7 +366,7 @@ describe Chef::Resource::Link do include_context "delete succeeds" it "the :delete action does not delete the target file" do resource.run_action(:delete) - expect(File.exists?(to)).to be_truthy + expect(File.exist?(to)).to be_truthy end end context "pointing nowhere" do @@ -323,7 +383,7 @@ describe Chef::Resource::Link do context "and the link already exists and is a hard link to the file" do before(:each) do link(to, target_file) - expect(File.exists?(target_file)).to be_truthy + expect(File.exist?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey end include_context "create symbolic link succeeds" @@ -343,7 +403,7 @@ describe Chef::Resource::Link do it "create errors out" do if windows? expect { resource.run_action(:create) }.to raise_error(Errno::EACCES) - elsif os_x? || solaris? || freebsd? || aix? + elsif macos? || solaris? || freebsd? || aix? expect { resource.run_action(:create) }.to raise_error(Errno::EPERM) else expect { resource.run_action(:create) }.to raise_error(Errno::EISDIR) @@ -354,11 +414,11 @@ describe Chef::Resource::Link do it_behaves_like "a securable resource without existing target" do let(:path) { target_file } - def allowed_acl(sid, expected_perms) + def allowed_acl(sid, expected_perms, _flags = 0) [ ACE.access_allowed(sid, expected_perms[:specific]) ] end - def denied_acl(sid, expected_perms) + def denied_acl(sid, expected_perms, _flags = 0) [ ACE.access_denied(sid, expected_perms[:specific]) ] end @@ -522,14 +582,14 @@ describe Chef::Resource::Link do context "and the link already exists and is a hard link to the file" do before(:each) do link(to, target_file) - expect(File.exists?(target_file)).to be_truthy + expect(File.exist?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey end include_context "create hard link is noop" include_context "delete succeeds" it "the :delete action does not delete the target file" do resource.run_action(:delete) - expect(File.exists?(to)).to be_truthy + expect(File.exist?(to)).to be_truthy end end context "and the link already exists and is a file" do @@ -546,7 +606,7 @@ describe Chef::Resource::Link do it "errors out" do if windows? expect { resource.run_action(:create) }.to raise_error(Errno::EACCES) - elsif os_x? || solaris? || freebsd? || aix? + elsif macos? || solaris? || freebsd? || aix? expect { resource.run_action(:create) }.to raise_error(Errno::EPERM) else expect { resource.run_action(:create) }.to raise_error(Errno::EISDIR) @@ -593,10 +653,10 @@ describe Chef::Resource::Link do end context "and the link does not yet exist" do it "links to the target file" do - skip("OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks") if os_x? || freebsd? || aix? + skip("macOS/FreeBSD/AIX/Solaris symlink? and readlink working on hard links to symlinks") if macos? || freebsd? || aix? || solaris? resource.run_action(:create) - expect(File.exists?(target_file)).to be_truthy - # OS X gets angry about this sort of link. Bug in OS X, IMO. + expect(File.exist?(target_file)).to be_truthy + # macOS gets angry about this sort of link. Bug in macOS, IMO. expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(@other_target)) end @@ -612,9 +672,9 @@ describe Chef::Resource::Link do end context "and the link does not yet exist" do it "links to the target file" do - skip("OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks") if os_x? || freebsd? || aix? + skip("macOS/FreeBSD/AIX/Solaris fails to create hardlinks to broken symlinks") if macos? || freebsd? || aix? || solaris? resource.run_action(:create) - expect(File.exists?(target_file) || File.symlink?(target_file)).to be_truthy + expect(File.exist?(target_file) || File.symlink?(target_file)).to be_truthy expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(@other_target)) end @@ -633,10 +693,4 @@ describe Chef::Resource::Link do end end end - - describe "when not supported on platform", :win2k3_only do - it "raises error" do - expect { resource }.to raise_error(Chef::Exceptions::Win32APIFunctionNotImplemented) - end - end end diff --git a/spec/functional/resource/locale_spec.rb b/spec/functional/resource/locale_spec.rb new file mode 100644 index 0000000000..5258f08696 --- /dev/null +++ b/spec/functional/resource/locale_spec.rb @@ -0,0 +1,108 @@ +# +# Author:: Nimesh Patni (<nimesh.patni@msystechnologies.com>) +# 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::Resource::Locale, :requires_root do + let(:node) do + n = Chef::Node.new + n.consume_external_attrs(OHAI_SYSTEM.data, {}) + n + end + let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:resource) { Chef::Resource::Locale.new("fakey_fakerton", run_context) } + + context "on debian/ubuntu", :debian_family_only do + def sets_system_locale(*locales) + system_locales = File.readlines("/etc/locale.conf") + expect(system_locales.map(&:strip)).to eq(locales) + end + + def unsets_system_locale(*locales) + system_locales = File.readlines("/etc/locale.conf") + expect(system_locales.map(&:strip)).not_to eq(locales) + end + + describe "action: update" do + context "Sets system variable" do + it "when LC var is given" do + resource.lc_env({ "LC_MESSAGES" => "en_US" }) + resource.run_action(:update) + sets_system_locale("LC_MESSAGES=en_US") + end + it "when lang is given" do + resource.lang("en_US") + resource.run_action(:update) + sets_system_locale("LANG=en_US") + end + it "when both lang & LC vars are given" do + resource.lang("en_US") + resource.lc_env({ "LC_TIME" => "en_IN" }) + resource.run_action(:update) + sets_system_locale("LANG=en_US", "LC_TIME=en_IN") + end + end + + context "Unsets system variable" do + it "when LC var is not given" do + resource.lc_env + resource.run_action(:update) + unsets_system_locale("LC_MESSAGES=en_US") + end + it "when lang is not given" do + resource.lang + resource.run_action(:update) + unsets_system_locale("LANG=en_US") + end + it "when both lang & LC vars are not given" do + resource.lang + resource.lc_env + resource.run_action(:update) + expect(resource).not_to be_updated_by_last_action + end + end + end + end + + context "on rhel", :rhel do + it "raises an exception due lacking the locale-gen tool" do + resource.lang("en_US") + expect { resource.run_action(:update) }.to raise_error(Chef::Exceptions::ProviderNotFound) + end + end + + context "on macos", :macos_only do + it "raises an exception due to being an unsupported platform" do + resource.lang("en_US") + expect { resource.run_action(:update) }.to raise_error(Chef::Exceptions::ProviderNotFound) + end + end + + # @TODO we need to enable these again + # context "on windows", :windows_only, requires_root: false do + # describe "action: update" do + # context "Sets system locale" do + # it "when lang is given" do + # resource.lang("en-US") + # resource.run_action(:update) + # end + # end + # end + # end +end diff --git a/spec/functional/resource/mount_spec.rb b/spec/functional/resource/mount_spec.rb index c756b0d3d4..f11f97d6c7 100644 --- a/spec/functional/resource/mount_spec.rb +++ b/spec/functional/resource/mount_spec.rb @@ -1,6 +1,6 @@ # # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,33 +17,32 @@ # require "spec_helper" -require "functional/resource/base" require "chef/mixin/shell_out" require "tmpdir" # run this test only for following platforms. -include_flag = !(%w{ubuntu centos aix solaris2}.include?(ohai[:platform])) - -describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => include_flag do - # Disabled in travis because it refuses to let us mount a ramdisk. /dev/ramX does not - # exist even after loading the kernel module +include_flag = !(%w{debian rhel amazon aix solaris2}.include?(ohai[:platform_family])) +describe Chef::Resource::Mount, :requires_root, external: include_flag do include Chef::Mixin::ShellOut # Platform specific setup, cleanup and validation helpers. - def setup_device_for_mount # use ramdisk for creating a test device for mount. # This can cleaner if we have chef resource/provider for ramdisk. - case ohai[:platform] + case ohai[:platform_family] when "aix" # On AIX, we can't create a ramdisk inside a WPAR, so we use # a "namefs" mount against / to test # https://www-304.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.performance/namefs_file_sys.htm device = "/" fstype = "namefs" - when "ubuntu", "centos" + when "debian", "rhel", "amazon" device = "/dev/ram1" + unless File.exist?(device) + shell_out("mknod -m 660 #{device} b 1 0") + shell_out("chown root:disk #{device}") + end shell_out("ls -1 /dev/ram*").stdout.each_line do |d| if shell_out("mount | grep #{d}").exitstatus == "1" # this device is not mounted, so use it. @@ -69,7 +68,7 @@ describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => inclu def mount_should_exist(mount_point, device, fstype = nil, options = nil) validation_cmd = "mount | grep #{mount_point} | grep #{device} " validation_cmd << " | grep #{fstype} " unless fstype.nil? - validation_cmd << " | grep #{options.join(',')} " unless options.nil? || options.empty? + validation_cmd << " | grep #{options.join(",")} " unless options.nil? || options.empty? expect(shell_out(validation_cmd).exitstatus).to eq(0) end @@ -101,6 +100,15 @@ describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => inclu expect(shell_out("cat #{unix_mount_config_file}").stdout).not_to include("#{mount_point}:") end + let(:run_context) do + node = Chef::Node.new + node.default[:platform] = ohai[:platform] + node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + end + let(:new_resource) do new_resource = Chef::Resource::Mount.new(@mount_point, run_context) new_resource.device @device @@ -121,8 +129,9 @@ describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => inclu end # Actual tests begin here. - before(:all) do + before do |test| @device, @fstype = setup_device_for_mount + @device = "/" if test.metadata[:skip_before] @mount_point = Dir.mktmpdir("testmount") @@ -137,13 +146,20 @@ describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => inclu end after(:all) do - Dir.rmdir(@mount_point) + Dir.rmdir(@mount_point) if @mount_point end after(:each) do cleanup_mount(new_resource.mount_point) end + describe "when device is '/'" do + it "should mount the filesystem if device is '/'", :skip_before do + new_resource.run_action(:mount) + mount_should_exist(new_resource.mount_point, new_resource.device) + end + end + describe "when the target state is a mounted filesystem" do it "should mount the filesystem if it isn't mounted" do expect(current_resource.enabled).to be_falsey @@ -157,7 +173,7 @@ describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => inclu # don't run the remount tests on solaris2 (tmpfs does not support remount) # Need to make sure the platforms we've already excluded are considered: skip_remount = include_flag || (ohai[:platform] == "solaris2") - describe "when the filesystem should be remounted and the resource supports remounting", :external => skip_remount do + describe "when the filesystem should be remounted and the resource supports remounting", external: skip_remount do it "should remount the filesystem if it is mounted" do new_resource.run_action(:mount) mount_should_exist(new_resource.mount_point, new_resource.device) diff --git a/spec/functional/resource/msu_package_spec.rb b/spec/functional/resource/msu_package_spec.rb new file mode 100644 index 0000000000..2e6fcdcbca --- /dev/null +++ b/spec/functional/resource/msu_package_spec.rb @@ -0,0 +1,107 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# 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" +require "chef/provider/package/cab" + +describe Chef::Resource::MsuPackage, :win2012r2_only do + + let(:package_name) { "Package_for_KB2959977" } + let(:package_source) { "https://download.microsoft.com/download/3/B/3/3B320C07-B7B1-41E5-81F4-79EBC02DF7D3/Windows8.1-KB2959977-x64.msu" } + let(:package_identity) { "Package_for_KB2959977~31bf3856ad364e35~amd64~~6.3.1.1" } + let(:timeout) { 3600 } + + let(:run_context) do + node = Chef::Node.new + node.default[:platform] = ohai[:platform] + node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + end + + let(:new_resource) { Chef::Resource::CabPackage.new("windows_test_pkg") } + let(:cab_provider) do + Chef::Provider::Package::Cab.new(new_resource, run_context) + end + + subject do + new_resource = Chef::Resource::MsuPackage.new("test msu package", run_context) + new_resource.package_name package_name + new_resource.source package_source + new_resource.timeout timeout + new_resource + end + + context "installing package" do + after { remove_package } + + it "installs the package successfully" do + subject.run_action(:install) + found_packages = cab_provider.installed_packages.select { |p| p["package_identity"] == package_identity } + expect(found_packages.length).to be == 1 + end + end + + context "removing a package" do + it "removes an installed package" do + subject.run_action(:install) + remove_package + found_packages = cab_provider.installed_packages.select { |p| p["package_identity"] == package_identity } + expect(found_packages.length).to be == 0 + end + end + + context "when an invalid msu package is given" do + def package_name + "Package_for_KB2859903" + end + + def package_source + "https://download.microsoft.com/download/5/2/B/52BE95BF-22D8-4415-B644-0FDF398F6D96/IE10-Windows6.1-KB2859903-x86.msu" + end + + it "raises error while installing" do + expect { subject.run_action(:install) }.to raise_error(Chef::Exceptions::Package) + end + + it "raises error while removing" do + expect { subject.run_action(:remove) }.to raise_error(Chef::Exceptions::Package) + end + end + + context "when an msu package is not applicable to the image." do + def package_name + "Package_for_KB4019990" + end + + def package_source + "http://download.windowsupdate.com/c/msdownload/update/software/updt/2017/05/windows8-rt-kb4019990-x64_a77f4e3e1f2d47205824763e7121bb11979c2716.msu" + end + + it "raises error while installing" do + expect { subject.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /The specified package is not applicable to this image./) + end + end + + def remove_package + pkg_to_remove = Chef::Resource::MsuPackage.new(package_name, run_context) + pkg_to_remove.source = package_source + pkg_to_remove.run_action(:remove) + end +end diff --git a/spec/functional/resource/ohai_spec.rb b/spec/functional/resource/ohai_spec.rb index 06bccfc398..836a4f6da3 100644 --- a/spec/functional/resource/ohai_spec.rb +++ b/spec/functional/resource/ohai_spec.rb @@ -1,6 +1,6 @@ # # Author:: Serdar Sutay (<serdar@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/functional/resource/powershell_package_source_spec.rb b/spec/functional/resource/powershell_package_source_spec.rb new file mode 100644 index 0000000000..abc9dcabd1 --- /dev/null +++ b/spec/functional/resource/powershell_package_source_spec.rb @@ -0,0 +1,107 @@ +# +# Author:: Matt Wrock (<matt@mattwrock.com>) +# 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" +require "chef/mixin/powershell_exec" + +describe Chef::Resource::PowershellPackageSource, :windows_gte_10 do + include Chef::Mixin::PowershellExec + + let(:source_name) { "fake" } + let(:url) { "https://www.nuget.org/api/v2" } + let(:trusted) { true } + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + + subject do + new_resource = Chef::Resource::PowershellPackageSource.new("test powershell package source", run_context) + new_resource.source_name source_name + new_resource.url url + new_resource.trusted trusted + new_resource.provider_name provider_name + new_resource + end + + let(:provider) do + provider = subject.provider_for_action(subject.action) + provider + end + + shared_examples "package_source" do + context "register a package source" do + after { remove_package_source } + + it "registers the package source" do + subject.run_action(:register) + expect(get_installed_package_source_name).to eq(source_name) + end + + it "does not register the package source if already installed" do + subject.run_action(:register) + subject.run_action(:register) + expect(subject).not_to be_updated_by_last_action + end + + it "updates an existing package source if changed" do + subject.run_action(:register) + subject.trusted !trusted + subject.run_action(:register) + expect(subject).to be_updated_by_last_action + end + end + + context "unregister a package source" do + it "unregisters the package source" do + subject.run_action(:register) + subject.run_action(:unregister) + expect(get_installed_package_source_name).to be_empty + end + + it "does not unregister the package source if not already installed" do + subject.run_action(:unregister) + expect(subject).not_to be_updated_by_last_action + end + end + end + + context "with NuGet provider" do + let(:provider_name) { "NuGet" } + + before(:all) do + powershell_exec!("[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;Install-PackageProvider -Name NuGet -Force") + end + + it_behaves_like "package_source" + end + + context "with PowerShellGet provider" do + let(:provider_name) { "PowerShellGet" } + + it_behaves_like "package_source" + end + + def get_installed_package_source_name + powershell_exec!("(Get-PackageSource -Name #{source_name} -ErrorAction SilentlyContinue).Name").result + end + + def remove_package_source + pkg_to_remove = Chef::Resource::PowershellPackageSource.new(source_name, run_context) + pkg_to_remove.run_action(:unregister) + end +end
\ No newline at end of file diff --git a/spec/functional/resource/powershell_script_spec.rb b/spec/functional/resource/powershell_script_spec.rb index af345b0ea4..68fa94afe9 100644 --- a/spec/functional/resource/powershell_script_spec.rb +++ b/spec/functional/resource/powershell_script_spec.rb @@ -1,6 +1,6 @@ # # Author:: Adam Edwards (<adamed@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,33 +23,31 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do include_context Chef::Resource::WindowsScript - let (:architecture_command) { "echo $env:PROCESSOR_ARCHITECTURE" } - let (:output_command) { " | out-file -encoding ASCII " } + let(:architecture_command) { "echo $env:PROCESSOR_ARCHITECTURE" } + let(:output_command) { " | out-file -encoding ASCII " } it_behaves_like "a Windows script running on Windows" - let(:successful_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" } - let(:failed_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe /badargument" } + let(:successful_executable_script_content) { "#{ENV["SystemRoot"]}\\system32\\attrib.exe $env:systemroot" } + let(:failed_executable_script_content) { "#{ENV["SystemRoot"]}\\system32\\attrib.exe /badargument" } let(:processor_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTURE" } let(:native_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTUREW6432" } let(:cmdlet_exit_code_not_found_content) { "get-item '.\\thisdoesnotexist'" } let(:cmdlet_exit_code_success_content) { "get-item ." } - let(:windows_process_exit_code_success_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" } + let(:windows_process_exit_code_success_content) { "#{ENV["SystemRoot"]}\\system32\\attrib.exe $env:systemroot" } let(:windows_process_exit_code_not_found_content) { "findstr /notavalidswitch" } - # Note that process exit codes on 32-bit Win2k3 cannot - # exceed maximum value of signed integer let(:arbitrary_nonzero_process_exit_code) { 4193 } let(:arbitrary_nonzero_process_exit_code_content) { "exit #{arbitrary_nonzero_process_exit_code}" } let(:invalid_powershell_interpreter_flag) { "/thisflagisinvalid" } let(:valid_powershell_interpreter_flag) { "-Sta" } let!(:resource) do - r = Chef::Resource::WindowsScript::PowershellScript.new("Powershell resource functional test", @run_context) + r = Chef::Resource::WindowsScript::PowershellScript.new("PowerShell resource functional test", @run_context) r.code(successful_executable_script_content) r end - describe "when the run action is invoked on Windows" do + shared_examples_for "a running powershell script" do it "successfully executes a non-cmdlet Windows binary as the last command of the script" do resource.code(successful_executable_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.returns(0) @@ -57,8 +55,6 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns the exit status 27 for a powershell script that exits with 27" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - file = Tempfile.new(["foo", ".ps1"]) begin file.write "exit 27" @@ -72,10 +68,9 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end end - let (:negative_exit_status) { -27 } - let (:unsigned_exit_status) { (-negative_exit_status ^ 65535) + 1 } + let(:negative_exit_status) { -27 } + let(:unsigned_exit_status) { (-negative_exit_status ^ 65535) + 1 } it "returns the exit status -27 as a signed integer or an unsigned 16-bit 2's complement value of 65509 for a powershell script that exits with -27" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? # Versions of PowerShell prior to 4.0 return a 16-bit unsigned value -- # PowerShell 4.0 and later versions return a 32-bit signed value. @@ -100,8 +95,6 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns the process exit code" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code(arbitrary_nonzero_process_exit_code_content) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) @@ -120,34 +113,24 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns 1 if the last command was a cmdlet that failed" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code(cmdlet_exit_code_not_found_content) resource.returns(1) resource.run_action(:run) end it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code([windows_process_exit_code_success_content, cmdlet_exit_code_not_found_content].join(";")) resource.returns(1) expect { resource.run_action(:run) }.not_to raise_error end it "raises a Mixlib::ShellOut::ShellCommandFailed error if the script is not syntactically correct" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code("if({)") resource.returns(0) expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end it "raises an error if the script is not syntactically correct even if returns is set to 1 which is what powershell.exe returns for syntactically invalid scripts" do - # This test fails because shell_out expects the exit status to be 1, but it is actually 0 - # The error is a false-positive. - skip "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code("if({)") resource.returns(1) expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) @@ -156,38 +139,30 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do # This somewhat ambiguous case, two failures of different types, # seems to violate the principle of returning the status of the # last line executed -- in this case, we return the status of the - # second to last line. This happens because Powershell gives no + # second to last line. This happens because PowerShell gives no # way for us to determine whether the last operation was a cmdlet # or Windows process. Because the latter gives more specific # errors than 0 or 1, we return that instead, which is acceptable # since callers can test for nonzero rather than testing for 1. it "returns 1 if the last command was a cmdlet that failed and was preceded by an unsuccessfully executed non-cmdlet Windows binary" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code([arbitrary_nonzero_process_exit_code_content, cmdlet_exit_code_not_found_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns 0 if the last command was a non-cmdlet Windows binary that succeeded and was preceded by a failed cmdlet" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that succeeded" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that failed" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.code([cmdlet_exit_code_not_found_content, arbitrary_nonzero_process_exit_code_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) @@ -206,8 +181,6 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end it "returns 1 for $false as the last line of the script when convert_boolean_return is true" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.convert_boolean_return true resource.code "$false" resource.returns(1) @@ -233,43 +206,79 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do expect(is_64_bit).to eq(detected_64_bit) end - it "returns 1 if an invalid flag is passed to the interpreter" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - + it "returns 0 if a valid flag is passed to the interpreter" do resource.code(cmdlet_exit_code_success_content) - resource.flags(invalid_powershell_interpreter_flag) - resource.returns(1) + resource.flags(valid_powershell_interpreter_flag) + resource.returns(0) resource.run_action(:run) end - it "returns 0 if a valid flag is passed to the interpreter" do + it "returns 0 if no flag is passed to the interpreter" do resource.code(cmdlet_exit_code_success_content) - resource.flags(valid_powershell_interpreter_flag) resource.returns(0) resource.run_action(:run) end + it "returns 1 if an invalid flag is passed to the interpreter" do + resource.code(cmdlet_exit_code_success_content) + resource.flags(invalid_powershell_interpreter_flag) + resource.returns(1) + expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) + end + it "raises an error when given a block and a guard_interpreter" do resource.guard_interpreter :sh resource.only_if { true } expect { resource.should_skip?(:run) }.to raise_error(ArgumentError, /guard_interpreter does not support blocks/) end + end - context "when dsc is supported", :windows_powershell_dsc_only do - it "can execute LCM configuration code" do - resource.code <<-EOF -configuration LCM -{ - param ($thumbprint) - localconfigurationmanager - { - RebootNodeIfNeeded = $false - ConfigurationMode = 'ApplyOnly' - } -} - EOF - expect { resource.run_action(:run) }.not_to raise_error - end + context "when using the powershell interpreter" do + before do + resource.interpreter "powershell" + end + + it_behaves_like "a running powershell script" + + it "runs Windows Powershell" do + resource.code("$PSVersionTable.PSVersion.Major | out-file -encoding ASCII #{script_output_path}") + resource.returns(0) + resource.run_action(:run) + + expect(get_script_output.to_i).to be < 6 + end + end + + context "when using the pwsh interpreter", :pwsh_installed do + before do + resource.interpreter "pwsh" + end + + it_behaves_like "a running powershell script" + + it "runs a version of powershell greater than 6" do + resource.code("$PSVersionTable.PSVersion.Major | out-file -encoding ASCII #{script_output_path}") + resource.returns(0) + resource.run_action(:run) + + expect(get_script_output.to_i).to be > 6 + end + end + + context "when dsc is supported", :windows_powershell_dsc_only do + it "can execute LCM configuration code" do + resource.code <<~EOF + configuration LCM + { + param ($thumbprint) + localconfigurationmanager + { + RebootNodeIfNeeded = $false + ConfigurationMode = 'ApplyOnly' + } + } + EOF + expect { resource.run_action(:run) }.not_to raise_error end end @@ -296,10 +305,10 @@ configuration LCM context "when running on a 32-bit version of Windows", :windows32_only do it "raises an exception if :x86_64 process architecture is specified" do - begin - expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect - rescue Chef::Exceptions::Win32ArchitectureIncorrect - end + + expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect + rescue Chef::Exceptions::Win32ArchitectureIncorrect + end end end @@ -314,7 +323,7 @@ configuration LCM expect(source_contains_case_insensitive_content?( get_script_output, "AMD64" )).to eq(true) end - it "executes a script with a 32-bit process if :i386 arch is specified", :not_supported_on_nano do + it "executes a script with a 32-bit process if :i386 arch is specified" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:i386) resource.returns(0) @@ -322,12 +331,6 @@ configuration LCM expect(source_contains_case_insensitive_content?( get_script_output, "x86" )).to eq(true) end - - it "raises an error when executing a script with a 32-bit process on Windows Nano Server", :windows_nano_only do - resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") - expect { resource.architecture(:i386) }.to raise_error(Chef::Exceptions::Win32ArchitectureIncorrect, - "cannot execute script with requested architecture 'i386' on Windows Nano Server") - end end describe "when executing guards" do @@ -376,13 +379,22 @@ configuration LCM context "with powershell_script as the guard_interpreter" do + context "when pwsh is the interpreter", :pwsh_installed do + before do + resource.interpreter "pwsh" + end + + it "uses powershell core to evaluate the guard" do + resource.not_if "$PSVersionTable.PSEdition -eq 'Core'" + expect(resource.should_skip?(:run)).to be_truthy + end + end + it "has a guard_interpreter attribute set to :powershell_script" do expect(resource.guard_interpreter).to eq(:powershell_script) end it "evaluates a powershell $false for a not_if block as true" do - pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server? - resource.not_if "$false" expect(resource.should_skip?(:run)).to be_falsey end @@ -393,8 +405,6 @@ configuration LCM end it "evaluates a powershell $false for an only_if block as false" do - pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server? - resource.only_if "$false" expect(resource.should_skip?(:run)).to be_truthy end @@ -415,8 +425,6 @@ configuration LCM end it "evaluates a non-zero powershell exit status for not_if as true" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.not_if "exit 37" expect(resource.should_skip?(:run)).to be_falsey end @@ -427,8 +435,6 @@ configuration LCM end it "evaluates a failed executable exit status for not_if as false" do - pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? - resource.not_if windows_process_exit_code_not_found_content expect(resource.should_skip?(:run)).to be_falsey end @@ -439,8 +445,6 @@ configuration LCM end it "evaluates a failed executable exit status for only_if as false" do - pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? - resource.only_if windows_process_exit_code_not_found_content expect(resource.should_skip?(:run)).to be_truthy end @@ -451,8 +455,6 @@ configuration LCM end it "evaluates a failed cmdlet exit status for not_if as true" do - pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? - resource.not_if "throw 'up'" expect(resource.should_skip?(:run)).to be_falsey end @@ -463,8 +465,6 @@ configuration LCM end it "evaluates a failed cmdlet exit status for only_if as false" do - pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? - resource.only_if "throw 'up'" expect(resource.should_skip?(:run)).to be_truthy end @@ -475,26 +475,26 @@ configuration LCM end it "evaluates a not_if block using the cwd guard parameter" do - custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" - resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd + custom_cwd = "#{ENV["SystemRoot"]}\\system32\\drivers\\etc" + resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", cwd: custom_cwd expect(resource.should_skip?(:run)).to be_truthy end it "evaluates an only_if block using the cwd guard parameter" do - custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" - resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd + custom_cwd = "#{ENV["SystemRoot"]}\\system32\\drivers\\etc" + resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", cwd: custom_cwd expect(resource.should_skip?(:run)).to be_falsey end it "inherits cwd from the parent resource for only_if" do - custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" + custom_cwd = "#{ENV["SystemRoot"]}\\system32\\drivers\\etc" resource.cwd custom_cwd resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')" expect(resource.should_skip?(:run)).to be_falsey end it "inherits cwd from the parent resource for not_if" do - custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" + custom_cwd = "#{ENV["SystemRoot"]}\\system32\\drivers\\etc" resource.cwd custom_cwd resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')" expect(resource.should_skip?(:run)).to be_truthy @@ -507,36 +507,30 @@ configuration LCM end it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean true as nonzero status code", :windows64_only do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.architecture :x86_64 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'AMD64')" expect(resource.should_skip?(:run)).to be_truthy end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code", :not_supported_on_nano do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code" do resource.architecture :i386 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'X86')" expect(resource.should_skip?(:run)).to be_falsey end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code", :not_supported_on_nano do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code" do resource.architecture :i386 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'X86')" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for only_if" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.convert_boolean_return true resource.only_if "$false" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for not_if" do - pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? - resource.convert_boolean_return true resource.not_if "$false" expect(resource.should_skip?(:run)).to be_falsey @@ -554,40 +548,33 @@ configuration LCM expect(resource.should_skip?(:run)).to be_truthy end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if", :not_supported_on_nano do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if" do resource.convert_boolean_return true resource.architecture :i386 resource.only_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" expect(resource.should_skip?(:run)).to be_falsey end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if", :not_supported_on_nano do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if" do resource.convert_boolean_return true resource.architecture :i386 resource.not_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" expect(resource.should_skip?(:run)).to be_falsey end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if", :not_supported_on_nano do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if" do resource.convert_boolean_return true resource.architecture :i386 resource.only_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" expect(resource.should_skip?(:run)).to be_truthy end - it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if", :not_supported_on_nano do + it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if" do resource.convert_boolean_return true resource.architecture :i386 resource.not_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" expect(resource.should_skip?(:run)).to be_truthy end - - it "raises an error when a 32-bit guard is used on Windows Nano Server", :windows_nano_only do - resource.only_if "$true", :architecture => :i386 - expect { resource.run_action(:run) }.to raise_error( - Chef::Exceptions::Win32ArchitectureIncorrect, - /cannot execute script with requested architecture 'i386' on Windows Nano Server/) - end end end diff --git a/spec/functional/resource/reboot_spec.rb b/spec/functional/resource/reboot_spec.rb index c264b122a7..5489dc1c72 100644 --- a/spec/functional/resource/reboot_spec.rb +++ b/spec/functional/resource/reboot_spec.rb @@ -22,9 +22,9 @@ describe Chef::Resource::Reboot do let(:expected) do { - :delay_mins => 5, - :requested_by => "reboot resource functional test", - :reason => "reboot resource spec test", + delay_mins: 5, + requested_by: "reboot resource functional test", + reason: "reboot resource spec test", } end @@ -45,7 +45,7 @@ describe Chef::Resource::Reboot do shared_context "testing run context modification" do def test_reboot_action(resource) reboot_info = resource.run_context.reboot_info - expect(reboot_info.keys.sort).to eq([:delay_mins, :reason, :requested_by, :timestamp]) + expect(reboot_info.keys.sort).to eq(%i{delay_mins reason requested_by timestamp}) expect(reboot_info[:delay_mins]).to eq(expected[:delay_mins]) expect(reboot_info[:reason]).to eq(expected[:reason]) expect(reboot_info[:requested_by]).to eq(expected[:requested_by]) diff --git a/spec/functional/resource/registry_spec.rb b/spec/functional/resource/registry_spec.rb index 8393e0a074..f3ad946f0e 100644 --- a/spec/functional/resource/registry_spec.rb +++ b/spec/functional/resource/registry_spec.rb @@ -1,7 +1,7 @@ # # Author:: Prajakta Purohit (<prajakta@chef.io>) # Author:: Lamont Granquist (<lamont@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,13 +34,13 @@ describe Chef::Resource::RegistryKey, :unix_only do context "when load_current_resource is run on a non-windows node" do it "throws an exception because you don't have a windows registry (derp)" do @resource.key("HKCU\\Software\\Opscode") - @resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) + @resource.values([{ name: "Color", type: :string, data: "Orange" }]) expect { @resource.run_action(:create) }.to raise_error(Chef::Exceptions::Win32NotWindows) end end end -describe Chef::Resource::RegistryKey, :windows_only, :broken => true do +describe Chef::Resource::RegistryKey, :windows_only, broken: true do # parent and key must be single keys, not paths let(:parent) { "Opscode" } @@ -107,14 +107,14 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do reset_registry end - #Reporting setup + # Reporting setup before do @node.name("windowsbox") @rest_client = double("Chef::ServerAPI (mock)") - allow(@rest_client).to receive(:create_url).and_return("reports/nodes/windowsbox/runs/#{@run_id}"); - allow(@rest_client).to receive(:raw_http_request).and_return({ "result" => "ok" }); - allow(@rest_client).to receive(:post_rest).and_return({ "uri" => "https://example.com/reports/nodes/windowsbox/runs/#{@run_id}" }); + allow(@rest_client).to receive(:create_url).and_return("reports/nodes/windowsbox/runs/#{@run_id}") + allow(@rest_client).to receive(:raw_http_request).and_return({ "result" => "ok" }) + allow(@rest_client).to receive(:post_rest).and_return({ "uri" => "https://example.com/reports/nodes/windowsbox/runs/#{@run_id}" }) @resource_reporter = Chef::ResourceReporter.new(@rest_client) @events.register(@resource_reporter) @@ -123,104 +123,128 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do @run_id = @resource_reporter.run_id @new_resource.cookbook_name = "monkey" - @cookbook_version = double("Cookbook::Version", :version => "1.2.3") - allow(@new_resource).to receive(:cookbook_version).and_return(@cookbook_version) + @cookbook_version = double("Cookbook::Version", version: "1.2.3") + @new_resource.cookbook_version(@cookbook_version) end - after (:all) do + after(:all) do clean_registry end context "when action is create" do - before (:all) do + before(:all) do reset_registry end it "creates registry key, value if the key is missing" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) + @new_resource.values([{ name: "Color", type: :string, data: "Orange" }]) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child)).to eq(true) - expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Color", type: :string, data: "Orange" })).to eq(true) end it "does not create the key if it already exists with same value, type and data" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) + @new_resource.values([{ name: "Color", type: :string, data: "Orange" }]) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child)).to eq(true) - expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Color", type: :string, data: "Orange" })).to eq(true) end it "does not create the key if it already exists with same value and type but datatype of data differs" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "number", :type => :dword, :data => "12345" }]) + @new_resource.values([{ name: "number", type: :dword, data: "12345" }]) @new_resource.run_action(:create) expect(@new_resource).not_to be_updated_by_last_action expect(@registry.key_exists?(reg_child)).to eq(true) - expect(@registry.data_exists?(reg_child, { :name => "number", :type => :dword, :data => 12344 })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "number", type: :dword, data: 12344 })).to eq(true) end it "creates a value if it does not exist" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Mango", :type => :string, :data => "Yellow" }]) + @new_resource.values([{ name: "Mango", type: :string, data: "Yellow" }]) @new_resource.run_action(:create) - expect(@registry.data_exists?(reg_child, { :name => "Mango", :type => :string, :data => "Yellow" })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Mango", type: :string, data: "Yellow" })).to eq(true) end it "modifies the data if the key and value exist and type matches" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Color", :type => :string, :data => "Not just Orange - OpscodeOrange!" }]) + @new_resource.values([{ name: "Color", type: :string, data: "Not just Orange - OpscodeOrange!" }]) @new_resource.run_action(:create) - expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Not just Orange - OpscodeOrange!" })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Color", type: :string, data: "Not just Orange - OpscodeOrange!" })).to eq(true) end it "modifys the type if the key and value exist and the type does not match" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Color", :type => :multi_string, :data => ["Not just Orange - OpscodeOrange!"] }]) + @new_resource.values([{ name: "Color", type: :multi_string, data: ["Not just Orange - OpscodeOrange!"] }]) @new_resource.run_action(:create) - expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :multi_string, :data => ["Not just Orange - OpscodeOrange!"] })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Color", type: :multi_string, data: ["Not just Orange - OpscodeOrange!"] })).to eq(true) end it "creates subkey if parent exists" do @new_resource.key(reg_child + '\OpscodeTest') - @new_resource.values([{ :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} }]) + @new_resource.values([{ name: "Chef", type: :multi_string, data: %w{OpscodeOrange Rules} }]) @new_resource.recursive(false) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child + '\OpscodeTest')).to eq(true) - expect(@registry.value_exists?(reg_child + '\OpscodeTest', { :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} })).to eq(true) + expect(@registry.value_exists?(reg_child + '\OpscodeTest', { name: "Chef", type: :multi_string, data: %w{OpscodeOrange Rules} })).to eq(true) end - it "gives error if action create and parent does not exist and recursive is set to false" do + it "raises an error if action create and parent does not exist and recursive is set to false" do @new_resource.key(reg_child + '\Missing1\Missing2') - @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) + @new_resource.values([{ name: "OC", type: :string, data: "MissingData" }]) @new_resource.recursive(false) expect { @new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end + it "raises an error if action create and type key missing in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC", data: "my_data" }]) + expect { @new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::RegKeyValuesTypeMissing) + end + + it "raises an error if action create and data key missing in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC", type: :string }]) + expect { @new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::RegKeyValuesDataMissing) + end + + it "raises an error if action create and only name key present in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC" }]) + expect { @new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::RegKeyValuesTypeMissing) + end + + it "does not raise an error if action create and all keys are present in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC", type: :string, data: "my_data" }]) + expect { @new_resource.run_action(:create) }.to_not raise_error + end + it "creates missing keys if action create and parent does not exist and recursive is set to true" do @new_resource.key(reg_child + '\Missing1\Missing2') - @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) + @new_resource.values([{ name: "OC", type: :string, data: "MissingData" }]) @new_resource.recursive(true) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child + '\Missing1\Missing2')).to eq(true) - expect(@registry.value_exists?(reg_child + '\Missing1\Missing2', { :name => "OC", :type => :string, :data => "MissingData" })).to eq(true) + expect(@registry.value_exists?(reg_child + '\Missing1\Missing2', { name: "OC", type: :string, data: "MissingData" })).to eq(true) end it "creates key with multiple value as specified" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "one", :type => :string, :data => "1" }, { :name => "two", :type => :string, :data => "2" }, { :name => "three", :type => :string, :data => "3" }]) + @new_resource.values([{ name: "one", type: :string, data: "1" }, { name: "two", type: :string, data: "2" }, { name: "three", type: :string, data: "3" }]) @new_resource.recursive(true) @new_resource.run_action(:create) - @new_resource.values.each do |value| + @new_resource.each_value do |value| expect(@registry.value_exists?(reg_child, value)).to eq(true) end end @@ -235,12 +259,12 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do end it "creates a key in a 32-bit registry that is not viewable in 64-bit" do @new_resource.key(reg_child + '\Atraxi' ) - @new_resource.values([{ :name => "OC", :type => :string, :data => "Data" }]) + @new_resource.values([{ name: "OC", type: :string, data: "Data" }]) @new_resource.recursive(true) @new_resource.architecture(:i386) @new_resource.run_action(:create) @registry.architecture = :i386 - expect(@registry.data_exists?(reg_child + '\Atraxi', { :name => "OC", :type => :string, :data => "Data" })).to eq(true) + expect(@registry.data_exists?(reg_child + '\Atraxi', { name: "OC", type: :string, data: "Data" })).to eq(true) @registry.architecture = :x86_64 expect(@registry.key_exists?(reg_child + '\Atraxi')).to eq(false) end @@ -248,7 +272,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do it "prepares the reporting data for action :create" do @new_resource.key(reg_child + '\Ood') - @new_resource.values([{ :name => "ReportingVal1", :type => :string, :data => "report1" }, { :name => "ReportingVal2", :type => :string, :data => "report2" }]) + @new_resource.values([{ name: "ReportingVal1", type: :string, data: "report1" }, { name: "ReportingVal2", type: :string, data: "report2" }]) @new_resource.recursive(true) @new_resource.run_action(:create) @report = @resource_reporter.prepare_run_data @@ -257,8 +281,8 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_child + '\Ood') - expect(@report["resources"][0]["after"][:values]).to eq([{ :name => "ReportingVal1", :type => :string, :data => "report1" }, - { :name => "ReportingVal2", :type => :string, :data => "report2" }]) + expect(@report["resources"][0]["after"][:values]).to eq([{ name: "ReportingVal1", type: :string, data: "report1" }, + { name: "ReportingVal2", type: :string, data: "report2" }]) expect(@report["resources"][0]["before"][:values]).to eq([]) expect(@report["resources"][0]["result"]).to eq("create") expect(@report["status"]).to eq("success") @@ -266,101 +290,154 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do end context "while running in whyrun mode" do - before (:each) do + before(:each) do Chef::Config[:why_run] = true end - it "does not throw an exception if the keys do not exist but recursive is set to false" do + it "does not raise an exception if the keys do not exist but recursive is set to false" do @new_resource.key(reg_child + '\Slitheen\Raxicoricofallapatorius') - @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) + @new_resource.values([{ name: "BriskWalk", type: :string, data: "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create) # should not raise_error expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) expect(@registry.key_exists?(reg_child + '\Slitheen\Raxicoricofallapatorius')).to eq(false) end + it "does not create key if the action is create" do @new_resource.key(reg_child + '\Slitheen') - @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) + @new_resource.values([{ name: "BriskWalk", type: :string, data: "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) end + + it "does not raise an exception if the action create and type key missing in values hash" do + @new_resource.key(reg_child + '\Slitheen') + @new_resource.values([{ name: "BriskWalk", data: "my_data" }]) + @new_resource.run_action(:create) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) + end + + it "does not raise an exception if the action create and data key missing in values hash" do + @new_resource.key(reg_child + '\Slitheen') + @new_resource.values([{ name: "BriskWalk", type: :string }]) + @new_resource.run_action(:create) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) + end + + it "does not raise an exception if the action create and only name key present in values hash" do + @new_resource.key(reg_child + '\Slitheen') + @new_resource.values([{ name: "BriskWalk" }]) + @new_resource.run_action(:create) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) + end + + it "does not raise an exception if the action create and all keys are present in values hash" do + @new_resource.key(reg_child + '\Slitheen') + @new_resource.values([{ name: "BriskWalk", type: :string, data: "my_data" }]) + @new_resource.run_action(:create) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) + end end end context "when action is create_if_missing" do - before (:all) do + before(:all) do reset_registry end it "creates registry key, value if the key is missing" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) + @new_resource.values([{ name: "Color", type: :string, data: "Orange" }]) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_parent)).to eq(true) expect(@registry.key_exists?(reg_child)).to eq(true) - expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Color", type: :string, data: "Orange" })).to eq(true) end it "does not create the key if it already exists with same value, type and data" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) + @new_resource.values([{ name: "Color", type: :string, data: "Orange" }]) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child)).to eq(true) - expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Color", type: :string, data: "Orange" })).to eq(true) end it "creates a value if it does not exist" do @new_resource.key(reg_child) - @new_resource.values([{ :name => "Mango", :type => :string, :data => "Yellow" }]) + @new_resource.values([{ name: "Mango", type: :string, data: "Yellow" }]) @new_resource.run_action(:create_if_missing) - expect(@registry.data_exists?(reg_child, { :name => "Mango", :type => :string, :data => "Yellow" })).to eq(true) + expect(@registry.data_exists?(reg_child, { name: "Mango", type: :string, data: "Yellow" })).to eq(true) end it "creates subkey if parent exists" do @new_resource.key(reg_child + '\Pyrovile') - @new_resource.values([{ :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} }]) + @new_resource.values([{ name: "Chef", type: :multi_string, data: %w{OpscodeOrange Rules} }]) @new_resource.recursive(false) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child + '\Pyrovile')).to eq(true) - expect(@registry.value_exists?(reg_child + '\Pyrovile', { :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} })).to eq(true) + expect(@registry.value_exists?(reg_child + '\Pyrovile', { name: "Chef", type: :multi_string, data: %w{OpscodeOrange Rules} })).to eq(true) end - it "gives error if action create and parent does not exist and recursive is set to false" do + it "raises an error if action create and parent does not exist and recursive is set to false" do @new_resource.key(reg_child + '\Sontaran\Sontar') - @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) + @new_resource.values([{ name: "OC", type: :string, data: "MissingData" }]) @new_resource.recursive(false) expect { @new_resource.run_action(:create_if_missing) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end + it "raises an error if action create_if_missing and type key missing in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC", data: "my_data" }]) + expect { @new_resource.run_action(:create_if_missing) }.to raise_error(Chef::Exceptions::RegKeyValuesTypeMissing) + end + + it "raises an error if action create_if_missing and data key missing in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC", type: :string }]) + expect { @new_resource.run_action(:create_if_missing) }.to raise_error(Chef::Exceptions::RegKeyValuesDataMissing) + end + + it "raises an error if action create_if_missing and only name key present in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC" }]) + expect { @new_resource.run_action(:create_if_missing) }.to raise_error(Chef::Exceptions::RegKeyValuesTypeMissing) + end + + it "does not raise an error if action create_if_missing and all keys are present in values hash" do + @new_resource.key(reg_child) + @new_resource.values([{ name: "OC", type: :string, data: "my_data" }]) + expect { @new_resource.run_action(:create_if_missing) }.to_not raise_error + end + it "creates missing keys if action create and parent does not exist and recursive is set to true" do @new_resource.key(reg_child + '\Sontaran\Sontar') - @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) + @new_resource.values([{ name: "OC", type: :string, data: "MissingData" }]) @new_resource.recursive(true) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child + '\Sontaran\Sontar')).to eq(true) - expect(@registry.value_exists?(reg_child + '\Sontaran\Sontar', { :name => "OC", :type => :string, :data => "MissingData" })).to eq(true) + expect(@registry.value_exists?(reg_child + '\Sontaran\Sontar', { name: "OC", type: :string, data: "MissingData" })).to eq(true) end it "creates key with multiple value as specified" do @new_resource.key(reg_child + '\Adipose') - @new_resource.values([{ :name => "one", :type => :string, :data => "1" }, { :name => "two", :type => :string, :data => "2" }, { :name => "three", :type => :string, :data => "3" }]) + @new_resource.values([{ name: "one", type: :string, data: "1" }, { name: "two", type: :string, data: "2" }, { name: "three", type: :string, data: "3" }]) @new_resource.recursive(true) @new_resource.run_action(:create_if_missing) - @new_resource.values.each do |value| + @new_resource.each_value do |value| expect(@registry.value_exists?(reg_child + '\Adipose', value)).to eq(true) end end it "prepares the reporting data for :create_if_missing" do @new_resource.key(reg_child + '\Judoon') - @new_resource.values([{ :name => "ReportingVal3", :type => :string, :data => "report3" }]) + @new_resource.values([{ name: "ReportingVal3", type: :string, data: "report3" }]) @new_resource.recursive(true) @new_resource.run_action(:create_if_missing) @report = @resource_reporter.prepare_run_data @@ -369,7 +446,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_child + '\Judoon') - expect(@report["resources"][0]["after"][:values]).to eq([{ :name => "ReportingVal3", :type => :string, :data => "report3" }]) + expect(@report["resources"][0]["after"][:values]).to eq([{ name: "ReportingVal3", type: :string, data: "report3" }]) expect(@report["resources"][0]["before"][:values]).to eq([]) expect(@report["resources"][0]["result"]).to eq("create_if_missing") expect(@report["status"]).to eq("success") @@ -377,25 +454,54 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do end context "while running in whyrun mode" do - before (:each) do + before(:each) do Chef::Config[:why_run] = true end - it "does not throw an exception if the keys do not exist but recursive is set to false" do + it "does not raise an exception if the keys do not exist but recursive is set to false" do @new_resource.key(reg_child + '\Zygons\Zygor') - @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) + @new_resource.values([{ name: "BriskWalk", type: :string, data: "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create_if_missing) # should not raise_error expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) expect(@registry.key_exists?(reg_child + '\Zygons\Zygor')).to eq(false) end + it "does nothing if the action is create_if_missing" do @new_resource.key(reg_child + '\Zygons') - @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) + @new_resource.values([{ name: "BriskWalk", type: :string, data: "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) end + + it "does not raise an exception if the action create_if_missing and type key missing in values hash" do + @new_resource.key(reg_child + '\Zygons') + @new_resource.values([{ name: "BriskWalk", data: "my_data" }]) + @new_resource.run_action(:create_if_missing) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) + end + + it "does not raise an exception if the action create_if_missing and data key missing in values hash" do + @new_resource.key(reg_child + '\Zygons') + @new_resource.values([{ name: "BriskWalk", type: :string }]) + @new_resource.run_action(:create_if_missing) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) + end + + it "does not raise an exception if the action create_if_missing and only name key present in values hash" do + @new_resource.key(reg_child + '\Zygons') + @new_resource.values([{ name: "BriskWalk" }]) + @new_resource.run_action(:create_if_missing) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) + end + + it "does not raise an exception if the action create_if_missing and all keys are present in values hash" do + @new_resource.key(reg_child + '\Zygons') + @new_resource.values([{ name: "BriskWalk", type: :string, data: "my_data" }]) + @new_resource.run_action(:create_if_missing) # should not raise_error + expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) + end end end @@ -416,66 +522,66 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do end it "takes no action if the key exists but the value does not" do - expect(@registry.data_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) + expect(@registry.data_exists?(reg_parent + '\Opscode', { name: "Color", type: :string, data: "Orange" })).to eq(true) @new_resource.key(reg_parent + '\Opscode') - @new_resource.values([{ :name => "LooksLike", :type => :multi_string, :data => %w{SeattleGrey OCOrange} }]) + @new_resource.values([{ name: "LooksLike", type: :multi_string, data: %w{SeattleGrey OCOrange} }]) @new_resource.recursive(false) @new_resource.run_action(:delete) - expect(@registry.data_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) + expect(@registry.data_exists?(reg_parent + '\Opscode', { name: "Color", type: :string, data: "Orange" })).to eq(true) end it "deletes only specified values under a key path" do @new_resource.key(reg_parent + '\Opscode') - @new_resource.values([{ :name => "Opscode", :type => :multi_string, :data => %w{Seattle Washington} }, { :name => "AKA", :type => :string, :data => "OC" }]) + @new_resource.values([{ name: "Opscode", type: :multi_string, data: %w{Seattle Washington} }, { name: "AKA", type: :string, data: "OC" }]) @new_resource.recursive(false) @new_resource.run_action(:delete) - expect(@registry.data_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) - expect(@registry.value_exists?(reg_parent + '\Opscode', { :name => "AKA", :type => :string, :data => "OC" })).to eq(false) - expect(@registry.value_exists?(reg_parent + '\Opscode', { :name => "Opscode", :type => :multi_string, :data => %w{Seattle Washington} })).to eq(false) + expect(@registry.data_exists?(reg_parent + '\Opscode', { name: "Color", type: :string, data: "Orange" })).to eq(true) + expect(@registry.value_exists?(reg_parent + '\Opscode', { name: "AKA", type: :string, data: "OC" })).to eq(false) + expect(@registry.value_exists?(reg_parent + '\Opscode', { name: "Opscode", type: :multi_string, data: %w{Seattle Washington} })).to eq(false) end it "it deletes the values with the same name irrespective of it type and data" do @new_resource.key(reg_parent + '\Opscode') - @new_resource.values([{ :name => "Color", :type => :multi_string, :data => %w{Black Orange} }]) + @new_resource.values([{ name: "Color", type: :multi_string, data: %w{Black Orange} }]) @new_resource.recursive(false) @new_resource.run_action(:delete) - expect(@registry.value_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(false) + expect(@registry.value_exists?(reg_parent + '\Opscode', { name: "Color", type: :string, data: "Orange" })).to eq(false) end it "prepares the reporting data for action :delete" do @new_resource.key(reg_parent + '\ReportKey') - @new_resource.values([{ :name => "ReportVal4", :type => :string, :data => "report4" }, { :name => "ReportVal5", :type => :string, :data => "report5" }]) + @new_resource.values([{ name: "ReportVal4", type: :string, data: "report4" }, { name: "ReportVal5", type: :string, data: "report5" }]) @new_resource.recursive(true) @new_resource.run_action(:delete) @report = @resource_reporter.prepare_run_data - expect(@registry.value_exists?(reg_parent + '\ReportKey', [{ :name => "ReportVal4", :type => :string, :data => "report4" }, { :name => "ReportVal5", :type => :string, :data => "report5" }])).to eq(false) + expect(@registry.value_exists?(reg_parent + '\ReportKey', [{ name: "ReportVal4", type: :string, data: "report4" }, { name: "ReportVal5", type: :string, data: "report5" }])).to eq(false) expect(@report["action"]).to eq("end") expect(@report["resources"].count).to eq(1) expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_parent + '\ReportKey') - expect(@report["resources"][0]["before"][:values]).to eq([{ :name => "ReportVal4", :type => :string, :data => "report4" }, - { :name => "ReportVal5", :type => :string, :data => "report5" }]) - #Not testing for after values to match since after -> new_resource values. + expect(@report["resources"][0]["before"][:values]).to eq([{ name: "ReportVal4", type: :string, data: "report4" }, + { name: "ReportVal5", type: :string, data: "report5" }]) + # Not testing for after values to match since after -> new_resource values. expect(@report["resources"][0]["result"]).to eq("delete") expect(@report["status"]).to eq("success") expect(@report["total_res_count"]).to eq("1") end context "while running in whyrun mode" do - before (:each) do + before(:each) do Chef::Config[:why_run] = true end it "does nothing if the action is delete" do @new_resource.key(reg_parent + '\OpscodeWhyRun') - @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) + @new_resource.values([{ name: "BriskWalk", type: :string, data: "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:delete) @@ -485,7 +591,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do end context "when the action is delete_key" do - before (:all) do + before(:all) do reset_registry create_deletable_keys end @@ -516,7 +622,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do it "ignores the values under a key" do @new_resource.key(reg_parent + '\OpscodeIgnoredValues') - #@new_resource.values([{:name=>"DontExist", :type=>:string, :data=>"These will be ignored anyways"}]) + # @new_resource.values([{:name=>"DontExist", :type=>:string, :data=>"These will be ignored anyways"}]) @new_resource.recursive(true) @new_resource.run_action(:delete_key) end @@ -539,27 +645,27 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_parent + '\ReportKey') - #Not testing for before or after values to match since - #after -> new_resource.values and - #before -> current_resource.values + # Not testing for before or after values to match since + # after -> new_resource.values and + # before -> current_resource.values expect(@report["resources"][0]["result"]).to eq("delete_key") expect(@report["status"]).to eq("success") expect(@report["total_res_count"]).to eq("1") end context "while running in whyrun mode" do - before (:each) do + before(:each) do Chef::Config[:why_run] = true end it "does not throw an exception if the key has subkeys but recursive is set to false" do @new_resource.key(reg_parent + '\OpscodeWhyRun') - @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) + @new_resource.values([{ name: "BriskWalk", type: :string, data: "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:delete_key) end it "does nothing if the action is delete_key" do @new_resource.key(reg_parent + '\OpscodeWhyRun') - @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) + @new_resource.values([{ name: "BriskWalk", type: :string, data: "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:delete_key) diff --git a/spec/functional/resource/remote_directory_spec.rb b/spec/functional/resource/remote_directory_spec.rb index c25e51cf2a..2d9d1de351 100644 --- a/spec/functional/resource/remote_directory_spec.rb +++ b/spec/functional/resource/remote_directory_spec.rb @@ -1,6 +1,6 @@ # # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb index 1f92a567f3..09e4fdccb4 100644 --- a/spec/functional/resource/remote_file_spec.rb +++ b/spec/functional/resource/remote_file_spec.rb @@ -1,6 +1,6 @@ # # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,9 @@ describe Chef::Resource::RemoteFile do before(:each) do @old_file_cache = Chef::Config[:file_cache_path] Chef::Config[:file_cache_path] = file_cache_path + Chef::Config[:rest_timeout] = 2 + Chef::Config[:http_retry_delay] = 1 + Chef::Config[:http_retry_count] = 2 end after(:each) do @@ -55,11 +58,11 @@ describe Chef::Resource::RemoteFile do let(:default_mode) { (0666 & ~File.umask).to_s(8) } context "when fetching files over HTTP" do - before(:each) do - start_tiny_server + before(:all) do + start_tiny_server(RequestTimeout: 1) end - after(:each) do + after(:all) do stop_tiny_server end @@ -97,21 +100,22 @@ describe Chef::Resource::RemoteFile do context "when fetching files over HTTPS" do - before(:each) do + before(:all) do cert_text = File.read(File.expand_path("ssl/chef-rspec.cert", CHEF_SPEC_DATA)) cert = OpenSSL::X509::Certificate.new(cert_text) key_text = File.read(File.expand_path("ssl/chef-rspec.key", CHEF_SPEC_DATA)) key = OpenSSL::PKey::RSA.new(key_text) - server_opts = { :SSLEnable => true, - :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, - :SSLCertificate => cert, - :SSLPrivateKey => key } + server_opts = { SSLEnable: true, + SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE, + SSLCertificate: cert, + SSLPrivateKey: key, + RequestTimeout: 1 } - start_tiny_server(server_opts) + start_tiny_server(**server_opts) end - after(:each) do + after(:all) do stop_tiny_server end @@ -123,12 +127,183 @@ describe Chef::Resource::RemoteFile do end + context "when running on Windows", :windows_only do + describe "when fetching files over SMB" do + include Chef::Mixin::ShellOut + let(:smb_share_root_directory) { directory = File.join(Dir.tmpdir, make_tmpname("windows_script_test")); Dir.mkdir(directory); directory } + let(:smb_file_local_file_name) { "smb_file.txt" } + let(:smb_file_local_path) { File.join( smb_share_root_directory, smb_file_local_file_name ) } + let(:smb_share_name) { "chef_smb_test" } + let(:smb_remote_path) { File.join("//#{ENV["COMPUTERNAME"]}", smb_share_name, smb_file_local_file_name).tr("/", "\\") } + let(:smb_file_content) { "hellofun" } + let(:local_destination_path) { File.join(Dir.tmpdir, make_tmpname("chef_remote_file")) } + let(:windows_current_user) { ENV["USERNAME"] } + let(:windows_current_user_domain) { ENV["USERDOMAIN"] || ENV["COMPUTERNAME"] } + let(:windows_current_user_qualified) { "#{windows_current_user_domain}\\#{windows_current_user}" } + + let(:remote_domain) { nil } + let(:remote_user) { nil } + let(:remote_password) { nil } + + let(:resource) do + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + resource = Chef::Resource::RemoteFile.new(path, run_context) + end + + before do + shell_out("net.exe share #{smb_share_name} /delete") + File.write(smb_file_local_path, smb_file_content ) + shell_out!("net.exe share #{smb_share_name}=\"#{smb_share_root_directory.tr("/", '\\')}\" /grant:\"authenticated users\",read") + end + + after do + shell_out("net.exe share #{smb_share_name} /delete") + File.delete(smb_file_local_path) if File.exist?(smb_file_local_path) + File.delete(local_destination_path) if File.exist?(local_destination_path) + Dir.rmdir(smb_share_root_directory) + end + + context "when configuring the Windows identity used to access the remote file" do + before do + resource.path(local_destination_path) + resource.source(smb_remote_path) + resource.remote_domain(remote_domain) + resource.remote_user(remote_user) + resource.remote_password(remote_password) + resource.node.default["platform_family"] = "windows" + allow_any_instance_of(Chef::Provider::RemoteFile::NetworkFile).to receive(:node).and_return({ "platform_family" => "windows" }) + end + + shared_examples_for "a remote_file resource accessing a remote file to which the specified user has access" do + it "has the same content as the original file" do + expect { resource.run_action(:create) }.not_to raise_error + expect(::File.read(local_destination_path).chomp).to eq smb_file_content + end + end + + shared_examples_for "a remote_file resource accessing a remote file to which the specified user does not have access" do + it "causes an error to be raised" do + expect { resource.run_action(:create) }.to raise_error(Errno::EACCES) + end + end + + shared_examples_for "a remote_file resource accessing a remote file with invalid user" do + it "causes an error to be raised" do + allow(Chef::Util::Windows::LogonSession).to receive(:validate_session_open!).and_return(true) + expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::Win32APIError) + end + end + + context "when the file is accessible to non-admin users only as the current identity" do + before do + shell_out!("icacls #{smb_file_local_path} /grant:r \"authenticated users:(W)\" /grant \"#{windows_current_user_qualified}:(R)\" /inheritance:r") + end + + context "when the resource is accessed using the current user's identity" do + let(:remote_user) { nil } + let(:remote_domain) { nil } + let(:remote_password) { nil } + + it_behaves_like "a remote_file resource accessing a remote file to which the specified user has access" + + describe "uses the ::Chef::Provider::RemoteFile::NetworkFile::TRANSFER_CHUNK_SIZE constant to chunk the file" do + let(:invalid_chunk_size) { -1 } + before do + stub_const("::Chef::Provider::RemoteFile::NetworkFile::TRANSFER_CHUNK_SIZE", invalid_chunk_size) + end + + it "raises an ArgumentError when the chunk size is negative" do + expect(::Chef::Provider::RemoteFile::NetworkFile::TRANSFER_CHUNK_SIZE).to eq(invalid_chunk_size) + expect { resource.run_action(:create) }.to raise_error(ArgumentError) + end + end + + context "when the file must be transferred in more than one chunk" do + before do + stub_const("::Chef::Provider::RemoteFile::NetworkFile::TRANSFER_CHUNK_SIZE", 3) + end + it_behaves_like "a remote_file resource accessing a remote file to which the specified user has access" + end + end + + context "when the resource is accessed using an alternate user's identity with no access to the file" do + let(:windows_nonadmin_user) { "chefremfile1" } + let(:windows_nonadmin_user_password) { "j82ajfxK3;2Xe1" } + include_context "a non-admin Windows user" + + before do + shell_out!("icacls #{smb_file_local_path} /grant:r \"authenticated users:(W)\" /deny \"#{windows_current_user_qualified}:(R)\" /inheritance:r") + end + + let(:remote_user) { windows_nonadmin_user } + let(:remote_domain) { windows_nonadmin_user_domain } + let(:remote_password) { windows_nonadmin_user_password } + + it_behaves_like "a remote_file resource accessing a remote file to which the specified user does not have access" + end + end + + context "when the the file is only accessible as a specific alternate identity" do + let(:windows_nonadmin_user) { "chefremfile2" } + let(:windows_nonadmin_user_password) { "j82ajfxK3;2Xe2" } + include_context "a non-admin Windows user" + + before do + shell_out!("icacls #{smb_file_local_path} /grant:r \"authenticated users:(W)\" /grant \"#{windows_current_user_qualified}:(R)\" /inheritance:r") + end + + context "when the resource is accessed using the specific non-qualified alternate user identity with access" do + let(:remote_user) { windows_nonadmin_user } + let(:remote_domain) { "." } + let(:remote_password) { windows_nonadmin_user_password } + + it_behaves_like "a remote_file resource accessing a remote file to which the specified user has access" + end + + context "when the resource is accessed using the specific alternate user identity with access and the domain is specified" do + let(:remote_user) { windows_nonadmin_user } + let(:remote_domain) { windows_nonadmin_user_domain } + let(:remote_password) { windows_nonadmin_user_password } + + it_behaves_like "a remote_file resource accessing a remote file to which the specified user has access" + end + + context "when the resource is accessed using the current user's identity" do + before do + shell_out!("icacls #{smb_file_local_path} /grant:r \"authenticated users:(W)\" /grant \"#{windows_nonadmin_user_qualified}:(R)\" /deny #{windows_current_user_qualified}:(R) /inheritance:r") + end + + it_behaves_like "a remote_file resource accessing a remote file to which the specified user does not have access" + end + + context "when the resource is accessed using an alternate user's identity with no access to the file" do + let(:windows_nonadmin_user) { "chefremfile3" } + let(:windows_nonadmin_user_password) { "j82ajfxK3;2Xe3" } + include_context "a non-admin Windows user" + + let(:remote_user) { windows_nonadmin_user_qualified } + let(:remote_domain) { nil } + let(:remote_password) { windows_nonadmin_user_password } + + before do + allow_any_instance_of(Chef::Util::Windows::LogonSession).to receive(:validate_session_open!).and_return(true) + end + + it_behaves_like "a remote_file resource accessing a remote file with invalid user" + end + end + end + end + end + context "when dealing with content length checking" do - before(:each) do - start_tiny_server + before(:all) do + start_tiny_server(RequestTimeout: 1) end - after(:each) do + after(:all) do stop_tiny_server end @@ -185,7 +360,7 @@ describe Chef::Resource::RemoteFile do it "should raise ContentLengthMismatch" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) - #File.should_not exist(path) # XXX: CHEF-5081 + # File.should_not exist(path) # XXX: CHEF-5081 end end @@ -198,7 +373,7 @@ describe Chef::Resource::RemoteFile do it "should raise ContentLengthMismatch" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) - #File.should_not exist(path) # XXX: CHEF-5081 + # File.should_not exist(path) # XXX: CHEF-5081 end end @@ -234,13 +409,7 @@ describe Chef::Resource::RemoteFile do it "should not create the file" do # This can legitimately raise either Errno::EADDRNOTAVAIL or Errno::ECONNREFUSED # in different Ruby versions. - old_value = RSpec::Expectations.configuration.on_potential_false_positives - RSpec::Expectations.configuration.on_potential_false_positives = :nothing - begin - expect { resource.run_action(:create) }.to raise_error - ensure - RSpec::Expectations.configuration.on_potential_false_positives = old_value - end + expect { resource.run_action(:create) }.to raise_error(SystemCallError) expect(File).not_to exist(path) end diff --git a/spec/functional/resource/rpm_spec.rb b/spec/functional/resource/rpm_spec.rb index ce9332e4ed..15b6731357 100644 --- a/spec/functional/resource/rpm_spec.rb +++ b/spec/functional/resource/rpm_spec.rb @@ -1,6 +1,6 @@ # # Author:: Prabhu Das (<prabhu.das@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,52 +17,49 @@ # require "spec_helper" -require "functional/resource/base" require "chef/mixin/shell_out" # run this test only for following platforms. -exclude_test = !%w{aix centos redhat suse}.include?(ohai[:platform]) -describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test do +exclude_test = !%w{aix rhel fedora suse amazon}.include?(ohai[:platform_family]) +describe Chef::Resource::RpmPackage, :requires_root, external: exclude_test do include Chef::Mixin::ShellOut let(:new_resource) do + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) new_resource = Chef::Resource::RpmPackage.new(@pkg_name, run_context) new_resource.source @pkg_path new_resource end def rpm_pkg_should_be_installed(resource) - case ohai[:platform] # Due to dependency issues , different rpm pkgs are used in different platforms. # dummy rpm package works in aix, without any dependency issues. - when "aix" + if ohai[:platform] == "aix" expect(shell_out("rpm -qa | grep dummy").exitstatus).to eq(0) # mytest rpm package works in centos, redhat and in suse without any dependency issues. - when "centos", "redhat", "suse" + else expect(shell_out("rpm -qa | grep mytest").exitstatus).to eq(0) - ::File.exists?("/opt/mytest/mytest.sh") # The mytest rpm package contains the mytest.sh file + ::File.exist?("/opt/mytest/mytest.sh") # The mytest rpm package contains the mytest.sh file end end def rpm_pkg_should_not_be_installed(resource) - case ohai[:platform] - when "aix" + if ohai[:platform] == "aix" expect(shell_out("rpm -qa | grep dummy").exitstatus).to eq(1) - when "centos", "redhat", "suse" + else expect(shell_out("rpm -qa | grep mytest").exitstatus).to eq(1) - !::File.exists?("/opt/mytest/mytest.sh") + !::File.exist?("/opt/mytest/mytest.sh") end end before(:all) do - case ohai[:platform] # Due to dependency issues , different rpm pkgs are used in different platforms. - when "aix" + if ohai[:platform] == "aix" @pkg_name = "dummy" @pkg_version = "1-0" @pkg_path = "#{Dir.tmpdir}/dummy-1-0.aix6.1.noarch.rpm" FileUtils.cp(File.join(CHEF_SPEC_ASSETS, "dummy-1-0.aix6.1.noarch.rpm") , @pkg_path) - when "centos", "redhat", "suse" + else @pkg_name = "mytest" @pkg_version = "1.0-1" @pkg_path = "#{Dir.tmpdir}/mytest-1.0-1.noarch.rpm" diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb index 32529fbb0c..4707fa8ee0 100644 --- a/spec/functional/resource/template_spec.rb +++ b/spec/functional/resource/template_spec.rb @@ -1,6 +1,6 @@ # # Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright 2011-2016, Chef Software Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ require "spec_helper" describe Chef::Resource::Template do def binread(file) - File.open(file, "rb") { |f| f.read } + File.open(file, "rb", &:read) end include_context Chef::Resource::File @@ -32,6 +32,7 @@ describe Chef::Resource::Template do let(:node) do node = Chef::Node.new node.normal[:slappiness] = "a warm gun" + node.normal[:nested][:secret] = "value" node end @@ -67,7 +68,7 @@ describe Chef::Resource::Template do context "when the target file does not exist" do it "creates the template with the rendered content using the variable attribute when the :create action is run" do resource.source("openldap_variable_stuff.conf.erb") - resource.variables(:secret => "nutella") + resource.variables(secret: "nutella") resource.run_action(:create) expect(IO.read(path)).to eq("super secret is nutella") end @@ -111,7 +112,7 @@ describe Chef::Resource::Template do context "using single helper syntax referencing @node" do before do node.normal[:helper_test_attr] = "value from helper method" - resource.helper(:helper_method) { "#{@node[:helper_test_attr]}" } + resource.helper(:helper_method) { (@node[:helper_test_attr]).to_s } end it_behaves_like "a template with helpers" @@ -202,11 +203,43 @@ describe Chef::Resource::Template do it "output should contain platform's line endings" do resource.run_action(:create) binread(path).each_line do |line| - expect(line).to end_with(Chef::Platform.windows? ? "\r\n" : "\n") + expect(line).to end_with(ChefUtils.windows? ? "\r\n" : "\n") end end end end end + describe "when template variables contain lazy{} calls" do + it "resolves the DelayedEvaluator" do + resource.source("openldap_variable_stuff.conf.erb") + resource.variables(secret: Chef::DelayedEvaluator.new { "nutella" }) + resource.run_action(:create) + expect(IO.read(path)).to eq("super secret is nutella") + end + + it "does not mutate the resource variables" do + resource.source("openldap_variable_stuff.conf.erb") + resource.variables(secret: Chef::DelayedEvaluator.new { "nutella" }) + resource.run_action(:create) + expect(resource.variables[:secret]).to be_a Chef::DelayedEvaluator + end + + it "resolves the DelayedEvaluator when deeply nested" do + resource.source("openldap_nested_variable_stuff.erb") + resource.variables(secret: [{ "key" => Chef::DelayedEvaluator.new { "nutella" } }]) + resource.run_action(:create) + expect(IO.read(path)).to eq("super secret is nutella") + end + end + + describe "when passing a node attribute mash as a template variable" do + it "uses the node attributes like a hash" do + resource.source("openldap_variable_stuff.conf.erb") + resource.variables(node[:nested]) + resource.run_action(:create) + expect(IO.read(path)).to eq("super secret is value") + end + end + end diff --git a/spec/functional/resource/timezone_spec.rb b/spec/functional/resource/timezone_spec.rb new file mode 100644 index 0000000000..d44d5b34a8 --- /dev/null +++ b/spec/functional/resource/timezone_spec.rb @@ -0,0 +1,41 @@ +# +# Author:: Gary Bright (<digitalgaz@hotmail.com>) +# 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::Resource::Timezone, :windows_only do + let(:timezone) { "GMT Standard Time" } + + def timezone_resource + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + + Chef::Resource::Timezone.new(timezone, run_context) + end + + describe "when a timezone is provided on windows" do + it "should set a timezone" do + timezone_resource.run_action(:set) + end + end + + describe "when a timezone is not provided on windows" do + let(:timezone) { nil } + it "raises an exception" do + expect { timezone_resource.run_action(:set) }.to raise_error(Chef::Exceptions::ValidationFailed) + end + end +end diff --git a/spec/functional/resource/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb index bedb37838c..50da812b0f 100644 --- a/spec/functional/resource/user/dscl_spec.rb +++ b/spec/functional/resource/user/dscl_spec.rb @@ -1,5 +1,5 @@ # -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,20 +19,17 @@ require "spec_helper" require "chef/mixin/shell_out" metadata = { - :mac_osx_only => true, - :requires_root => true, - :not_supported_on_mac_osx_106 => true, + macos_1013: true, + requires_root: true, } describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metadata do include Chef::Mixin::ShellOut def clean_user - begin - shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'") - rescue Mixlib::ShellOut::ShellCommandFailed - # Raised when the user is already cleaned - end + shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'") + rescue Mixlib::ShellOut::ShellCommandFailed + # Raised when the user is already cleaned end def user_should_exist @@ -124,13 +121,7 @@ describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metada describe "when password is being set via shadow hash" do let(:password) do - if node[:platform_version].start_with?("10.7.") - # On Mac 10.7 we only need to set the password - "c9b3bd1a0cde797eef0eff16c580dab996ba3a21961cccc\ -d0f5e65c61558243e50b1a490088bd4824e3b35562d383ca02260398\ -ef1979b302212ec1c5383d1d05fc8d843" - else - "c734b6e4787c3727bb35e29fdd92b97c\ + "c734b6e4787c3727bb35e29fdd92b97c\ 1de12df509577a045728255ec7c6c5f5\ c18efa05ed02b682ffa7ebc05119900e\ b1d4880833aa7a190afc13e2bf0936b8\ @@ -138,7 +129,6 @@ b1d4880833aa7a190afc13e2bf0936b8\ 9464a8c234f3919082400b4f939bb77b\ c5adbbac718b7eb99463a7b679571e0f\ 1c9fef2ef08d0b9e9c2bcf644eed2ffc" - end end let(:iterations) { 25000 } diff --git a/spec/functional/resource/user/mac_user_spec.rb b/spec/functional/resource/user/mac_user_spec.rb new file mode 100644 index 0000000000..dabc303afb --- /dev/null +++ b/spec/functional/resource/user/mac_user_spec.rb @@ -0,0 +1,207 @@ +# +# 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" +require "chef/mixin/shell_out" + +metadata = { + macos_gte_1014: true, + requires_root: true, +} + +describe "Chef::Resource::User with Chef::Provider::User::MacUser provider", metadata do + include Chef::Mixin::ShellOut + + def clean_user + shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'") + rescue Mixlib::ShellOut::ShellCommandFailed + # Raised when the user is already cleaned + end + + def ensure_file_cache_path_exists + path = Chef::Config["file_cache_path"] + FileUtils.mkdir_p(path) unless File.directory?(path) + end + + def user_should_exist + expect(shell_out("/usr/bin/dscl . -read /Users/#{username}").error?).to be(false) + end + + def check_password(pass) + # In order to test the password we use dscl passwd command since + # that's the only command that gets the user password from CLI. + expect(shell_out("dscl . -passwd /Users/greatchef #{pass} new_password").exitstatus).to eq(0) + # Now reset the password back + expect(shell_out("dscl . -passwd /Users/greatchef new_password #{pass}").exitstatus).to eq(0) + 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 + + let(:run_context) do + Chef::RunContext.new(node, {}, events) + end + + let(:username) do + "greatchef" + end + + let(:uid) { nil } + let(:gid) { 20 } + let(:home) { nil } + let(:manage_home) { false } + let(:password) { "XXXYYYZZZ" } + let(:comment) { "Great Chef" } + let(:shell) { "/bin/bash" } + let(:salt) { nil } + let(:iterations) { nil } + + let(:user_resource) do + r = Chef::Resource::User::MacUser.new("TEST USER RESOURCE", run_context) + r.username(username) + r.uid(uid) + r.gid(gid) + r.home(home) + r.shell(shell) + r.comment(comment) + r.manage_home(manage_home) + r.password(password) + r.salt(salt) + r.iterations(iterations) + r + end + + before do + clean_user + ensure_file_cache_path_exists + end + + after(:each) do + clean_user + end + + describe "action :create" do + it "should create the user" do + user_resource.run_action(:create) + user_should_exist + check_password(password) + end + end + + describe "when user exists" do + before do + existing_resource = user_resource.dup + existing_resource.run_action(:create) + user_should_exist + end + + describe "when password is updated" do + it "should update the password of the user" do + user_resource.password("mykitchen") + user_resource.run_action(:create) + check_password("mykitchen") + end + end + end + + describe "when password is being set via shadow hash" do + let(:password) do + "c734b6e4787c3727bb35e29fdd92b97c\ +1de12df509577a045728255ec7c6c5f5\ +c18efa05ed02b682ffa7ebc05119900e\ +b1d4880833aa7a190afc13e2bf0936b8\ +20123e8c98f0f9bcac2a629d9163caac\ +9464a8c234f3919082400b4f939bb77b\ +c5adbbac718b7eb99463a7b679571e0f\ +1c9fef2ef08d0b9e9c2bcf644eed2ffc" + end + + let(:iterations) { 25000 } + let(:salt) { "9e2e7d5ee473b496fd24cf0bbfcaedfcb291ee21740e570d1e917e874f8788ca" } + + it "action :create should create the user" do + user_resource.run_action(:create) + user_should_exist + check_password("soawesome") + end + + describe "when user exists" do + before do + existing_resource = user_resource.dup + existing_resource.run_action(:create) + user_should_exist + end + + describe "when password is updated" do + describe "without salt" do + let(:salt) { nil } + + it "it sets the password" do + user_resource.password("mykitchen") + user_resource.run_action(:create) + check_password("mykitchen") + end + end + + describe "with salt and plaintext password" do + it "raises Chef::Exceptions::User" do + expect do + user_resource.password("notasha512") + user_resource.run_action(:create) + end.to raise_error(Chef::Exceptions::User) + end + end + end + end + end + + describe "when a user is member of some groups" do + let(:groups) { %w{staff operator} } + + before do + existing_resource = user_resource.dup + existing_resource.run_action(:create) + + groups.each do |group| + shell_out!("/usr/bin/dscl . -append '/Groups/#{group}' GroupMembership #{username}") + end + end + + after do + groups.each do |group| + # Do not raise an error when user is correctly removed + shell_out("/usr/bin/dscl . -delete '/Groups/#{group}' GroupMembership #{username}") + end + end + + it ":remove action removes the user from the groups and deletes the user" do + user_resource.run_action(:remove) + groups.each do |group| + # Do not raise an error when group is empty + expect(shell_out("dscl . read /Groups/staff GroupMembership").stdout).not_to include(group) + end + end + end + +end diff --git a/spec/functional/resource/user/useradd_spec.rb b/spec/functional/resource/user/useradd_spec.rb deleted file mode 100644 index 79d62436f5..0000000000 --- a/spec/functional/resource/user/useradd_spec.rb +++ /dev/null @@ -1,698 +0,0 @@ -# encoding: UTF-8 -# -# Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2013-2016, 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 "functional/resource/base" -require "chef/mixin/shell_out" - -def resource_for_platform(username, run_context) - Chef::Resource.resource_for_node(:user, node).new(username, run_context) -end - -# ideally we could somehow pass an array of [ ...::Aix, ...::Linux ] to the -# filter, but we have to pick the right one for the O/S. -def user_provider_filter - case ohai[:os] - when "aix" - Chef::Provider::User::Aix - when "linux" - Chef::Provider::User::Linux - end -end - -metadata = { - :unix_only => true, - :requires_root => true, - :not_supported_on_mac_osx => true, - :provider => { :user => user_provider_filter }, -} - -describe Chef::Provider::User::Useradd, metadata do - - include Chef::Mixin::ShellOut - - # Utility code for /etc/passwd interaction, avoid any caching of user records: - PwEntry = Struct.new(:name, :passwd, :uid, :gid, :gecos, :home, :shell) - - class UserNotFound < StandardError; end - - def pw_entry - passwd_file = File.open("/etc/passwd", "rb") { |f| f.read } - matcher = /^#{Regexp.escape(username)}.+$/ - if passwd_entry = passwd_file.scan(matcher).first - PwEntry.new(*passwd_entry.split(":")) - else - raise UserNotFound, "no entry matching #{matcher.inspect} found in /etc/passwd" - end - end - - def etc_shadow - case ohai[:platform] - when "aix" - File.open("/etc/security/passwd") { |f| f.read } - else - File.open("/etc/shadow") { |f| f.read } - end - end - - def self.quote_in_username_unsupported? - if OHAI_SYSTEM["platform_family"] == "debian" - false - else - "Only debian family systems support quotes in username" - end - end - - def password_should_be_set - if ohai[:platform] == "aix" - expect(pw_entry.passwd).to eq("!") - else - expect(pw_entry.passwd).to eq("x") - end - end - - def try_cleanup - ["/home/cheftestfoo", "/home/cheftestbar", "/home/cf-test"].each do |f| - FileUtils.rm_rf(f) if File.exists? f - end - - ["cf-test"].each do |u| - r = resource_for_platform("DELETE USER", run_context) - r.manage_home true - r.username("cf-test") - r.run_action(:remove) - end - end - - before do - # Silence shell_out live stream - Chef::Log.level = :warn - try_cleanup - end - - after do - max_retries = 3 - while max_retries > 0 - begin - pw_entry # will raise if the user doesn't exist - status = shell_out!("userdel", "-r", username, :returns => [0, 8, 12]) - - # Error code 8 during userdel indicates that the user is logged in. - # This occurs randomly because the accounts daemon holds a lock due to which userdel fails. - # The work around is to retry userdel for 3 times. - break if status.exitstatus != 8 - - sleep 1 - max_retries = max_retries - 1 - rescue UserNotFound - break - end - end - - status.error! if max_retries == 0 - 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 - - let(:run_context) do - Chef::RunContext.new(node, {}, events) - end - - let(:username) { "cf-test" } - let(:uid) { nil } - let(:home) { nil } - let(:manage_home) { false } - let(:password) { nil } - let(:system) { false } - let(:comment) { nil } - - let(:user_resource) do - r = resource_for_platform("TEST USER RESOURCE", run_context) - r.username(username) - r.uid(uid) - r.home(home) - r.comment(comment) - r.manage_home(manage_home) - r.password(password) - r.system(system) - r - end - - let(:expected_shadow) do - if ohai[:platform] == "aix" - expected_shadow = "cf-test" # For aix just check user entry in shadow file - else - expected_shadow = "cf-test:$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - describe "action :create" do - - context "when the user does not exist beforehand" do - before do - user_resource.run_action(:create) - expect(user_resource).to be_updated_by_last_action - end - - it "ensures the user exists" do - expect(pw_entry.name).to eq(username) - end - - # On Debian, the only constraints are that usernames must neither start - # with a dash ('-') nor plus ('+') nor tilde ('~') nor contain a colon - # (':'), a comma (','), or a whitespace (space: ' ', end of line: '\n', - # tabulation: '\t', etc.). Note that using a slash ('/') may break the - # default algorithm for the definition of the user's home directory. - - context "and the username contains a single quote", skip: quote_in_username_unsupported? do - - let(:username) { "t'bilisi" } - - it "ensures the user exists" do - expect(pw_entry.name).to eq(username) - end - end - - context "when uid is set" do - # Should verify uid not in use... - let(:uid) { 1999 } - - it "ensures the user has the given uid" do - expect(pw_entry.uid).to eq("1999") - end - end - - context "when comment is set" do - let(:comment) { "hello this is dog" } - - it "ensures the comment is set" do - expect(pw_entry.gecos).to eq("hello this is dog") - end - - context "in standard gecos format" do - let(:comment) { "Bobo T. Clown,some building,555-555-5555,@boboclown" } - - it "ensures the comment is set" do - expect(pw_entry.gecos).to eq(comment) - end - end - - context "to a string containing multibyte characters" do - let(:comment) { "(╯°□°)╯︵ ┻━┻" } - - it "ensures the comment is set" do - actual = pw_entry.gecos - actual.force_encoding(Encoding::UTF_8) if "".respond_to?(:force_encoding) - expect(actual).to eq(comment) - end - end - - context "to a string containing an apostrophe `'`" do - let(:comment) { "don't go" } - - it "ensures the comment is set" do - expect(pw_entry.gecos).to eq(comment) - end - end - end - - context "when home is set" do - let(:home) { "/home/#{username}" } - - it "ensures the user's home is set to the given path" do - expect(pw_entry.home).to eq(home) - end - - it "does not create the home dir without `manage_home'" do - expect(File).not_to exist(home) - end - - context "and manage_home is enabled" do - let(:manage_home) { true } - - it "ensures the user's home directory exists" do - expect(File).to exist(home) - end - end - - context "and manage_home is the default" do - let(:manage_home) { nil } - - it "does not create the home dir without `manage_home'" do - expect(File).not_to exist(home) - end - end - end - - context "when a password is specified" do - # openssl passwd -1 "secretpassword" - let(:password) do - case ohai[:platform] - when "aix" - "eL5qfEVznSNss" - else - "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - it "sets the user's shadow password" do - password_should_be_set - expect(etc_shadow).to include(expected_shadow) - end - end - - context "when a system user is specified", skip: aix? do - let(:system) { true } - let(:uid_min) do - # from `man useradd`, login user means uid will be between - # UID_SYS_MIN and UID_SYS_MAX defined in /etc/login.defs. On my - # Ubuntu 13.04 system, these are commented out, so we'll look at - # UID_MIN to find the lower limit of the non-system-user range, and - # use that value in our assertions. - login_defs = File.open("/etc/login.defs", "rb") { |f| f.read } - uid_min_scan = /^UID_MIN\s+(\d+)/ - login_defs.match(uid_min_scan)[1] - end - - it "ensures the user has the properties of a system user" do - expect(pw_entry.uid.to_i).to be < uid_min.to_i - end - end - end # when the user does not exist beforehand - - context "when the user already exists" do - - let(:expect_updated?) { true } - - let(:existing_uid) { nil } - let(:existing_home) { nil } - let(:existing_manage_home) { false } - let(:existing_password) { nil } - let(:existing_system) { false } - let(:existing_comment) { nil } - - let(:existing_user) do - r = resource_for_platform("TEST USER RESOURCE", run_context) - # username is identity attr, must match. - r.username(username) - r.uid(existing_uid) - r.home(existing_home) - r.comment(existing_comment) - r.manage_home(existing_manage_home) - r.password(existing_password) - r.system(existing_system) - r - end - - before do - if reason = skip - skip(reason) - end - existing_user.run_action(:create) - expect(existing_user).to be_updated_by_last_action - user_resource.run_action(:create) - expect(user_resource.updated_by_last_action?).to eq(expect_updated?) - end - - context "and all properties are in the desired state" do - let(:uid) { 1999 } - let(:home) { "/home/bobo" } - let(:manage_home) { true } - # openssl passwd -1 "secretpassword" - let(:password) do - case ohai[:platform] - when "aix" - "eL5qfEVznSNss" - else - "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - let(:system) { false } - let(:comment) { "hello this is dog" } - - let(:existing_uid) { uid } - let(:existing_home) { home } - let(:existing_manage_home) { manage_home } - let(:existing_password) { password } - let(:existing_system) { false } - let(:existing_comment) { comment } - - let(:expect_updated?) { false } - - it "does not update the user" do - expect(user_resource).not_to be_updated - end - end - - context "and the uid is updated" do - let(:uid) { 1999 } - let(:existing_uid) { 1998 } - - it "ensures the uid is set to the desired value" do - expect(pw_entry.uid).to eq("1999") - end - end - - context "and the comment is updated" do - let(:comment) { "hello this is dog" } - let(:existing_comment) { "woof" } - - it "ensures the comment field is set to the desired value" do - expect(pw_entry.gecos).to eq("hello this is dog") - end - end - - context "and home directory is updated" do - let(:existing_home) { "/home/cheftestfoo" } - let(:home) { "/home/cheftestbar" } - it "ensures the home directory is set to the desired value" do - expect(pw_entry.home).to eq("/home/cheftestbar") - end - - context "and manage_home is enabled" do - let(:existing_manage_home) { true } - let(:manage_home) { true } - it "moves the home directory to the new location" do - expect(File).not_to exist("/home/cheftestfoo") - expect(File).to exist("/home/cheftestbar") - end - end - - context "and manage_home wasn't enabled but is now" do - let(:existing_manage_home) { false } - let(:manage_home) { true } - - if %w{rhel fedora}.include?(OHAI_SYSTEM["platform_family"]) - # Inconsistent behavior. See: CHEF-2205 - it "created the home dir b/c of CHEF-2205 so it still exists" do - # This behavior seems contrary to expectation and non-convergent. - expect(File).not_to exist("/home/cheftestfoo") - expect(File).to exist("/home/cheftestbar") - end - elsif ohai[:platform] == "aix" - it "creates the home dir in the desired location" do - expect(File).not_to exist("/home/cheftestfoo") - expect(File).to exist("/home/cheftestbar") - end - else - it "does not create the home dir in the desired location (XXX)" do - # This behavior seems contrary to expectation and non-convergent. - expect(File).not_to exist("/home/cheftestfoo") - expect(File).not_to exist("/home/cheftestbar") - end - end - end - - context "and manage_home was enabled but is not now" do - let(:existing_manage_home) { true } - let(:manage_home) { false } - - it "leaves the old home directory around (XXX)" do - # Would it be better to remove the old home? - expect(File).to exist("/home/cheftestfoo") - expect(File).not_to exist("/home/cheftestbar") - end - end - end - - context "and a password is added" do - # openssl passwd -1 "secretpassword" - let(:password) do - case ohai[:platform] - when "aix" - "eL5qfEVznSNss" - else - "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - it "ensures the password is set" do - password_should_be_set - expect(etc_shadow).to include(expected_shadow) - end - - end - - context "and the password is updated" do - # openssl passwd -1 "OLDpassword" - let(:existing_password) do - case ohai[:platform] - when "aix" - "jkzG6MvUxjk2g" - else - "$1$1dVmwm4z$CftsFn8eBDjDRUytYKkXB." - end - end - - # openssl passwd -1 "secretpassword" - let(:password) do - case ohai[:platform] - when "aix" - "eL5qfEVznSNss" - else - "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - it "ensures the password is set to the desired value" do - password_should_be_set - expect(etc_shadow).to include(expected_shadow) - end - end - - context "and the user is changed from not-system to system" do - let(:existing_system) { false } - let(:system) { true } - - let(:expect_updated?) { false } - - it "does not modify the user at all" do - end - end - - context "and the user is changed from system to not-system" do - let(:existing_system) { true } - let(:system) { false } - - let(:expect_updated?) { false } - - it "does not modify the user at all" do - end - end - - end # when the user already exists - end # action :create - - shared_context "user exists for lock/unlock" do - let(:user_locked_context?) { false } - - def shadow_entry - etc_shadow.lines.find { |l| l.include?(username) } - end - - def shadow_password - shadow_entry.split(":")[1] - end - - def aix_user_lock_status - lock_info = shell_out!("lsuser -a account_locked #{username}") - /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1] - end - - def user_account_should_be_locked - case ohai[:platform] - when "aix" - expect(aix_user_lock_status).to eq("true") - else - expect(shadow_password).to include("!") - end - end - - def user_account_should_be_unlocked - case ohai[:platform] - when "aix" - expect(aix_user_lock_status).to eq("false") - else - expect(shadow_password).not_to include("!") - end - end - - def lock_user_account - case ohai[:platform] - when "aix" - shell_out!("chuser account_locked=true #{username}") - else - shell_out!("usermod -L #{username}") - end - end - - before do - # create user and setup locked/unlocked state - user_resource.dup.run_action(:create) - - if user_locked_context? - lock_user_account - user_account_should_be_locked - elsif password - user_account_should_be_unlocked - end - end - end - - describe "action :lock" do - context "when the user does not exist" do - it "raises a sensible error" do - expect { user_resource.run_action(:lock) }.to raise_error(Chef::Exceptions::User) - end - end - - context "when the user exists" do - - include_context "user exists for lock/unlock" - - before do - user_resource.run_action(:lock) - end - - context "and the user is not locked" do - # user will be locked if it has no password - let(:password) do - case ohai[:platform] - when "aix" - "eL5qfEVznSNss" - else - "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - it "locks the user's password" do - user_account_should_be_locked - end - end - - context "and the user is locked" do - # user will be locked if it has no password - let(:password) do - case ohai[:platform] - when "aix" - "eL5qfEVznSNss" - else - "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - let(:user_locked_context?) { true } - it "does not update the user" do - expect(user_resource).not_to be_updated_by_last_action - end - end - end - end # action :lock - - describe "action :unlock" do - context "when the user does not exist" do - it "raises a sensible error" do - expect { user_resource.run_action(:unlock) }.to raise_error(Chef::Exceptions::User) - end - end - - context "when the user exists" do - - include_context "user exists for lock/unlock" - - before do - begin - user_resource.run_action(:unlock) - @error = nil - rescue Exception => e - @error = e - end - end - - context "and has no password" do - - # TODO: platform_family should be setup in spec_helper w/ tags - if %w{suse opensuse}.include?(OHAI_SYSTEM["platform_family"]) - # suse gets this right: - it "errors out trying to unlock the user" do - expect(@error).to be_a(Mixlib::ShellOut::ShellCommandFailed) - expect(@error.message).to include("Cannot unlock the password") - end - else - - # borked on all other platforms: - it "is marked as updated but doesn't modify the user (XXX)" do - # This should be an error instead; note that usermod still exits 0 - # (which is probably why this case silently fails): - # - # DEBUG: ---- Begin output of usermod -U chef-functional-test ---- - # DEBUG: STDOUT: - # DEBUG: STDERR: usermod: unlocking the user's password would result in a passwordless account. - # You should set a password with usermod -p to unlock this user's password. - # DEBUG: ---- End output of usermod -U chef-functional-test ---- - # DEBUG: Ran usermod -U chef-functional-test returned 0 - expect(@error).to be_nil - if ohai[:platform] == "aix" - expect(pw_entry.passwd).to eq("*") - user_account_should_be_unlocked - else - expect(pw_entry.passwd).to eq("x") - expect(shadow_password).to include("!") - end - end - end - end - - context "and has a password" do - let(:password) do - case ohai[:platform] - when "aix" - "eL5qfEVznSNss" - else - "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" - end - end - - context "and the user is not locked" do - it "does not update the user" do - expect(user_resource).not_to be_updated_by_last_action - end - end - - context "and the user is locked" do - let(:user_locked_context?) { true } - - it "unlocks the user's password" do - user_account_should_be_unlocked - end - end - end - end - end # action :unlock - -end diff --git a/spec/functional/resource/user/windows_spec.rb b/spec/functional/resource/user/windows_spec.rb index f61a51c636..72db110266 100644 --- a/spec/functional/resource/user/windows_spec.rb +++ b/spec/functional/resource/user/windows_spec.rb @@ -1,5 +1,7 @@ # Author:: Jay Mundrawala (<jdm@chef.io>) -# Copyright:: Copyright 2015-2016, Chef Software +# Author:: Stuart Preston (<stuart@chef.io>) +# +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +24,7 @@ describe Chef::Provider::User::Windows, :windows_only do include Chef::Mixin::ShellOut let(:username) { "ChefFunctionalTest" } - let(:password) { SecureRandom.uuid } + let(:password) { "DummyP2ssw0rd!" } let(:node) do n = Chef::Node.new @@ -31,9 +33,10 @@ describe Chef::Provider::User::Windows, :windows_only do end let(:events) { Chef::EventDispatch::Dispatcher.new } + let(:logger) { double("Mixlib::Log::Child").as_null_object } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) do - Chef::Resource::User.new(username, run_context).tap do |r| + Chef::Resource::User::WindowsUser.new(username, run_context).tap do |r| r.provider(Chef::Provider::User::Windows) r.password(password) end @@ -43,33 +46,142 @@ describe Chef::Provider::User::Windows, :windows_only do shell_out("net user #{u} /delete") end - before do + def backup_secedit_policy + backup_command = "secedit /export /cfg #{ENV["TEMP"]}\\secedit_restore.inf /areas SECURITYPOLICY" + shell_out(backup_command) + end + + def restore_secedit_policy + security_database = "C:\\windows\\security\\database\\seceditnew.sdb" + restore_command = "secedit /configure /db #{security_database} /cfg #{ENV["TEMP"]}\\secedit_restore.inf /areas SECURITYPOLICY" + shell_out(restore_command) + end + + def set_windows_minimum_password_length(minimum_password_length = 0) + require "tempfile" + temp_security_database = "C:\\windows\\security\\database\\seceditnew.sdb" + temp_security_template = Tempfile.new(["chefpolicy", ".inf"]) + file_content = <<~EOF + [Unicode] + Unicode=yes + [System Access] + MinimumPasswordLength = #{minimum_password_length} + PasswordComplexity = 0 + [Version] + signature="$CHICAGO$" + Revision=1 + EOF + windows_template_path = temp_security_template.path.gsub("/") { "\\" } + security_command = "secedit /configure /db #{temp_security_database} /cfg #{windows_template_path} /areas SECURITYPOLICY" + temp_security_template.write(file_content) + temp_security_template.close + shell_out(security_command) + end + + before(:all) do + backup_secedit_policy + end + + before(:each) do delete_user(username) + allow(run_context).to receive(:logger).and_return(logger) + end + + after(:all) do + restore_secedit_policy end describe "action :create" do - it "creates a user when a username and password are given" do - new_resource.run_action(:create) - expect(new_resource).to be_updated_by_last_action - expect(shell_out("net user #{username}").exitstatus).to eq(0) - end + context "on a Windows system with a policy that requires non-blank passwords and no complexity requirements" do - it "reports no changes if there are no changes needed" do - new_resource.run_action(:create) - new_resource.run_action(:create) - expect(new_resource).not_to be_updated_by_last_action + before(:all) do + set_windows_minimum_password_length(1) + end + + context "when a username and non-empty password are given" do + it "creates a user" do + new_resource.run_action(:create) + expect(new_resource).to be_updated_by_last_action + expect(shell_out("net user #{username}").exitstatus).to eq(0) + end + + it "is idempotent" do + new_resource.run_action(:create) + new_resource.run_action(:create) + expect(new_resource).not_to be_updated_by_last_action + end + + it "allows changing the password" do + new_resource.run_action(:create) + new_resource.password(SecureRandom.uuid) + new_resource.run_action(:create) + expect(new_resource).to be_updated_by_last_action + end + end + + context "when a username and empty password are given" do + it "does not create the specified user" do + new_resource.password("") + expect { new_resource.run_action(:create) }.to raise_exception(Chef::Exceptions::Win32APIError, /The password does not meet the password policy requirements/) + end + end end - it "allows chaning the password" do - new_resource.run_action(:create) - new_resource.password(SecureRandom.uuid) - new_resource.run_action(:create) - expect(new_resource).to be_updated_by_last_action + context "on a Windows system with a policy that allows blank passwords" do + + before(:all) do + set_windows_minimum_password_length(0) + end + + context "when a username and non-empty password are given" do + it "creates a user" do + new_resource.run_action(:create) + expect(new_resource).to be_updated_by_last_action + expect(shell_out("net user #{username}").exitstatus).to eq(0) + end + + it "is idempotent" do + new_resource.run_action(:create) + new_resource.run_action(:create) + expect(new_resource).not_to be_updated_by_last_action + end + + it "allows changing the password" do + new_resource.run_action(:create) + new_resource.password(SecureRandom.uuid) + new_resource.run_action(:create) + expect(new_resource).to be_updated_by_last_action + end + end + + context "when a username and empty password are given" do + it "creates a user" do + new_resource.password("") + new_resource.run_action(:create) + expect(new_resource).to be_updated_by_last_action + expect(shell_out("net user #{username}").exitstatus).to eq(0) + end + + it "is idempotent" do + new_resource.password("") + new_resource.run_action(:create) + new_resource.run_action(:create) + expect(new_resource).not_to be_updated_by_last_action + end + + it "allows changing the password from empty to a value" do + new_resource.password("") + new_resource.run_action(:create) + new_resource.password(SecureRandom.uuid) + new_resource.run_action(:create) + expect(new_resource).to be_updated_by_last_action + end + end end context "with a gid specified" do it "warns unsupported" do - expect(Chef::Log).to receive(:warn).with(/not implemented/) + expect(logger).to receive(:warn).with(/not implemented/) new_resource.gid("agroup") new_resource.run_action(:create) end diff --git a/spec/functional/resource/windows_certificate_spec.rb b/spec/functional/resource/windows_certificate_spec.rb new file mode 100644 index 0000000000..b5d0484e0c --- /dev/null +++ b/spec/functional/resource/windows_certificate_spec.rb @@ -0,0 +1,316 @@ +# Author: Nimesh Patni (nimesh.patni@msystechnologies.com) +# 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" +require "chef/mixin/powershell_exec" +require "chef/resource/windows_certificate" + +describe Chef::Resource::WindowsCertificate, :windows_only do + include Chef::Mixin::PowershellExec + + def create_store + powershell_exec <<~EOC + New-Item -Path Cert:\\LocalMachine\\#{store} + EOC + end + + def delete_store + powershell_exec <<~EOC + Remove-Item -Path Cert:\\LocalMachine\\#{store} -Recurse + EOC + end + + def certificate_count + powershell_exec(<<~EOC).result.to_i + (Get-ChildItem -Force -Path Cert:\\LocalMachine\\#{store} | measure).Count + EOC + end + + let(:password) { "P@ssw0rd!" } + let(:store) { "Chef-Functional-Test" } + let(:cer_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.cer") } + let(:base64_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "base64_test.cer") } + let(:pem_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.pem") } + let(:p7b_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.p7b") } + let(:pfx_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.pfx") } + let(:tests_thumbprint) { "e45a4a7ff731e143cf20b8bfb9c7c4edd5238bb3" } + let(:other_cer_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "othertest.cer") } + let(:others_thumbprint) { "6eae1deefaf59daf1a97c9ceeff39c98b3da38cb" } + let(:p7b_thumbprint) { "f867e25b928061318ed2c36ca517681774b06260" } + let(:p7b_nested_thumbprint) { "dc395eae6be5b69951b8b6e1090cfc33df30d2cd" } + + let(:resource) do + run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + Chef::Resource::WindowsCertificate.new("ChefFunctionalTest", run_context).tap do |r| + r.store_name = store + end + end + + before do + # Bypass validation of the store name so we can use a fake test store. + allow_any_instance_of(Chef::Mixin::ParamsValidate) + .to receive(:_pv_equal_to) + .with({ store_name: store }, :store_name, anything) + .and_return(true) + + create_store + end + + after { delete_store } + + describe "action: create" do + it "starts with no certificates" do + expect(certificate_count).to eq(0) + end + + it "can add a certificate idempotently" do + resource.source = cer_path + resource.run_action(:create) + + expect(certificate_count).to eq(1) + expect(resource).to be_updated_by_last_action + + # Adding the cert again should have no effect + resource.run_action(:create) + expect(certificate_count).to eq(1) + expect(resource).not_to be_updated_by_last_action + + # Adding the cert again with a different format should have no effect + resource.source = pem_path + resource.run_action(:create) + expect(certificate_count).to eq(1) + expect(resource).not_to be_updated_by_last_action + + # Adding another cert should work correctly + resource.source = other_cer_path + resource.run_action(:create) + + expect(certificate_count).to eq(2) + expect(resource).to be_updated_by_last_action + end + + it "can add a base64 encoded certificate idempotently" do + resource.source = base64_path + resource.run_action(:create) + + expect(certificate_count).to eq(1) + + resource.run_action(:create) + expect(certificate_count).to eq(1) + expect(resource).not_to be_updated_by_last_action + end + + it "can add a PEM certificate idempotently" do + resource.source = pem_path + resource.run_action(:create) + + expect(certificate_count).to eq(1) + + resource.run_action(:create) + + expect(certificate_count).to eq(1) + expect(resource).not_to be_updated_by_last_action + end + + it "can add a P7B certificate idempotently" do + resource.source = p7b_path + resource.run_action(:create) + + # A P7B cert includes nested certs + expect(certificate_count).to eq(3) + + resource.run_action(:create) + + expect(resource).not_to be_updated_by_last_action + expect(certificate_count).to eq(3) + end + + it "can add a PFX certificate idempotently with a valid password" do + resource.source = pfx_path + resource.pfx_password = password + resource.run_action(:create) + + expect(certificate_count).to eq(1) + + resource.run_action(:create) + expect(certificate_count).to eq(1) + expect(resource).not_to be_updated_by_last_action + end + + it "raises an error when adding a PFX certificate with an invalid password" do + resource.source = pfx_path + resource.pfx_password = "Invalid password" + + expect { resource.run_action(:create) }.to raise_error(OpenSSL::PKCS12::PKCS12Error) + end + end + + describe "action: verify" do + it "fails with no certificates in the store" do + expect(Chef::Log).to receive(:info).with("Certificate not found") + + resource.source = tests_thumbprint + resource.run_action(:verify) + + expect(resource).not_to be_updated_by_last_action + end + + context "with a certificate in the store" do + before do + resource.source = cer_path + resource.run_action(:create) + end + + it "succeeds with a valid thumbprint" do + expect(Chef::Log).to receive(:info).with("Certificate is valid") + + resource.source = tests_thumbprint + resource.run_action(:verify) + + expect(resource).not_to be_updated_by_last_action + end + + it "fails with an invalid thumbprint" do + expect(Chef::Log).to receive(:info).with("Certificate not found") + + resource.source = others_thumbprint + resource.run_action(:verify) + + expect(resource).not_to be_updated_by_last_action + end + end + + context "with a nested certificate in the store" do + before do + resource.source = p7b_path + resource.run_action(:create) + end + + it "succeeds with the main certificate's thumbprint" do + expect(Chef::Log).to receive(:info).with("Certificate is valid") + + resource.source = p7b_thumbprint + resource.run_action(:verify) + + expect(resource).not_to be_updated_by_last_action + end + + it "succeeds with the nested certificate's thumbprint" do + expect(Chef::Log).to receive(:info).with("Certificate is valid") + + resource.source = p7b_nested_thumbprint + resource.run_action(:verify) + + expect(resource).not_to be_updated_by_last_action + end + + it "fails with an invalid thumbprint" do + expect(Chef::Log).to receive(:info).with("Certificate not found") + + resource.source = others_thumbprint + resource.run_action(:verify) + + expect(resource).not_to be_updated_by_last_action + end + end + end + + describe "action: fetch" do + it "does nothing with no certificates in the store" do + expect(Chef::Log).not_to receive(:info) + + resource.source = tests_thumbprint + resource.run_action(:fetch) + + expect(resource).not_to be_updated_by_last_action + end + + context "with a certificate in the store" do + before do + resource.source = cer_path + resource.run_action(:create) + end + + it "succeeds with a valid thumbprint" do + resource.source = tests_thumbprint + + Dir.mktmpdir do |dir| + path = File.join(dir, "test.pem") + expect(Chef::Log).to receive(:info).with("Certificate export in #{path}") + + resource.cert_path = path + resource.run_action(:fetch) + + expect(File.exist?(path)).to be_truthy + end + + expect(resource).not_to be_updated_by_last_action + end + + it "fails with an invalid thumbprint" do + expect(Chef::Log).not_to receive(:info) + + resource.source = others_thumbprint + + Dir.mktmpdir do |dir| + path = File.join(dir, "test.pem") + + resource.cert_path = path + resource.run_action(:fetch) + + expect(File.exist?(path)).to be_falsy + end + + expect(resource).not_to be_updated_by_last_action + end + end + end + + describe "action: delete" do + it "does nothing when attempting to delete a certificate that doesn't exist" do + expect(Chef::Log).to receive(:debug).with("Certificate not found") + + resource.source = tests_thumbprint + resource.run_action(:delete) + end + + it "deletes an existing certificate while leaving other certificates alone" do + # Add two certs + resource.source = cer_path + resource.run_action(:create) + + resource.source = other_cer_path + resource.run_action(:create) + + # Delete the first cert added + resource.source = tests_thumbprint + resource.run_action(:delete) + + expect(certificate_count).to eq(1) + expect(resource).to be_updated_by_last_action + + resource.run_action(:delete) + expect(certificate_count).to eq(1) + expect(resource).not_to be_updated_by_last_action + + # Verify second cert still exists + expect(Chef::Log).to receive(:info).with("Certificate is valid") + resource.source = others_thumbprint + resource.run_action(:verify) + end + end +end diff --git a/spec/functional/resource/windows_env_spec.rb b/spec/functional/resource/windows_env_spec.rb new file mode 100644 index 0000000000..bbcbf393e2 --- /dev/null +++ b/spec/functional/resource/windows_env_spec.rb @@ -0,0 +1,285 @@ +# +# Author:: Adam Edwards (<adamed@chef.io>) +# 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::Resource::WindowsEnv, :windows_only do + context "when running on Windows" do + let(:chef_env_test_lower_case) { "chefenvtest" } + let(:chef_env_test_mixed_case) { "chefENVtest" } + let(:chef_env_with_delim) { "chef_env_with_delim" } + let(:chef_env_delim) { ";" } + let(:chef_env_test_delim) { "#{value1};#{value2}" } + let(:env_dne_key) { "env_dne_key" } + let(:env_value1) { "value1" } + let(:env_value2) { "value2" } + let(:delim_value) { "#{env_value1};#{env_value2}" } + let(:env_user) { ENV["USERNAME"].upcase } + let(:default_env_user) { "<SYSTEM>" } + + let(:env_obj) do + wmi = WmiLite::Wmi.new + environment_variables = wmi.query("select * from Win32_Environment where name = '#{test_resource.key_name}'") + if environment_variables && environment_variables.length > 0 + environment_variables.each do |env| + env_obj = env.wmi_ole_object + return env_obj if env_obj.username.split('\\').last.casecmp(test_resource.user) == 0 + end + end + nil + end + + let(:env_value_expandable) { "%SystemRoot%" } + let(:test_run_context) do + node = Chef::Node.new + node.default["os"] = "windows" + node.default["platform"] = "windows" + node.default["platform_version"] = "6.1" + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + end + let(:test_resource) do + Chef::Resource::WindowsEnv.new("unknown", test_run_context) + end + + before(:each) do + resource_lower = Chef::Resource::WindowsEnv.new(chef_env_test_lower_case, test_run_context) + resource_lower.run_action(:delete) + resource_lower = Chef::Resource::WindowsEnv.new(chef_env_test_lower_case, test_run_context) + resource_lower.user(env_user) + resource_lower.run_action(:delete) + resource_mixed = Chef::Resource::WindowsEnv.new(chef_env_test_mixed_case, test_run_context) + resource_mixed.run_action(:delete) + resource_mixed = Chef::Resource::WindowsEnv.new(chef_env_test_mixed_case, test_run_context) + resource_lower.user(env_user) + resource_mixed.run_action(:delete) + end + + context "when the create action is invoked" do + it "should create an environment variable for action create" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + end + + it "should create an environment variable with default user System for action create" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + expect(env_obj.username.upcase).to eq(default_env_user) + end + + it "should create an environment variable with user for action create" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.user(env_user) + test_resource.run_action(:create) + expect(env_obj.username.split('\\').last.upcase).to eq(env_user) + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + end + it "should modify an existing variable's value to a new value" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value2) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + + it "should not modify an existing variable's value to a new value if the users are different" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value2) + test_resource.user(env_user) + test_resource.run_action(:create) + test_resource.key_name(chef_env_test_lower_case) + test_resource.user(default_env_user) + expect(env_obj.variablevalue).to eq(env_value1) + end + + it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.key_name(chef_env_test_mixed_case) + test_resource.value(env_value2) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + end + + it "should not expand environment variables if the variable is not PATH" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value_expandable) + test_resource.run_action(:create) + expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) + end + end + + context "when the modify action is invoked" do + it "should raise an exception for modify if the variable doesn't exist" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + expect { test_resource.run_action(:modify) }.to raise_error(Chef::Exceptions::WindowsEnv) + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + end + + it "should modify an existing variable's value to a new value" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value2) + test_resource.run_action(:modify) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + + # This example covers Chef Issue #1754 + it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.key_name(chef_env_test_mixed_case) + test_resource.value(env_value2) + test_resource.run_action(:modify) + expect(ENV[chef_env_test_lower_case]).to eq(env_value2) + end + + it "should not expand environment variables if the variable is not PATH" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.value(env_value_expandable) + test_resource.run_action(:modify) + expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) + end + end + + context "when using PATH" do + let(:random_name) { Time.now.to_i } + let(:env_val) { "#{env_value_expandable}_#{random_name}" } + let!(:path_before) { test_resource.provider_for_action(test_resource.action).env_value("PATH") || "" } + let!(:env_path_before) { ENV["PATH"] } + + it "should expand PATH" do + expect(path_before).not_to include(env_val) + test_resource.key_name("PATH") + test_resource.value("#{path_before};#{env_val}") + test_resource.run_action(:create) + expect(ENV["PATH"]).not_to include(env_val) + expect(ENV["PATH"]).to include((random_name).to_s) + end + + after(:each) do + # cleanup so we don't flood the path + test_resource.key_name("PATH") + test_resource.value(path_before) + test_resource.run_action(:create) + ENV["PATH"] = env_path_before + end + end + + end + + context "when the delete action is invoked" do + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.run_action(:create) + end + + it "should delete a System environment variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.run_action(:delete) + expect(ENV[chef_env_test_lower_case]).to eq(nil) + end + + it "should not delete an System environment variable if user are passed" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.user(env_user) + test_resource.run_action(:delete) + test_resource.user(default_env_user) + expect(env_obj).not_to be_nil + end + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + test_resource.user(env_user) + test_resource.run_action(:create) + end + + it "should delete a user environment variable" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.run_action(:delete) + expect(env_obj).to eq(nil) + end + + it "should not delete an user environment variable if user is not passed" do + expect(ENV[chef_env_test_lower_case]).to eq(env_value1) + test_resource.user(default_env_user) + test_resource.run_action(:delete) + test_resource.user(env_user) + expect(env_obj).not_to be_nil + end + end + + context "when env variable exist with same name" do + before(:each) do + test_resource.key_name(chef_env_with_delim) + test_resource.delim(chef_env_delim) + test_resource.value(delim_value) + test_resource.run_action(:create) + end + + it "should not delete variable when a delim present" do + expect(ENV[chef_env_with_delim]).to eq(delim_value) + test_resource.value(env_value1) + test_resource.run_action(:delete) + expect(ENV[chef_env_with_delim]).to eq(env_value2) + end + end + + it "should not raise an exception when a non-existent environment variable is deleted" do + expect(ENV[chef_env_test_lower_case]).to eq(nil) + test_resource.key_name(chef_env_test_lower_case) + test_resource.value(env_value1) + expect { test_resource.run_action(:delete) }.not_to raise_error + expect(ENV[chef_env_test_lower_case]).to eq(nil) + end + + it "should delete a value from the current process even if it is not in the registry" do + expect(ENV[env_dne_key]).to eq(nil) + ENV[env_dne_key] = env_value1 + test_resource.key_name(env_dne_key) + test_resource.run_action(:delete) + expect(ENV[env_dne_key]).to eq(nil) + end + + end + end +end diff --git a/spec/functional/resource/windows_firewall_rule_spec.rb b/spec/functional/resource/windows_firewall_rule_spec.rb new file mode 100644 index 0000000000..86220c1b71 --- /dev/null +++ b/spec/functional/resource/windows_firewall_rule_spec.rb @@ -0,0 +1,93 @@ +# +# Author:: Matt Wrock (<matt@mattwrock.com>) +# 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" +require "chef/mixin/powershell_exec" + +describe Chef::Resource::WindowsFirewallRule, :windows_only do + include Chef::Mixin::PowershellExec + + let(:rule_name) { "fake_rule" } + let(:remote_port) { "5555" } + let(:enabled) { false } + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + + subject do + new_resource = Chef::Resource::WindowsFirewallRule.new("test firewall rule", run_context) + new_resource.rule_name rule_name + new_resource.remote_port remote_port + new_resource.enabled enabled + new_resource + end + + let(:provider) do + provider = subject.provider_for_action(subject.action) + provider + end + + context "create a new rule" do + after { delete_rule } + + it "creates the rule" do + subject.run_action(:create) + expect(get_installed_rule_name).to eq(rule_name) + expect(get_installed_rule_remote_port).to eq(remote_port) + end + + it "does not create rule if it already exists" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "updates the rule if it changed" do + subject.run_action(:create) + subject.remote_port = "7777" + subject.run_action(:create) + expect(get_installed_rule_remote_port).to eq("7777") + end + end + + context "delete a rule" do + it "deletes an existing rule" do + subject.run_action(:create) + subject.run_action(:delete) + expect(get_installed_rule_name).to be_empty + end + + it "does not delete rule if it does not exist" do + subject.run_action(:delete) + expect(subject).not_to be_updated_by_last_action + end + end + + def get_installed_rule_name + powershell_exec!("(Get-NetFirewallRule -Name #{rule_name} -ErrorAction SilentlyContinue).Name").result + end + + def get_installed_rule_remote_port + powershell_exec!("((Get-NetFirewallRule -Name #{rule_name} -ErrorAction SilentlyContinue) | Get-NetFirewallPortFilter).RemotePort").result + end + + def delete_rule + rule_to_remove = Chef::Resource::WindowsFirewallRule.new(rule_name, run_context) + rule_to_remove.run_action(:delete) + end +end diff --git a/spec/functional/resource/windows_font_spec.rb b/spec/functional/resource/windows_font_spec.rb new file mode 100644 index 0000000000..e46e4aca17 --- /dev/null +++ b/spec/functional/resource/windows_font_spec.rb @@ -0,0 +1,49 @@ +# +# Author:: Dheeraj Singh Dubey (<ddubey@chef.io>) +# 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::Resource::WindowsFont, :windows_only do + let(:resource_name) { "Playmaker.ttf" } + let(:resource_source) { "https://www.wfonts.com/download/data/2020/05/06/playmaker/Playmaker.ttf" } + + let(:run_context) do + node = Chef::Node.new + node.default[:platform] = ohai[:platform] + node.default[:platform_version] = ohai[:platform_version] + node.default[:os] = ohai[:os] + events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, events) + end + + subject do + resource = Chef::Resource::WindowsFont.new(resource_name, run_context) + resource.source resource_source + resource + end + + it "installs font on first install" do + subject.run_action(:install) + expect(subject).to be_updated_by_last_action + end + + it "does not install font when already installed" do + subject.run_action(:install) + expect(subject).not_to be_updated_by_last_action + end +end diff --git a/spec/functional/resource/windows_package_spec.rb b/spec/functional/resource/windows_package_spec.rb index bc508dc526..5fed41e9ae 100644 --- a/spec/functional/resource/windows_package_spec.rb +++ b/spec/functional/resource/windows_package_spec.rb @@ -1,6 +1,6 @@ # # Author:: Matt Wrock (<matt@mattwrock.com>) -# Copyright:: Copyright 2015-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ # require "spec_helper" -require "functional/resource/base" describe Chef::Resource::WindowsPackage, :windows_only, :volatile do let(:pkg_name) { nil } @@ -26,6 +25,10 @@ describe Chef::Resource::WindowsPackage, :windows_only, :volatile do let(:pkg_version) { nil } let(:pkg_type) { nil } let(:pkg_options) { nil } + let(:remote_file_attributes) { nil } + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end subject do new_resource = Chef::Resource::WindowsPackage.new(pkg_name, run_context) @@ -34,13 +37,14 @@ describe Chef::Resource::WindowsPackage, :windows_only, :volatile do new_resource.installer_type pkg_type new_resource.options pkg_options new_resource.checksum pkg_checksum + new_resource.remote_file_attributes new_resource end describe "install package" do let(:pkg_name) { "Microsoft Visual C++ 2005 Redistributable" } - let(:pkg_checksum) { "d6832398e3bc9156a660745f427dc1c2392ce4e9a872e04f41f62d0c6bae07a8" } - let(:pkg_path) { "https://download.microsoft.com/download/6/B/B/6BB661D6-A8AE-4819-B79F-236472F6070C/vcredist_x86.exe" } + let(:pkg_checksum) { "4ee4da0fe62d5fa1b5e80c6e6d88a4a2f8b3b140c35da51053d0d7b72a381d29" } + let(:pkg_path) { "https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE" } let(:pkg_checksum) { nil } let(:pkg_type) { :custom } let(:pkg_options) { "/Q" } @@ -56,9 +60,9 @@ describe Chef::Resource::WindowsPackage, :windows_only, :volatile do end context "installing additional version" do - let(:pkg_path) { "https://download.microsoft.com/download/e/1/c/e1c773de-73ba-494a-a5ba-f24906ecf088/vcredist_x86.exe" } - let(:pkg_checksum) { "eb00f891919d4f894ab725b158459db8834470c382dc60cd3c3ee2c6de6da92c" } - let(:pkg_version) { "8.0.56336" } + let(:pkg_path) { "https://download.microsoft.com/download/6/B/B/6BB661D6-A8AE-4819-B79F-236472F6070C/vcredist_x86.exe" } + let(:pkg_checksum) { "d6832398e3bc9156a660745f427dc1c2392ce4e9a872e04f41f62d0c6bae07a8" } + let(:pkg_version) { "8.0.59193" } it "installs older version" do subject.run_action(:install) @@ -70,14 +74,14 @@ describe Chef::Resource::WindowsPackage, :windows_only, :volatile do subject { Chef::Resource::WindowsPackage.new(pkg_name, run_context) } context "multiple versions and a version given to remove" do - before { subject.version("8.0.56336") } + before { subject.version("8.0.59193") } it "removes specified version" do subject.run_action(:remove) expect(subject).to be_updated_by_last_action prov = subject.provider_for_action(:remove) prov.load_current_resource - expect(prov.current_version_array).to eq([["8.0.59193"]]) + expect(prov.current_version_array).to eq([["8.0.61001"]]) end end @@ -102,8 +106,8 @@ describe Chef::Resource::WindowsPackage, :windows_only, :volatile do install1.run_action(:install) install2 = Chef::Resource::WindowsPackage.new(pkg_name, run_context) - install2.source "https://download.microsoft.com/download/e/1/c/e1c773de-73ba-494a-a5ba-f24906ecf088/vcredist_x86.exe" - install2.version "8.0.56336" + install2.source "https://download.microsoft.com/download/6/B/B/6BB661D6-A8AE-4819-B79F-236472F6070C/vcredist_x86.exe" + install2.version "8.0.59193" install2.installer_type pkg_type install2.options pkg_options install2.run_action(:install) @@ -136,7 +140,7 @@ describe Chef::Resource::WindowsPackage, :windows_only, :volatile do context "inno" do let(:pkg_name) { "Mercurial 3.6.1 (64-bit)" } - let(:pkg_path) { "http://mercurial.selenic.com/release/windows/Mercurial-3.6.1-x64.exe" } + let(:pkg_path) { "https://www.mercurial-scm.org/release/windows/Mercurial-3.6.1-x64.exe" } let(:pkg_checksum) { "febd29578cb6736163d232708b834a2ddd119aa40abc536b2c313fc5e1b5831d" } it "finds the correct installer type" do @@ -165,4 +169,25 @@ describe Chef::Resource::WindowsPackage, :windows_only, :volatile do expect(subject).to be_updated_by_last_action end end + + describe "install package with remote_file_attributes" do + let(:pkg_name) { "7zip" } + let(:pkg_path) { "http://www.7-zip.org/a/7z938-x64.msi" } + let(:remote_file_attributes) { + { + path: ::File.join(Chef::Config[:file_cache_path], "7zip.msi"), + checksum: "7c8e873991c82ad9cfcdbdf45254ea6101e9a645e12977dcd518979e50fdedf3", + } + } + + it "installs the package" do + subject.run_action(:install) + expect(subject).to be_updated_by_last_action + end + + it "uninstalls the package" do + subject.run_action(:remove) + expect(subject).to be_updated_by_last_action + end + end end diff --git a/spec/functional/resource/windows_path_spec.rb b/spec/functional/resource/windows_path_spec.rb new file mode 100644 index 0000000000..b2a3e5f5a4 --- /dev/null +++ b/spec/functional/resource/windows_path_spec.rb @@ -0,0 +1,68 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# 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::Resource::WindowsPath, :windows_only do + let(:path) { "test_path" } + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + + before(:all) do + @old_path = ENV["PATH"].dup + end + + after(:all) do + ENV["PATH"] = @old_path + end + + subject do + new_resource = Chef::Resource::WindowsPath.new(path, run_context) + new_resource + end + + describe "adding path" do + after { remove_path } + + it "appends the user given path in the Environment variable Path" do + subject.run_action(:add) + expect(ENV["PATH"]).to include(path) + end + end + + describe "removing path" do + before { add_path } + + it "removes the user given path from the Environment variable Path" do + subject.run_action(:remove) + expect(ENV["PATH"]).not_to include(path) + end + end + + def remove_path + new_resource = Chef::Resource::WindowsPath.new(path, run_context) + new_resource.run_action(:remove) + end + + def add_path + new_resource = Chef::Resource::WindowsPath.new(path, run_context) + new_resource.run_action(:add) + end +end diff --git a/spec/functional/resource/windows_security_policy_spec.rb b/spec/functional/resource/windows_security_policy_spec.rb new file mode 100644 index 0000000000..76764e01b0 --- /dev/null +++ b/spec/functional/resource/windows_security_policy_spec.rb @@ -0,0 +1,86 @@ +# +# Author:: Ashwini Nehate (<anehate@chef.io>) +# 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::Resource::WindowsSecurityPolicy, :windows_only do + let(:secoption) { "MaximumPasswordAge" } + let(:secvalue) { "30" } + let(:windows_test_run_context) do + node = Chef::Node.new + node.consume_external_attrs(OHAI_SYSTEM.data, {}) # node[:languages][:powershell][:version] + node.automatic["os"] = "windows" + node.automatic["platform"] = "windows" + node.automatic["platform_version"] = "6.1" + node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + end + + subject do + new_resource = Chef::Resource::WindowsSecurityPolicy.new(secoption, windows_test_run_context) + new_resource.secoption = secoption + new_resource.secvalue = secvalue + new_resource + end + + describe "Set MaximumPasswordAge Policy" do + after { + subject.secvalue("60") + subject.run_action(:set) + } + + it "should set MaximumPasswordAge to 30" do + subject.secvalue("30") + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + + it "should be idempotent" do + subject.secvalue("30") + subject.run_action(:set) + guardscript_and_script_time = subject.elapsed_time + subject.run_action(:set) + only_guardscript_time = subject.elapsed_time + expect(only_guardscript_time).to be < guardscript_and_script_time + end + end + + describe "secoption and id: " do + it "accepts 'MinimumPasswordAge', 'MinimumPasswordAge', 'MaximumPasswordAge', 'MinimumPasswordLength', 'PasswordComplexity', 'PasswordHistorySize', 'LockoutBadCount', 'RequireLogonToChangePassword', 'ForceLogoffWhenHourExpire', 'NewAdministratorName', 'NewGuestName', 'ClearTextPassword', 'LSAAnonymousNameLookup', 'EnableAdminAccount', 'EnableGuestAccount' " do + expect { subject.secoption("MinimumPasswordAge") }.not_to raise_error + expect { subject.secoption("MaximumPasswordAge") }.not_to raise_error + expect { subject.secoption("MinimumPasswordLength") }.not_to raise_error + expect { subject.secoption("PasswordComplexity") }.not_to raise_error + expect { subject.secoption("PasswordHistorySize") }.not_to raise_error + expect { subject.secoption("LockoutBadCount") }.not_to raise_error + expect { subject.secoption("RequireLogonToChangePassword") }.not_to raise_error + expect { subject.secoption("ForceLogoffWhenHourExpire") }.not_to raise_error + expect { subject.secoption("NewAdministratorName") }.not_to raise_error + expect { subject.secoption("NewGuestName") }.not_to raise_error + expect { subject.secoption("ClearTextPassword") }.not_to raise_error + expect { subject.secoption("LSAAnonymousNameLookup") }.not_to raise_error + expect { subject.secoption("EnableAdminAccount") }.not_to raise_error + expect { subject.secoption("EnableGuestAccount") }.not_to raise_error + end + + it "rejects any other option" do + expect { subject.secoption "XYZ" }.to raise_error(ArgumentError) + end + end +end diff --git a/spec/functional/resource/windows_service_spec.rb b/spec/functional/resource/windows_service_spec.rb index 531f9e9250..4c0c3acb58 100644 --- a/spec/functional/resource/windows_service_spec.rb +++ b/spec/functional/resource/windows_service_spec.rb @@ -1,6 +1,6 @@ # # Author:: Chris Doherty (<cdoherty@chef.io>) -# Copyright:: Copyright 2014-2016, Chef Software, Inc. +# Copyright:: Copyright (c) Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,13 +18,12 @@ require "spec_helper" -describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only, :appveyor_only, :broken => true do - # Marking as broken. This test is causing appveyor tests to exit with 116. +describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only do include_context "using Win32::Service" let(:username) { "service_spec_user" } - let(:qualified_username) { "#{ENV['COMPUTERNAME']}\\#{username}" } + let(:qualified_username) { "#{ENV["COMPUTERNAME"]}\\#{username}" } let(:password) { "1a2b3c4X!&narf" } let(:user_resource) do @@ -36,7 +35,7 @@ describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_ end let(:global_service_file_path) do - "#{ENV['WINDIR']}\\temp\\#{File.basename(test_service[:service_file_path])}" + "#{ENV["WINDIR"]}\\temp\\#{File.basename(test_service[:service_file_path])}" end let(:service_params) do @@ -59,10 +58,14 @@ describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_ let(:service_resource) do r = Chef::Resource::WindowsService.new(service_params[:service_name], run_context) - [:run_as_user, :run_as_password].each { |prop| r.send(prop, service_params[prop]) } + %i{run_as_user run_as_password}.each { |prop| r.send(prop, service_params[prop]) } r end + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + before do user_resource.run_action(:create) diff --git a/spec/functional/resource/windows_share_spec.rb b/spec/functional/resource/windows_share_spec.rb new file mode 100644 index 0000000000..6fae7d45d3 --- /dev/null +++ b/spec/functional/resource/windows_share_spec.rb @@ -0,0 +1,103 @@ +# +# Author:: Matt Wrock (<matt@mattwrock.com>) +# 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" +require "chef/mixin/powershell_exec" + +describe Chef::Resource::WindowsShare, :windows_only do + include Chef::Mixin::PowershellExec + + let(:share_name) { "fake_share" } + let(:path) { ENV["temp"] } + let(:concurrent_user_limit) { 7 } + let(:full_users) { ["#{ENV["USERNAME"]}"] } + + let(:run_context) do + node = Chef::Node.new + node.default["hostname"] = ENV["COMPUTERNAME"] + Chef::RunContext.new(node, {}, Chef::EventDispatch::Dispatcher.new) + end + + subject do + new_resource = Chef::Resource::WindowsShare.new("test windows share", run_context) + new_resource.share_name share_name + new_resource.path path + new_resource.concurrent_user_limit concurrent_user_limit + new_resource.full_users full_users + new_resource + end + + let(:provider) do + provider = subject.provider_for_action(subject.action) + provider + end + + context "create a new share" do + after { delete_share } + + it "creates the share" do + subject.run_action(:create) + share = get_installed_share + expect(share["Name"]).to eq(share_name) + expect(share["Path"]).to eq(path) + expect(get_installed_share_access["AccountName"]).to eq("#{ENV["COMPUTERNAME"]}\\#{full_users[0]}") + end + + it "does not create share if it already exists" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "updates the share if it changed" do + subject.run_action(:create) + subject.concurrent_user_limit 8 + subject.full_users ["BUILTIN\\Administrators"] + subject.run_action(:create) + share = get_installed_share + expect(share["ConcurrentUserLimit"]).to eq(8) + expect(get_installed_share_access["AccountName"]).to eq("BUILTIN\\Administrators") + end + + end + + context "delete a share" do + it "deletes an existing share" do + subject.run_action(:create) + subject.run_action(:delete) + expect(get_installed_share).to be_empty + end + + it "does not delete share if it does not exist" do + subject.run_action(:delete) + expect(subject).not_to be_updated_by_last_action + end + end + + def get_installed_share + powershell_exec!("Get-SmbShare -Name #{share_name} -ErrorAction SilentlyContinue").result + end + + def get_installed_share_access + powershell_exec!("Get-SmbShareAccess -Name #{share_name} -ErrorAction SilentlyContinue").result + end + + def delete_share + rule_to_remove = Chef::Resource::WindowsShare.new(share_name, run_context) + rule_to_remove.run_action(:delete) + end +end diff --git a/spec/functional/resource/windows_task_spec.rb b/spec/functional/resource/windows_task_spec.rb new file mode 100644 index 0000000000..3affa625fd --- /dev/null +++ b/spec/functional/resource/windows_task_spec.rb @@ -0,0 +1,1969 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# 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" +require "chef-utils/dist" + +describe Chef::Resource::WindowsTask, :windows_only do + # resource.task.application_name will default to task_name unless resource.command is set + let(:task_name) { "chef-client-functional-test" } + let(:new_resource) { Chef::Resource::WindowsTask.new(task_name, run_context) } + let(:windows_task_provider) do + new_resource.provider_for_action(:create) + end + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + + describe "action :create" do + after { delete_task } + context "when command is with arguments" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + # Make sure MM/DD/YYYY is accepted + + new_resource.start_day "09/20/2017" + new_resource.frequency :hourly + new_resource + end + + context "With Arguments" do + it "creates scheduled task and sets command arguments" do + subject.command "#{ChefUtils::Dist::Infra::CLIENT} -W" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq(ChefUtils::Dist::Infra::CLIENT) + expect(current_resource.task.parameters).to eq("-W") + end + + it "does not converge the resource if it is already converged" do + subject.command "#{ChefUtils::Dist::Infra::CLIENT} -W" + subject.run_action(:create) + subject.command "#{ChefUtils::Dist::Infra::CLIENT} -W" + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates scheduled task and sets command arguments when arguments inclusive single quotes" do + subject.command "#{ChefUtils::Dist::Infra::CLIENT} -W -L 'C:\\chef\\chef-ad-join.log'" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq(ChefUtils::Dist::Infra::CLIENT) + expect(current_resource.task.parameters).to eq("-W -L 'C:\\chef\\chef-ad-join.log'") + end + + it "does not converge the resource if it is already converged" do + subject.command "#{ChefUtils::Dist::Infra::CLIENT} -W -L 'C:\\chef\\chef-ad-join.log'" + subject.run_action(:create) + subject.command "#{ChefUtils::Dist::Infra::CLIENT} -W -L 'C:\\chef\\chef-ad-join.log'" + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates scheduled task and sets command arguments with spaces in command" do + subject.command '"C:\\Program Files\\example\\program.exe" -arg1 --arg2' + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq('"C:\\Program Files\\example\\program.exe"') + expect(current_resource.task.parameters).to eq("-arg1 --arg2") + end + + it "does not converge the resource if it is already converged" do + subject.command '"C:\\Program Files\\example\\program.exe" -arg1 --arg2' + subject.run_action(:create) + subject.command '"C:\\Program Files\\example\\program.exe" -arg1 --arg2' + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates scheduled task and sets command arguments with spaces in arguments" do + subject.command 'powershell.exe -file "C:\\Program Files\\app\\script.ps1"' + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq("powershell.exe") + expect(current_resource.task.parameters).to eq('-file "C:\\Program Files\\app\\script.ps1"') + end + + it "does not converge the resource if it is already converged" do + subject.command 'powershell.exe -file "C:\\Program Files\\app\\script.ps1"' + subject.run_action(:create) + subject.command 'powershell.exe -file "C:\\Program Files\\app\\script.ps1"' + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates scheduled task and sets command arguments" do + subject.command "ping http://www.google.com" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq("ping") + expect(current_resource.task.parameters).to eq("http://www.google.com") + end + + it "does not converge the resource if it is already converged" do + subject.command "ping http://www.google.com" + subject.run_action(:create) + subject.command "ping http://www.google.com" + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "Without Arguments" do + it "creates scheduled task and sets command arguments" do + subject.command ChefUtils::Dist::Infra::CLIENT + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq(ChefUtils::Dist::Infra::CLIENT) + expect(current_resource.task.parameters).to be_empty + end + + it "does not converge the resource if it is already converged" do + subject.command ChefUtils::Dist::Infra::CLIENT + subject.run_action(:create) + subject.command ChefUtils::Dist::Infra::CLIENT + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + it "creates scheduled task and Re-sets command arguments" do + subject.command 'powershell.exe -file "C:\\Program Files\\app\\script.ps1"' + subject.run_action(:create) + current_resource = call_for_load_current_resource + expect(current_resource.task.application_name).to eq("powershell.exe") + expect(current_resource.task.parameters).to eq('-file "C:\\Program Files\\app\\script.ps1"') + + subject.command "powershell.exe" + subject.run_action(:create) + current_resource = call_for_load_current_resource + expect(current_resource.task.application_name).to eq("powershell.exe") + expect(current_resource.task.parameters).to be_empty + expect(subject).to be_updated_by_last_action + end + end + + context "when description is passed" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource.command task_name + # Make sure MM/DD/YYYY is accepted + new_resource.start_day "09/20/2017" + new_resource.frequency :hourly + new_resource + end + + let(:some_description) { "this is test description" } + + it "create the task and sets its description" do + subject.description some_description + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.description).to eq(some_description) + end + + it "does not converge the resource if it is already converged" do + subject.description some_description + subject.run_action(:create) + subject.description some_description + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "updates task with new description if task already exist" do + subject.description some_description + subject.run_action(:create) + subject.description "test description" + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + end + end + + context "when frequency_modifier are not passed" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + # Make sure MM/DD/YYYY is accepted + new_resource.start_day "09/20/2017" + new_resource.frequency :hourly + new_resource + end + + it "creates a scheduled task to run every 1 hr starting on 09/20/2017" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq(task_name) + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:start_year]).to eq("2017") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("20") + expect(trigger_details[:minutes_interval]).to eq(60) + expect(trigger_details[:trigger_type]).to eq(1) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "frequency :minute" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :minute + new_resource.frequency_modifier 15 + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates a scheduled task that runs after every 15 minutes" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:minutes_interval]).to eq(15) + expect(trigger_details[:trigger_type]).to eq(1) + expect(current_resource.task.principals[:run_level]).to eq(1) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "updates a scheduled task when frequency_modifier updated to 20" do + subject.run_action(:create) + current_resource = call_for_load_current_resource + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:minutes_interval]).to eq(15) + subject.frequency_modifier 20 + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + # #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:minutes_interval]).to eq(20) + expect(trigger_details[:trigger_type]).to eq(1) + expect(current_resource.task.principals[:run_level]).to eq(1) + end + end + + context "frequency :hourly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :hourly + new_resource.frequency_modifier 3 + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates a scheduled task that runs after every 3 hrs" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:minutes_interval]).to eq(180) + expect(trigger_details[:trigger_type]).to eq(1) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "updates a scheduled task to run every 5 hrs when frequency modifier updated to 5" do + subject.run_action(:create) + current_resource = call_for_load_current_resource + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:minutes_interval]).to eq(180) + # updating frequency modifier to 5 from 3 + subject.frequency_modifier 5 + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:minutes_interval]).to eq(300) + expect(trigger_details[:trigger_type]).to eq(1) + end + end + + context "frequency :daily" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :daily + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates a scheduled task to run daily" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(2) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days_interval]).to eq(1) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + describe "frequency :monthly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :monthly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + context "with start_day and start_time" do + before do + subject.start_day "02/12/2018" + subject.start_time "05:15" + end + + it "if day property is not set creates a scheduled task to run monthly on first day of the month" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1) + expect(trigger_details[:type][:months]).to eq(4095) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on first, second and third day of the month" do + subject.day "1, 2, 3" + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(7) + expect(trigger_details[:type][:months]).to eq(4095) + end + + it "does not converge the resource if it is already converged" do + subject.day "1, 2, 3" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on 1, 2, 3, 4, 8, 20, 21, 15, 28, 31 day of the month" do + subject.day "1, 2, 3, 4, 8, 20, 21, 15, 28, 31" + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1209548943) # TODO:: windows_task_provider.send(:days_of_month) + expect(trigger_details[:type][:months]).to eq(4095) # windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.day "1, 2, 3, 4, 8, 20, 21, 15, 28, 31" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on Jan, Feb, Apr, Dec on 1st 2nd 3rd 4th 8th and 20th day of these months" do + subject.day "1, 2, 3, 4, 8, 20, 21, 30" + subject.months "Jan, Feb, May, Sep, Dec" + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(538443919) # TODO:windows_task_provider.send(:days_of_month) + expect(trigger_details[:type][:months]).to eq(2323) # windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.day "1, 2, 3, 4, 8, 20, 21, 30" + subject.months "Jan, Feb, May, Sep, Dec" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly by giving day option with frequency_modifier" do + subject.frequency_modifier "First" + subject.day "Mon, Fri, Sun" + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(5) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days_of_week]).to eq(35) + expect(trigger_details[:type][:weeks_of_month]).to eq(1) + expect(trigger_details[:type][:months]).to eq(4095) # windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "First" + subject.day "Mon, Fri, Sun" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "with frequency_modifier" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :monthly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "raises argument error if frequency_modifier is 'first, second' and day is not provided." do + subject.frequency_modifier "first, second" + expect { subject.after_created }.to raise_error("Please select day on which you want to run the task e.g. 'Mon, Tue'. Multiple values must be separated by comma.") + end + + it "raises argument error if months is passed along with frequency_modifier" do + subject.frequency_modifier 3 + subject.months "Jan, Mar" + expect { subject.after_created }.to raise_error("For frequency :monthly either use property months or frequency_modifier to set months.") + end + + it "not raises any Argument error if frequency_modifier set as 'first, second, third' and day is provided" do + subject.frequency_modifier "first, second, third" + subject.day "Mon, Fri" + expect { subject.after_created }.not_to raise_error + end + + it "not raises any Argument error if frequency_modifier 2 " do + subject.frequency_modifier 2 + subject.day "Mon, Sun" + expect { subject.after_created }.not_to raise_error + end + + it "raises argument error if frequency_modifier > 12" do + subject.frequency_modifier 13 + expect { subject.after_created }.to raise_error("frequency_modifier value 13 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + + it "raises argument error if frequency_modifier < 1" do + subject.frequency_modifier 0 + expect { subject.after_created }.to raise_error("frequency_modifier value 0 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + + it "creates scheduled task to run task monthly on Monday and Friday of first, second and third week of month" do + subject.frequency_modifier "first, second, third" + subject.day "Mon, Fri" + expect { subject.after_created }.not_to raise_error + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(5) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:weeks_of_month]).to eq(7) + expect(trigger_details[:type][:days_of_week]).to eq(34) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "first, second, third" + subject.day "Mon, Fri" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates scheduled task to run task monthly on every 6 months when frequency_modifier is 6 and to run on 1st and 2nd day of month" do + subject.frequency_modifier 6 + subject.day "1, 2" + expect { subject.after_created }.not_to raise_error + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(2080) + expect(trigger_details[:type][:days]).to eq(3) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier 6 + subject.day "1, 2" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when day is set as last or lastday for frequency :monthly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :monthly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates scheduled task to run monthly to run last day of the month" do + subject.day "last" + expect { subject.after_created }.not_to raise_error + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days]).to eq(0) + expect(trigger_details[:run_on_last_day_of_month]).to eq(true) + end + + it "does not converge the resource if it is already converged" do + subject.day "last" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "day property set as 'lastday' creates scheduled task to run monthly to run last day of the month" do + subject.day "lastday" + expect { subject.after_created }.not_to raise_error + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days]).to eq(0) + expect(trigger_details[:run_on_last_day_of_month]).to eq(true) + end + + it "does not converge the resource if it is already converged" do + subject.day "lastday" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when frequency_modifier is set as last for frequency :monthly" do + it "creates scheduled task to run monthly on last week of the month" do + subject.frequency_modifier "last" + subject.day "Mon, Fri" + expect { subject.after_created }.not_to raise_error + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(5) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days_of_week]).to eq(34) + expect(trigger_details[:run_on_last_week_of_month]).to eq(true) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "last" + subject.day "Mon, Fri" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when wild card (*) set as months" do + it "creates the scheduled task to run on 1st day of the all months" do + subject.months "*" + expect { subject.after_created }.not_to raise_error + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(trigger_details[:type][:months]).to eq(4095) + expect(trigger_details[:type][:days]).to eq(1) + end + + it "does not converge the resource if it is already converged" do + subject.months "*" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when wild card (*) set as day" do + it "raises argument error" do + subject.day "*" + expect { subject.after_created }.to raise_error("day wild card (*) is only valid with frequency :weekly") + end + end + + context "Pass either start day or start time by passing day compulsory or only pass frequency_modifier" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :monthly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates a scheduled task to run monthly on second day of the month" do + subject.day "2" + subject.start_day "03/07/2018" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(2) + expect(trigger_details[:type][:months]).to eq(4095) + end + + it "does not converge the resource if it is already converged" do + subject.day "2" + subject.start_day "03/07/2018" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on first, second and third day of the month" do + subject.day "1,2,3" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(7) + expect(trigger_details[:type][:months]).to eq(4095) + end + + it "does not converge the resource if it is already converged" do + subject.day "1,2,3" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on each wednesday of the month" do + subject.frequency_modifier "1" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1) + expect(trigger_details[:type][:months]).to eq(4095) # windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "2" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "creates a scheduled task to run monthly on each wednesday of the month" do + subject.frequency_modifier "2" + subject.months = nil + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + # loading current resource + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(4) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:type][:days]).to eq(1) + expect(trigger_details[:type][:months]).to eq(2730) # windows_task_provider.send(:months_of_year) + end + + it "does not converge the resource if it is already converged" do + subject.frequency_modifier "2" + subject.months = nil + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + end + + ## ToDO: Add functional specs to handle frequency monthly with frequency modifier set as 1-12 + context "frequency :once" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :once + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + context "when start_time is not provided" do + it "raises argument error" do + expect { subject.after_created }.to raise_error("`start_time` needs to be provided with `frequency :once`") + end + end + + context "when start_time is provided" do + it "creates the scheduled task to run once at 5pm" do + subject.start_time "17:00" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(1) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect("#{trigger_details[:start_hour]}:#{trigger_details[:start_minute]}" ).to eq(subject.start_time) + end + + it "does not converge the resource if it is already converged" do + subject.start_time "17:00" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + end + + context "frequency :weekly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :weekly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates the scheduled task to run weekly" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(1) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + context "when wild card (*) is set as day" do + it "creates hte scheduled task for all days of week" do + subject.day "*" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(1) + expect(trigger_details[:type][:days_of_week]).to eq(127) + end + + it "does not converge the resource if it is already converged" do + subject.day "*" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when days are provided" do + it "creates the scheduled task to run on particular days" do + subject.day "Mon, Fri" + subject.frequency_modifier 2 + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(2) + expect(trigger_details[:type][:days_of_week]).to eq(34) + end + + it "updates the scheduled task to run on if frequency_modifier is updated" do + subject.day "sun" + subject.frequency_modifier 2 + subject.run_action(:create) + current_resource = call_for_load_current_resource + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:type][:weeks_interval]).to eq(2) + expect(trigger_details[:type][:days_of_week]).to eq(1) + subject.day "Mon, Sun" + subject.frequency_modifier 3 + # call for update + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(3) + expect(trigger_details[:type][:days_of_week]).to eq(3) + end + + it "does not converge the resource if it is already converged" do + subject.day "Mon, Fri" + subject.frequency_modifier 3 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when day property set as last" do + it "raises argument error" do + subject.day "last" + expect { subject.after_created }.to raise_error("day values 1-31 or last is only valid with frequency :monthly") + end + end + + context "when invalid day is passed" do + it "raises error" do + subject.day "abc" + expect { subject.after_created }.to raise_error("day property invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN, *. Multiple values must be separated by a comma.") + end + end + + context "when months are passed" do + it "raises error that months are supported only when frequency=:monthly" do + subject.months "Jan" + expect { subject.after_created }.to raise_error("months property is only valid for tasks that run monthly") + end + end + + context "when start_day is not set" do + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "updates the day if start_day is not provided and user updates day property" do + skip "Unable to run this test case since start_day is current system date which can be different each time so can't verify the dynamic values" + subject.run_action(:create) + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(trigger_details[:type][:days_of_week]).to eq(8) + subject.day "Sat" + subject.run_action(:create) + # #loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(3) + expect(trigger_details[:type][:weeks_interval]).to eq(1) + expect(trigger_details[:type][:days_of_week]).to eq(64) + end + end + end + + context "frequency :onstart" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :onstart + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates the scheduled task to run at system start up" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(8) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + context "when start_day and start_time is set" do + it "creates task to activate on '09/10/2018' at '15:00' when start_day = '09/10/2018' and start_time = '15:00' provided" do + subject.start_day "09/10/2018" + subject.start_time "15:00" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(8) + expect(trigger_details[:start_year]).to eq("2018") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("10") + expect(trigger_details[:start_hour]).to eq("15") + expect(trigger_details[:start_minute]).to eq("00") + end + end + end + + context "frequency :on_logon" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :on_logon + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates the scheduled task to on logon" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(9) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + context "when start_day and start_time is set" do + it "creates task to activate on '09/10/2018' at '15:00' when start_day = '09/10/2018' and start_time = '15:00' provided" do + subject.start_day "09/10/2018" + subject.start_time "15:00" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(9) + expect(trigger_details[:start_year]).to eq("2018") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("10") + expect(trigger_details[:start_hour]).to eq("15") + expect(trigger_details[:start_minute]).to eq("00") + end + end + end + + context "frequency :on_idle" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :on_idle + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + context "when idle_time is not passed" do + it "raises error" do + expect { subject.after_created }.to raise_error("idle_time value should be set for :on_idle frequency.") + end + end + + context "when idle_time is passed" do + it "creates the scheduled task to run when system is idle" do + subject.idle_time 20 + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(6) + expect(current_resource.task.settings[:idle_settings][:idle_duration]).to eq("PT20M") + expect(current_resource.task.settings[:run_only_if_idle]).to eq(true) + end + + it "does not converge the resource if it is already converged" do + subject.idle_time 20 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when start_day and start_time is set" do + it "creates task to activate on '09/10/2018' at '15:00' when start_day = '09/10/2018' and start_time = '15:00' provided" do + subject.idle_time 20 + subject.start_day "09/10/2018" + subject.start_time "15:00" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(trigger_details[:trigger_type]).to eq(6) + expect(trigger_details[:start_year]).to eq("2018") + expect(trigger_details[:start_month]).to eq("09") + expect(trigger_details[:start_day]).to eq("10") + expect(trigger_details[:start_hour]).to eq("15") + expect(trigger_details[:start_minute]).to eq("00") + end + end + end + + context "when random_delay is passed" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "sets the random_delay for frequency :minute" do + subject.frequency :minute + subject.random_delay "20" + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + trigger_details = current_resource.task.trigger(0) + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(trigger_details[:trigger_type]).to eq(1) + expect(trigger_details[:random_minutes_interval]).to eq(20) + end + + it "does not converge the resource if it is already converged" do + subject.frequency :minute + subject.random_delay "20" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "raises error if invalid random_delay is passed" do + subject.frequency :minute + subject.random_delay "abc" + expect { subject.after_created }.to raise_error("Invalid value passed for `random_delay`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60').") + end + + it "raises error if random_delay is passed with frequency on_idle" do + subject.frequency :on_idle + subject.random_delay "20" + expect { subject.after_created }.to raise_error("`random_delay` property is supported only for frequency :once, :minute, :hourly, :daily, :weekly and :monthly") + end + end + + context "when battery options are passed" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "sets the default if options are not provided" do + subject.frequency :minute + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.stop_if_going_on_batteries).to eql(false) + expect(current_resource.disallow_start_if_on_batteries).to eql(false) + end + + it "sets disallow_start_if_on_batteries to true" do + subject.frequency :minute + subject.disallow_start_if_on_batteries true + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:disallow_start_if_on_batteries]).to eql(true) + end + + it "sets disallow_start_if_on_batteries to false" do + subject.frequency :minute + subject.disallow_start_if_on_batteries false + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:disallow_start_if_on_batteries]).to eql(false) + end + + it "sets stop_if_going_on_batteries to true" do + subject.frequency :minute + subject.stop_if_going_on_batteries true + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:stop_if_going_on_batteries]).to eql(true) + end + + it "sets stop_if_going_on_batteries to false" do + subject.frequency :minute + subject.stop_if_going_on_batteries false + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:stop_if_going_on_batteries]).to eql(false) + end + + it "sets the default if options are nil" do + subject.frequency :minute + subject.stop_if_going_on_batteries nil + subject.disallow_start_if_on_batteries nil + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:stop_if_going_on_batteries]).to eql(false) + expect(current_resource.task.settings[:disallow_start_if_on_batteries]).to eql(false) + end + + it "does not converge the resource if it is already converged" do + subject.frequency :minute + subject.stop_if_going_on_batteries true + subject.disallow_start_if_on_batteries false + subject.run_action(:create) + subject.frequency :minute + subject.stop_if_going_on_batteries true + subject.disallow_start_if_on_batteries false + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "frequency :none" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :none + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates the scheduled task to run on demand only" do + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + + expect(current_resource.task.application_name).to eq(task_name) + expect(current_resource.task.principals[:run_level]).to eq(1) + expect(current_resource.task.trigger_count).to eq(0) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when start_when_available is passed" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "sets start_when_available to true" do + subject.frequency :minute + subject.start_when_available true + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:start_when_available]).to eql(true) + end + + it "sets start_when_available to false" do + subject.frequency :minute + subject.start_when_available false + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:start_when_available]).to eql(false) + end + + it "sets the default if start_when_available is nil" do + subject.frequency :minute + subject.start_when_available nil + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:start_when_available]).to eql(false) + end + + it "does not converge the resource if it is already converged" do + subject.frequency :minute + subject.start_when_available true + subject.run_action(:create) + subject.frequency :minute + subject.start_when_available true + subject.disallow_start_if_on_batteries false + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + end + + context "task_name with parent folder" do + describe "task_name with path '\\foo\\chef-client-functional-test' " do + let(:task_name) { "\\foo\\chef-client-functional-test" } + after { delete_task } + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :once + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates the scheduled task with task name 'chef-client-functional-test' inside path '\\foo'" do + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq(task_name) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + describe "task_name with path '\\foo\\bar\\chef-client-functional-test' " do + let(:task_name) { "\\foo\\bar\\chef-client-functional-test" } + after { delete_task } + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :once + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "creates the scheduled task with task with name 'chef-client-functional-test' inside path '\\foo\\bar' " do + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.application_name).to eq(task_name) + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + end + + describe "priority" do + after { delete_task } + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.frequency :once + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "default sets to 7" do + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("below_normal_7") + end + + it "0 sets priority level to critical" do + subject.priority = 0 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("critical") + end + + it "2 sets priority level to highest" do + subject.priority = 1 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("highest") + end + + it "2 sets priority level to above_normal" do + subject.priority = 2 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("above_normal_2") + end + + it "3 sets priority level to above_normal" do + subject.priority = 3 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("above_normal_3") + end + + it "4 sets priority level to normal" do + subject.priority = 4 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("normal_4") + end + + it "5 sets priority level to normal" do + subject.priority = 5 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("normal_5") + end + + it "6 sets priority level to normal" do + subject.priority = 6 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("normal_6") + end + + it "7 sets priority level to below_normal" do + subject.priority = 7 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("below_normal_7") + end + + it "8 sets priority level to below_normal" do + subject.priority = 8 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("below_normal_8") + end + + it "9 sets priority level to lowest" do + subject.priority = 9 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("lowest") + end + + it "10 sets priority level to idle" do + subject.priority = 10 + call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.task.priority).to eq("idle") + end + + it "is idempotent" do + subject.priority 8 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + end + + describe "Examples of idempotent checks for each frequency" do + after { delete_task } + context "For frequency :once" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :once + new_resource.start_time "17:00" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "create task by adding frequency_modifier as 1" do + subject.frequency_modifier 1 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "create task by adding frequency_modifier as 5" do + subject.frequency_modifier 5 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "For frequency :none" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource.frequency :none + new_resource + end + + it "create task by adding frequency_modifier as 1" do + subject.frequency_modifier 1 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "create task by adding frequency_modifier as 5" do + subject.frequency_modifier 5 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "For frequency :weekly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :weekly + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "create task by adding start_day" do + subject.start_day "12/28/2018" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "create task by adding frequency_modifier and random_delay" do + subject.frequency_modifier 3 + subject.random_delay "60" + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "For frequency :monthly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :once + new_resource.start_time "17:00" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "create task by adding frequency_modifier as 1" do + subject.frequency_modifier 1 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "create task by adding frequency_modifier as 5" do + subject.frequency_modifier 5 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "For frequency :hourly" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :hourly + new_resource.frequency_modifier 5 + new_resource.random_delay "2400" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "create task by adding frequency_modifier and random_delay" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "For frequency :daily" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :daily + new_resource.frequency_modifier 2 + new_resource.random_delay "2400" + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "create task by adding frequency_modifier and random_delay" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "For frequency :on_logon" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.frequency :on_logon + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "create task by adding frequency_modifier and random_delay" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + + it "create task by adding frequency_modifier as 5" do + subject.frequency_modifier 5 + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + + context "For frequency :onstart" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.frequency :onstart + new_resource.frequency_modifier 20 + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + it "create task by adding frequency_modifier as 20" do + subject.run_action(:create) + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end + end + + describe "#after_created" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource + end + + context "when start_day is passed with frequency :onstart" do + it "does not raises error" do + subject.frequency :onstart + subject.start_day "09/20/2017" + expect { subject.after_created }.not_to raise_error + end + end + + context "when a non system user is passed without password" do + it "raises error" do + subject.user "USER" + subject.frequency :onstart + expect { subject.after_created }.to raise_error(%q{Please provide a password or check if this task needs to be interactive! Valid passwordless users are: 'SYSTEM', 'NT AUTHORITY\SYSTEM', 'LOCAL SERVICE', 'NT AUTHORITY\LOCAL SERVICE', 'NETWORK SERVICE', 'NT AUTHORITY\NETWORK SERVICE', 'ADMINISTRATORS', 'BUILTIN\ADMINISTRATORS', 'USERS', 'BUILTIN\USERS', 'GUESTS', 'BUILTIN\GUESTS'}) + end + it "does not raises error when task is interactive" do + subject.user "USER" + subject.frequency :onstart + subject.interactive_enabled true + expect { subject.after_created }.not_to raise_error + end + end + + context "when a system user is passed without password" do + it "does not raises error" do + subject.user "ADMINISTRATORS" + subject.frequency :onstart + expect { subject.after_created }.not_to raise_error + end + it "does not raises error when task is interactive" do + subject.user "ADMINISTRATORS" + subject.frequency :onstart + subject.interactive_enabled true + expect { subject.after_created }.not_to raise_error + end + end + + context "when a non system user is passed with password" do + it "does not raises error" do + subject.user "USER" + subject.password "XXXX" + subject.frequency :onstart + expect { subject.after_created }.not_to raise_error + end + it "does not raises error when task is interactive" do + subject.user "USER" + subject.password "XXXX" + subject.frequency :onstart + subject.interactive_enabled true + expect { subject.after_created }.not_to raise_error + end + end + + context "when a system user is passed with password" do + it "raises error" do + subject.user "ADMINISTRATORS" + subject.password "XXXX" + subject.frequency :onstart + expect { subject.after_created }.to raise_error("Password is not required for system users.") + end + it "raises error when task is interactive" do + subject.user "ADMINISTRATORS" + subject.password "XXXX" + subject.frequency :onstart + subject.interactive_enabled true + expect { subject.after_created }.to raise_error("Password is not required for system users.") + end + end + + context "when frequency_modifier > 1439 is passed for frequency=:minute" do + it "raises error" do + subject.frequency_modifier 1450 + subject.frequency :minute + expect { subject.after_created }.to raise_error("frequency_modifier value 1450 is invalid. Valid values for :minute frequency are 1 - 1439.") + end + end + + context "when frequency_modifier > 23 is passed for frequency=:minute" do + it "raises error" do + subject.frequency_modifier 24 + subject.frequency :hourly + expect { subject.after_created }.to raise_error("frequency_modifier value 24 is invalid. Valid values for :hourly frequency are 1 - 23.") + end + end + + context "when frequency_modifier > 23 is passed for frequency=:minute" do + it "raises error" do + subject.frequency_modifier 366 + subject.frequency :daily + expect { subject.after_created }.to raise_error("frequency_modifier value 366 is invalid. Valid values for :daily frequency are 1 - 365.") + end + end + + context "when frequency_modifier > 52 is passed for frequency=:minute" do + it "raises error" do + subject.frequency_modifier 53 + subject.frequency :weekly + expect { subject.after_created }.to raise_error("frequency_modifier value 53 is invalid. Valid values for :weekly frequency are 1 - 52.") + end + end + + context "when invalid frequency_modifier is passed for :monthly frequency" do + it "raises error" do + subject.frequency :monthly + subject.frequency_modifier "13" + expect { subject.after_created }.to raise_error("frequency_modifier value 13 is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + end + + context "when invalid frequency_modifier is passed for :monthly frequency" do + it "raises error" do + subject.frequency :monthly + subject.frequency_modifier "xyz" + expect { subject.after_created }.to raise_error("frequency_modifier value xyz is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'.") + end + end + + context "when invalid months are passed" do + it "raises error" do + subject.months "xyz" + subject.frequency :monthly + expect { subject.after_created }.to raise_error("months property invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC, *. Multiple values must be separated by a comma.") + end + end + + context "when idle_time > 999 is passed" do + it "raises error" do + subject.idle_time 1000 + subject.frequency :on_idle + expect { subject.after_created }.to raise_error("idle_time value 1000 is invalid. Valid values for :on_idle frequency are 1 - 999.") + end + end + + context "when idle_time is passed for frequency=:monthly" do + it "raises error" do + subject.idle_time 300 + subject.frequency :monthly + expect { subject.after_created }.to raise_error("idle_time property is only valid for tasks that run on_idle") + end + end + end + + describe "action :delete" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accepts this + new_resource.frequency :hourly + new_resource + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:delete) + subject.run_action(:delete) + expect(subject).not_to be_updated_by_last_action + end + + it "does not converge the resource if it is already converged" do + subject.run_action(:create) + subject.run_action(:delete) + subject.run_action(:delete) + expect(subject).not_to be_updated_by_last_action + end + end + + describe "action :run" do + after { delete_task } + + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command "dir" + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly + new_resource + end + + it "runs the existing task" do + subject.run_action(:create) + subject.run_action(:run) + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("queued").or eq("running").or eq("ready") # queued or can be running + end + end + + describe "action :end", :volatile do + after { delete_task } + + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command "dir" + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource + end + + it "ends the running task" do + subject.run_action(:create) + subject.run_action(:run) + subject.run_action(:end) + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("queued").or eq("ready") # queued or can be ready + end + end + + describe "action :enable" do + let(:task_name) { "chef-client-functional-test-enable" } + after { delete_task } + + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly + new_resource + end + + it "enables the disabled task" do + subject.run_action(:create) + subject.run_action(:disable) + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("not scheduled") + subject.run_action(:enable) + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("ready") + end + end + + describe "action :disable" do + let(:task_name) { "chef-client-functional-test-disable" } + after { delete_task } + + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly + new_resource + end + + it "disables the task" do + subject.run_action(:create) + subject.run_action(:disable) + current_resource = call_for_load_current_resource + expect(current_resource.task.status).to eq("not scheduled") + end + end + + describe "action :change" do + after { delete_task } + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since + new_resource.frequency :hourly + new_resource + end + + it "call action_create since change action is alias for create" do + subject.run_action(:change) + expect(subject).to be_updated_by_last_action + end + end + + def delete_task + task_to_delete = Chef::Resource::WindowsTask.new(task_name, run_context) + task_to_delete.run_action(:delete) + end + + def call_for_create_action + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(false) + subject.run_action(:create) + expect(subject).to be_updated_by_last_action + end + + def call_for_load_current_resource + windows_task_provider.send(:load_current_resource) + end +end diff --git a/spec/functional/resource/windows_user_privilege_spec.rb b/spec/functional/resource/windows_user_privilege_spec.rb new file mode 100644 index 0000000000..f52bbbe23b --- /dev/null +++ b/spec/functional/resource/windows_user_privilege_spec.rb @@ -0,0 +1,192 @@ +# +# Author:: Vasundhara Jagdale (<vasundhara.jagdale@chef.io>) +# Copyright:: Copyright (c) Chef Software Inc. + +# 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::Resource::WindowsUserPrivilege, :windows_only do + let(:principal) { nil } + let(:privilege) { nil } + let(:users) { nil } + let(:sensitive) { true } + + let(:windows_test_run_context) do + node = Chef::Node.new + node.consume_external_attrs(OHAI_SYSTEM.data, {}) # node[:languages][:powershell][:version] + node.automatic["os"] = "windows" + node.automatic["platform"] = "windows" + node.automatic["platform_version"] = "6.1" + node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + end + + subject do + new_resource = Chef::Resource::WindowsUserPrivilege.new(principal, windows_test_run_context) + new_resource.privilege = privilege + new_resource.principal = principal + new_resource.users = users + new_resource + end + + describe "#add privilege" do + after { subject.run_action(:remove) } + + context "when privilege is passed as string" do + let(:principal) { "Administrator" } + let(:privilege) { "SeCreateSymbolicLinkPrivilege" } + + it "adds user to privilege" do + # Removing so that add update happens + subject.run_action(:remove) + subject.run_action(:add) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.run_action(:add) + subject.run_action(:add) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when privilege is passed as array" do + let(:principal) { "Administrator" } + let(:privilege) { %w{SeCreateSymbolicLinkPrivilege SeCreatePagefilePrivilege} } + + it "adds user to privilege" do + subject.run_action(:add) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.run_action(:add) + subject.run_action(:add) + expect(subject).not_to be_updated_by_last_action + end + end + end + + describe "#set privilege" do + after { remove_user_privilege("Administrator", subject.privilege) } + + let(:principal) { "user_privilege" } + let(:users) { %w{Administrators Administrator} } + let(:privilege) { %w{SeCreateSymbolicLinkPrivilege} } + + it "sets user to privilege" do + subject.action(:set) + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.action(:set) + subject.run_action(:set) + subject.run_action(:set) + expect(subject).not_to be_updated_by_last_action + end + + it "raise error if users not provided" do + subject.users = nil + subject.action(:set) + expect { subject.run_action(:set) }.to raise_error(Chef::Exceptions::ValidationFailed) + end + end + + describe "#remove privilege" do + let(:principal) { "Administrator" } + context "when privilege is passed as array" do + let(:privilege) { "SeCreateSymbolicLinkPrivilege" } + it "remove user from privilege" do + subject.run_action(:add) + subject.run_action(:remove) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.run_action(:add) + subject.run_action(:remove) + subject.run_action(:remove) + expect(subject).not_to be_updated_by_last_action + end + end + + context "when privilege is passed as array" do + let(:privilege) { %w{SeCreateSymbolicLinkPrivilege SeCreatePagefilePrivilege} } + it "remove user from privilege" do + subject.run_action(:add) + subject.run_action(:remove) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.run_action(:add) + subject.run_action(:remove) + subject.run_action(:remove) + expect(subject).not_to be_updated_by_last_action + end + end + end + + describe "running with non admin user" do + include Chef::Mixin::UserContext + + let(:user) { "security_user" } + let(:password) { "Security@123" } + let(:principal) { "user_privilege" } + let(:users) { ["Administrators", "#{domain}\\security_user"] } + let(:privilege) { %w{SeCreateSymbolicLinkPrivilege} } + + let(:domain) do + ENV["COMPUTERNAME"] + end + + before do + allow_any_instance_of(Chef::Mixin::UserContext).to receive(:node).and_return({ "platform_family" => "windows" }) + add_user = Mixlib::ShellOut.new("net user #{user} #{password} /ADD") + add_user.run_command + add_user.error! + end + + after do + remove_user_privilege("#{domain}\\#{user}", subject.privilege) + delete_user = Mixlib::ShellOut.new("net user #{user} /delete") + delete_user.run_command + delete_user.error! + end + + it "sets user to privilege" do + subject.action(:set) + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.action(:set) + subject.run_action(:set) + subject.run_action(:set) + expect(subject).not_to be_updated_by_last_action + end + end + + def remove_user_privilege(user, privilege) + subject.action(:remove) + subject.principal = user + subject.privilege = privilege + subject.run_action(:remove) + end +end diff --git a/spec/functional/resource/yum_package_spec.rb b/spec/functional/resource/yum_package_spec.rb new file mode 100644 index 0000000000..5f902cff17 --- /dev/null +++ b/spec/functional/resource/yum_package_spec.rb @@ -0,0 +1,981 @@ +# +# 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" +require "chef/mixin/shell_out" + +# run this test only for following platforms. +exclude_test = !(%w{rhel fedora amazon}.include?(ohai[:platform_family]) && !File.exist?("/usr/bin/dnf")) +describe Chef::Resource::YumPackage, :requires_root, external: exclude_test do + include Chef::Mixin::ShellOut + + # NOTE: every single test here either needs to explicitly call flush_cache or needs to explicitly + # call preinstall (which explicitly calls flush_cache). It is your responsibility to do one or the + # other in order to minimize calling flush_cache a half dozen times per test. + + def flush_cache + Chef::Resource::YumPackage.new("shouldnt-matter", run_context).run_action(:flush_cache) + end + + def preinstall(*rpms) + rpms.each do |rpm| + shell_out!("rpm -ivh #{CHEF_SPEC_ASSETS}/yumrepo/#{rpm}") + end + flush_cache + end + + before(:all) do + shell_out!("yum -y install yum-utils") + end + + before(:each) do + File.open("/etc/yum.repos.d/chef-yum-localtesting.repo", "w+") do |f| + f.write <<~EOF + [chef-yum-localtesting] + name=Chef DNF spec testing repo + baseurl=file://#{CHEF_SPEC_ASSETS}/yumrepo + enable=1 + gpgcheck=0 + EOF + end + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | grep chef_rpm | xargs -r rpm -e") + # next line is useful cleanup if you happen to have been testing both yum + dnf func tests on the same box and + # have some dnf garbage left around + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + end + + after(:all) do + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' | grep chef_rpm | xargs -r rpm -e") + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" + end + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + + let(:package_name) { "chef_rpm" } + let(:yum_package) do + r = Chef::Resource::YumPackage.new(package_name, run_context) + r.options("--nogpgcheck") + r + end + + def pkg_arch + ohai[:kernel][:machine] + end + + describe ":install" do + context "vanilla use case" do + let(:package_name) { "chef_rpm" } + + it "installs if the package is not installed" do + flush_cache + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install twice" do + flush_cache + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "does not install if the i686 package is installed", :intel_64bit do + skip "FIXME: do nothing, or install the #{pkg_arch} version?" + preinstall("chef_rpm-1.10-1.i686.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + end + + it "does not install if the prior version i686 package is installed", :intel_64bit do + skip "FIXME: do nothing, or install the #{pkg_arch} version?" + preinstall("chef_rpm-1.2-1.i686.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$") + end + end + + context "with versions or globs in the name" do + it "works with a version" do + flush_cache + yum_package.package_name("chef_rpm-1.10") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "works with an older version" do + flush_cache + yum_package.package_name("chef_rpm-1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with an evra" do + flush_cache + yum_package.package_name("chef_rpm-0:1.2-1.#{pkg_arch}") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with version and release" do + flush_cache + yum_package.package_name("chef_rpm-1.2-1") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with a version glob" do + flush_cache + yum_package.package_name("chef_rpm-1*") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "works with a name glob + version glob" do + flush_cache + yum_package.package_name("chef_rp*-1*") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "upgrades when the installed version does not match the version string" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm-1.10") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}") + end + + it "downgrades when the installed version is higher than the package_name version" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade true + yum_package.package_name("chef_rpm-1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + # version only matches the actual yum version, does not work with epoch or release or combined evr + context "with version property" do + it "matches the full version" do + flush_cache + yum_package.package_name("chef_rpm") + yum_package.version("1.10") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "matches with a glob" do + # we are unlikely to ever fix this. if you've found this comment you should use e.g. "tcpdump-4*" in + # the name field rather than trying to use a name of "tcpdump" and a version of "4*". + pending "this does not work, is not easily supported by the underlying yum libraries, but does work in the new dnf_package provider" + flush_cache + yum_package.package_name("chef_rpm") + yum_package.version("1*") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "matches the vr" do + flush_cache + yum_package.package_name("chef_rpm") + yum_package.version("1.10-1") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "matches the evr" do + flush_cache + yum_package.package_name("chef_rpm") + yum_package.version("0:1.10-1") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "matches with a vr glob" do + pending "doesn't work on command line either" + flush_cache + yum_package.package_name("chef_rpm") + yum_package.version("1.10-1*") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "matches with an evr glob" do + pending "doesn't work on command line either" + flush_cache + yum_package.package_name("chef_rpm") + yum_package.version("0:1.10-1*") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + end + + context "downgrades" do + it "downgrades the package when allow_downgrade" do + flush_cache + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm") + yum_package.allow_downgrade true + yum_package.version("1.2-1") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "with arches", :intel_64bit do + it "installs with 64-bit arch in the name" do + flush_cache + yum_package.package_name("chef_rpm.#{pkg_arch}") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "installs with 32-bit arch in the name" do + flush_cache + yum_package.package_name("chef_rpm.i686") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + end + + it "installs with 64-bit arch in the property" do + flush_cache + yum_package.package_name("chef_rpm") + yum_package.arch((pkg_arch).to_s) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "installs with 32-bit arch in the property" do + flush_cache + yum_package.package_name("chef_rpm") + yum_package.arch("i686") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + end + end + + context "with constraints" do + it "with nothing installed, it installs the latest version" do + flush_cache + yum_package.package_name("chef_rpm >= 1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm >= 1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm >= 1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with nothing intalled, it installs the latest version" do + flush_cache + yum_package.package_name("chef_rpm > 1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "when it is not met by an installed rpm, it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm > 1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with an equality constraint, when it is not met by an installed rpm, it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm = 1.10") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with an equality constraint, when it is met by an installed rpm, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm = 1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "when it is met by an installed rpm, it does nothing" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm > 1.2") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "when there is no solution to the contraint" do + flush_cache + yum_package.package_name("chef_rpm > 2.0") + expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "when there is no solution to the contraint but an rpm is preinstalled" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm > 2.0") + expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "with a less than constraint, when nothing is installed, it installs" do + flush_cache + yum_package.allow_downgrade true + yum_package.package_name("chef_rpm < 1.10") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a less than constraint, when the install version matches, it does nothing" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade true + yum_package.package_name("chef_rpm < 1.10") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a less than constraint, when the install version fails, it should downgrade" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade true + yum_package.package_name("chef_rpm < 1.10") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "with source arguments" do + it "raises an exception when the package does not exist" do + flush_cache + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") + expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "does not raise a hard exception in why-run mode when the package does not exist" do + Chef::Config[:why_run] = true + flush_cache + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/this-file-better-not-exist.rpm") + yum_package.run_action(:install) + expect { yum_package.run_action(:install) }.not_to raise_error + end + + it "installs the package when using the source argument" do + flush_cache + yum_package.name "something" + yum_package.package_name "somethingelse" + yum_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "installs the package when the name is a path to a file" do + flush_cache + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "downgrade on a local file is ignored when allow_downgrade is false" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade false + yum_package.version "1.2-1" + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "downgrade on a local file with allow_downgrade true works" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.version "1.2-1" + yum_package.allow_downgrade true + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "does not downgrade the package with :install" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not upgrade the package with :install" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed and there is a version string" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.version "1.2-1" + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed and there is a version string with arch" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.version "1.2-1.#{pkg_arch}" + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with a local source" do + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" + flush_cache + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "multipackage with arches", :intel_64bit do + it "installs two rpms" do + flush_cache + yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + end + + it "does nothing if both are installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") + flush_cache + yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + end + + it "installs the second rpm if the first is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + end + + it "installs the first rpm if the second is installed" do + preinstall("chef_rpm-1.10-1.i686.rpm") + yum_package.package_name([ "chef_rpm.#{pkg_arch}", "chef_rpm.i686" ] ) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs two rpms with multi-arch" do + flush_cache + yum_package.package_name(%w{chef_rpm chef_rpm} ) + yum_package.arch([pkg_arch, "i686"]) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs the second rpm if the first is installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name(%w{chef_rpm chef_rpm} ) + yum_package.arch([pkg_arch, "i686"]) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "installs the first rpm if the second is installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name(%w{chef_rpm chef_rpm} ) + yum_package.arch([pkg_arch, "i686"]) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.#{pkg_arch}$/) + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match(/^chef_rpm-1.10-1.i686$/) + end + + # unlikely to work consistently correct, okay to deprecate the arch-array in favor of the arch in the name + it "does nothing if both are installed (muti-arch)" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") + yum_package.package_name(%w{chef_rpm chef_rpm} ) + yum_package.arch([pkg_arch, "i686"]) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be false + end + end + + context "repo controls" do + it "should fail with the repo disabled" do + flush_cache + yum_package.options("--disablerepo=chef-yum-localtesting") + expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + end + + it "should work with disablerepo first" do + flush_cache + yum_package.options(["--disablerepo=*", "--enablerepo=chef-yum-localtesting"]) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "should work to enable a disabled repo" do + shell_out!("yum-config-manager --disable chef-yum-localtesting") + flush_cache + expect { yum_package.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /No candidate version available/) + flush_cache + yum_package.options("--enablerepo=chef-yum-localtesting") + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "when an idempotent install action is run, does not leave repos disabled" do + flush_cache + # this is a bit tricky -- we need this action to be idempotent, so that it doesn't recycle any + # caches, but need it to hit whatavailable with the repo disabled. using :upgrade like this + # accomplishes both those goals (it would be easier if we had other rpms in this repo, but with + # one rpm we need to do this). + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.options("--disablerepo=chef-yum-localtesting") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + # now we're still using the same cache in the yum_helper.py cache and we test to see if the + # repo that we temporarily disabled is enabled on this pass. + yum_package.package_name("chef_rpm-1.10-1.#{pkg_arch}") + yum_package.options(nil) + yum_package.run_action(:install) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + end + end + + describe ":upgrade" do + + context "with source arguments" do + it "installs the package when using the source argument" do + flush_cache + yum_package.name "something" + yum_package.package_name "somethingelse" + yum_package.source("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "installs the package when the name is a path to a file" do + flush_cache + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "downgrades the package when allow_downgrade is true" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade true + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "upgrades the package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with a local source" do + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" + flush_cache + yum_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + context "version pinning" do + it "with an equality pin in the name it upgrades a prior package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm-1.10") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a prco equality pin in the name it upgrades a prior package" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm == 1.10") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with an equality pin in the name it downgrades a later package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade true + yum_package.package_name("chef_rpm-1.2") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a prco equality pin in the name it downgrades a later package" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade true + yum_package.package_name("chef_rpm == 1.2") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a > pin in the name and no rpm installed it installs" do + flush_cache + yum_package.package_name("chef_rpm > 1.2") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a < pin in the name and no rpm installed it installs" do + flush_cache + yum_package.package_name("chef_rpm < 1.10") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a > pin in the name and matching rpm installed it does nothing" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm > 1.2") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a < pin in the name and no rpm installed it installs" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm < 1.10") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "with a > pin in the name and non-matching rpm installed it upgrades" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.package_name("chef_rpm > 1.2") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "with a < pin in the name and non-matching rpm installed it downgrades" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.allow_downgrade true + yum_package.package_name("chef_rpm < 1.10") + yum_package.run_action(:upgrade) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + end + + describe ":remove" do + context "vanilla use case" do + let(:package_name) { "chef_rpm" } + it "does nothing if the package is not installed" do + flush_cache + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "does not remove the package twice" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the i686 package is installed", :intel_64bit do + skip "FIXME: should this be fixed or is the current behavior correct?" + preinstall("chef_rpm-1.10-1.i686.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the prior version i686 package is installed", :intel_64bit do + skip "FIXME: should this be fixed or is the current behavior correct?" + preinstall("chef_rpm-1.2-1.i686.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + end + + context "with 64-bit arch", :intel_64bit do + let(:package_name) { "chef_rpm.#{pkg_arch}" } + it "does nothing if the package is not installed" do + flush_cache + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "does nothing if the i686 package is installed" do + preinstall("chef_rpm-1.10-1.i686.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.i686$") + end + + it "does nothing if the prior version i686 package is installed" do + preinstall("chef_rpm-1.2-1.i686.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.i686$") + end + end + + context "with 32-bit arch", :intel_64bit do + let(:package_name) { "chef_rpm.i686" } + it "removes only the 32-bit arch if both are installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm", "chef_rpm-1.10-1.i686.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/yum.repos.d/chef-yum-localtesting.repo" + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + yum_package.run_action(:remove) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + end + end + + describe ":lock and :unlock" do + before(:all) do + shell_out!("yum -y install yum-versionlock") + end + + before(:each) do + shell_out("yum versionlock delete '*:chef_rpm-*'") # will exit with error when nothing is locked, we don't care + end + + it "locks an rpm" do + flush_cache + yum_package.package_name("chef_rpm") + yum_package.run_action(:lock) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("yum versionlock list").stdout.chomp).to match("^0:chef_rpm-") + end + + it "does not lock if its already locked" do + flush_cache + shell_out!("yum versionlock add chef_rpm") + yum_package.package_name("chef_rpm") + yum_package.run_action(:lock) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("yum versionlock list").stdout.chomp).to match("^0:chef_rpm-") + end + + it "unlocks an rpm" do + flush_cache + shell_out!("yum versionlock add chef_rpm") + yum_package.package_name("chef_rpm") + yum_package.run_action(:unlock) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("yum versionlock list").stdout.chomp).not_to match("^0:chef_rpm-") + end + + it "does not unlock an already locked rpm" do + flush_cache + yum_package.package_name("chef_rpm") + yum_package.run_action(:unlock) + expect(yum_package.updated_by_last_action?).to be false + expect(shell_out("yum versionlock list").stdout.chomp).not_to match("^0:chef_rpm-") + end + + it "check that we can lock based on provides" do + flush_cache + yum_package.package_name("chef_rpm_provides") + yum_package.run_action(:lock) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("yum versionlock list").stdout.chomp).to match("^0:chef_rpm-") + end + + it "check that we can unlock based on provides" do + flush_cache + shell_out!("yum versionlock add chef_rpm") + yum_package.package_name("chef_rpm_provides") + yum_package.run_action(:unlock) + expect(yum_package.updated_by_last_action?).to be true + expect(shell_out("yum versionlock list").stdout.chomp).not_to match("^0:chef_rpm-") + end + end +end diff --git a/spec/functional/resource/zypper_package_spec.rb b/spec/functional/resource/zypper_package_spec.rb new file mode 100644 index 0000000000..ce6a3bf33c --- /dev/null +++ b/spec/functional/resource/zypper_package_spec.rb @@ -0,0 +1,247 @@ +# +# Author:: Dheeraj Dubey (<dheeraj.dubey@msystechnologies.com>) +# 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" +require "chef/mixin/shell_out" + +describe Chef::Resource::ZypperPackage, :requires_root, :suse_only do + include Chef::Mixin::ShellOut + + # NOTE: every single test here needs to explicitly call preinstall. + + def preinstall(*rpms) + rpms.each do |rpm| + shell_out!("rpm -ivh #{CHEF_SPEC_ASSETS}/zypprepo/#{rpm}") + end + end + + before(:each) do + File.open("/etc/zypp/repos.d/chef-zypp-localtesting.repo", "w+") do |f| + f.write <<~EOF + [chef-zypp-localtesting] + name=Chef zypper spec testing repo + baseurl=file://#{CHEF_SPEC_ASSETS}/zypprepo + enable=1 + gpgcheck=0 + EOF + end + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm | xargs -r rpm -e") + # next line is useful cleanup if you happen to have been testing zypper func tests on the same box and + # have some zypper garbage left around + # FileUtils.rm_f "/etc/zypp/repos.d/chef-zypp-localtesting.repo" + end + + after(:all) do + shell_out!("rpm -qa --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm | xargs -r rpm -e") + FileUtils.rm_f "/etc/zypp/repos.d/chef-zypp-localtesting.repo" + end + + let(:run_context) do + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) + end + + let(:package_name) { "chef_rpm" } + let(:zypper_package) do + r = Chef::Resource::ZypperPackage.new(package_name, run_context) + r.global_options("--no-gpg-checks") + r + end + + def pkg_arch + ohai[:kernel][:machine] + end + + context "installing a package" do + after { remove_package } + it "installs the latest version" do + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install twice" do + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "does not install if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "multipackage installs which result in nils from the superclass" do + # this looks weird, it tests an internal condition of the allow_nils behavior where the arrays passed to install_package will have + # nil values, and ensures that doesn't wind up creating weirdness in the resulting shell_out that causes it to fail + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + zypper_package.package_name(%w{chef_rpm chef_rpm}) + zypper_package.version(["1.2", "1.10"]) + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + end + + context "with versions" do + it "works with a version" do + zypper_package.package_name("chef_rpm") + zypper_package.version("1.10") + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.10-1.#{pkg_arch}$") + end + + it "works with an older version" do + zypper_package.package_name("chef_rpm") + zypper_package.version("1.2") + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "works with version and release" do + zypper_package.package_name("chef_rpm") + zypper_package.version("1.2-1") + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + + it "downgrades when the installed version is higher than the package_name version" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + zypper_package.allow_downgrade true + zypper_package.package_name("chef_rpm") + zypper_package.version("1.2-1") + zypper_package.run_action(:install) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^chef_rpm-1.2-1.#{pkg_arch}$") + end + end + + describe ":remove" do + context "vanilla use case" do + let(:package_name) { "chef_rpm" } + it "does nothing if the package is not installed" do + zypper_package.run_action(:remove) + expect(zypper_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + zypper_package.run_action(:remove) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "does not remove the package twice" do + preinstall("chef_rpm-1.10-1.#{pkg_arch}.rpm") + zypper_package.run_action(:remove) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + zypper_package.run_action(:remove) + expect(zypper_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + + it "removes the package if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + zypper_package.run_action(:remove) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + end + + context "with no available version" do + it "works when a package is installed" do + FileUtils.rm_f "/etc/zypp/repos.d/chef-zypp-localtesting.repo" + preinstall("chef_rpm-1.2-1.#{pkg_arch}.rpm") + zypper_package.run_action(:remove) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q --queryformat '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' chef_rpm").stdout.chomp).to match("^package chef_rpm is not installed$") + end + end + end + + describe ":lock and :unlock" do + before(:each) do + shell_out("zypper removelock chef_rpm") # will exit with error when nothing is locked, we don't care + end + + it "locks an rpm" do + zypper_package.package_name("chef_rpm") + zypper_package.run_action(:lock) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("zypper locks | grep chef_rpm").stdout.chomp).to match("chef_rpm") + end + + it "does not lock if its already locked" do + shell_out!("zypper addlock chef_rpm") + zypper_package.package_name("chef_rpm") + zypper_package.run_action(:lock) + expect(zypper_package.updated_by_last_action?).to be false + expect(shell_out("zypper locks | grep chef_rpm").stdout.chomp).to match("chef_rpm") + end + + it "unlocks an rpm" do + shell_out!("zypper addlock chef_rpm") + zypper_package.package_name("chef_rpm") + zypper_package.run_action(:unlock) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("zypper locks | grep chef_rpm").stdout.chomp).not_to match("chef_rpm") + end + + it "does not unlock an already locked rpm" do + zypper_package.package_name("chef_rpm") + zypper_package.run_action(:unlock) + expect(zypper_package.updated_by_last_action?).to be false + expect(shell_out("zypper locks | grep chef_rpm").stdout.chomp).not_to match("chef_rpm") + end + + it "check that we can lock based on provides" do + zypper_package.package_name("chef_rpm_provides") + zypper_package.run_action(:lock) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("zypper locks | grep chef_rpm_provides").stdout.chomp).to match("chef_rpm_provides") + end + + it "check that we can unlock based on provides" do + shell_out!("zypper addlock chef_rpm_provides") + zypper_package.package_name("chef_rpm_provides") + zypper_package.run_action(:unlock) + expect(zypper_package.updated_by_last_action?).to be true + expect(shell_out("zypper locks | grep chef_rpm_provides").stdout.chomp).not_to match("chef_rpm_provides") + end + end + def remove_package + pkg_to_remove = Chef::Resource::ZypperPackage.new(package_name, run_context) + pkg_to_remove.run_action(:remove) + end +end |