diff options
Diffstat (limited to 'spec/functional/resource')
33 files changed, 4240 insertions, 1327 deletions
diff --git a/spec/functional/resource/package_spec.rb b/spec/functional/resource/apt_package_spec.rb index 6dc55f7f01..c032bafc92 100644 --- a/spec/functional/resource/package_spec.rb +++ b/spec/functional/resource/apt_package_spec.rb @@ -1,7 +1,7 @@ # encoding: UTF-8 # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software, Inc. +# Copyright:: Copyright 2013-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,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 " + @@ -92,7 +92,7 @@ metadata = { :unix_only => true, :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 +143,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") @@ -260,7 +260,7 @@ describe Chef::Resource::Package, metadata do end before do - node.set[:preseed_value] = "FROM TEMPLATE" + node.normal[:preseed_value] = "FROM TEMPLATE" end it "preseeds the package, then installs it" do diff --git a/spec/functional/resource/bash_spec.rb b/spec/functional/resource/bash_spec.rb index 87211ec264..4a5fee64bc 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 2014-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,61 +21,27 @@ require "functional/resource/base" describe Chef::Resource::Bash, :unix_only do let(:code) { "echo hello" } - let(:resource) { + let(:resource) do 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 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..2b176ed06c 100644 --- a/spec/functional/resource/batch_spec.rb +++ b/spec/functional/resource/batch_spec.rb @@ -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..0b45e097af 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 2013-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -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 fb51fd2d64..e8dae581b9 100644 --- a/spec/functional/resource/chocolatey_package_spec.rb +++ b/spec/functional/resource/chocolatey_package_spec.rb @@ -18,16 +18,9 @@ require "spec_helper" require "chef/mixin/powershell_out" -describe Chef::Resource::ChocolateyPackage, :windows_only do +describe Chef::Resource::ChocolateyPackage, :windows_only, :choco_installed 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 - let(:package_name) { "test-A" } let(:package_list) { proc { powershell_out!("choco list -lo -r #{Array(package_name).join(' ')}").stdout.chomp } } let(:package_source) { File.join(CHEF_SPEC_ASSETS, "chocolatey_feed") } @@ -82,11 +75,6 @@ describe Chef::Resource::ChocolateyPackage, :windows_only do subject.package_name "blah" expect { subject.run_action(:install) }.to raise_error Chef::Exceptions::Package end - - it "raises if package version is not found" do - subject.version "3.0" - expect { subject.run_action(:install) }.to raise_error Chef::Exceptions::Package - end end context "upgrading a package" do diff --git a/spec/functional/resource/cron_spec.rb b/spec/functional/resource/cron_spec.rb index 3380eccb0d..1bff8bf874 100644 --- a/spec/functional/resource/cron_spec.rb +++ b/spec/functional/resource/cron_spec.rb @@ -1,7 +1,7 @@ # encoding: UTF-8 # # Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -120,7 +120,7 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do 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) + expect(shell_out("crontab -l -u #{new_resource.user} | grep '#{attribute.upcase}=\"#{value}\"'").exitstatus).to eq(0) end after do @@ -146,6 +146,13 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do new_resource.home "/home/opscode" create_and_validate_with_attribute(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, "") + end + end end describe "negative tests for create action" do @@ -154,7 +161,7 @@ describe Chef::Resource::Cron, :requires_root, :unix_only do 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/) + expect { new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron) cron_should_not_exists(new_resource.name) end diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb deleted file mode 100644 index 72eaea3c12..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 RuntimeError, "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/dnf_package_spec.rb b/spec/functional/resource/dnf_package_spec.rb new file mode 100644 index 0000000000..01611ef996 --- /dev/null +++ b/spec/functional/resource/dnf_package_spec.rb @@ -0,0 +1,686 @@ +# +# Copyright:: Copyright 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" + +# run this test only for following platforms. +exclude_test = !(%w{rhel fedora}.include?(ohai[:platform_family]) && File.exist?("/usr/bin/dnf")) +describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test do + include Chef::Mixin::ShellOut + + 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(: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 | grep chef_rpm | xargs -r rpm -e") + end + + after(:all) do + shell_out!("rpm -qa | grep chef_rpm | xargs -r rpm -e") + FileUtils.rm_f "/etc/yum.repos.d/chef-dnf-localtesting.repo" + end + + let(:package_name) { "chef_rpm" } + let(:dnf_package) { Chef::Resource::DnfPackage.new(package_name, run_context) } + + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "does not install if the package is installed" do + preinstall("chef_rpm-1.10-1.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "does not install if the prior version package is installed" do + preinstall("chef_rpm-1.2-1.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + end + + it "does not install if the i686 package is installed" do + skip "FIXME: do nothing, or install the x86_64 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.i686") + end + + it "does not install if the prior version i686 package is installed" do + skip "FIXME: do nothing, or install the x86_64 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 chef_rpm").stdout.chomp).to eql("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 + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + end + + it "works with an evr" do + flush_cache + dnf_package.package_name("chef_rpm-0:1.2-1") + 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 eql("chef_rpm-1.2-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "matches with a vr glob" do + pending "doesn't work on command line either" + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "matches with an evr glob" do + pending "doesn't work on command line either" + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + end + + context "downgrades" do + it "just work with DNF" do + preinstall("chef_rpm-1.10-1.x86_64.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 eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + dnf_package.version("1.2") + dnf_package.run_action(:install) + dnf_package.allow_downgrade true + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + end + end + + context "with arches" do + it "installs with 64-bit arch in the name" do + flush_cache + dnf_package.package_name("chef_rpm.x86_64") + 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 eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.i686") + end + + it "installs with 64-bit arch in the property" do + flush_cache + dnf_package.package_name("chef_rpm") + dnf_package.arch("x86_64") + 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 eql("chef_rpm-1.10-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.i686") + 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.2-1.x86_64.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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + end + + it "when it is met, it does nothing" do + preinstall("chef_rpm-1.10-1.x86_64.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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "with nothing intalled, 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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "when it is not met by an installed rpm, it upgrades" do + preinstall("chef_rpm-1.2-1.x86_64.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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "when it is met by an installed rpm, it does nothing" do + preinstall("chef_rpm-1.10-1.x86_64.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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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.x86_64.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 + 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.x86_64.rpm") + 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 eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + 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 eql("chef_rpm-1.2-1.x86_64") + end + + it "does not downgrade the package with :install" do + preinstall("chef_rpm-1.10-1.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "does not upgrade the package with :install" do + preinstall("chef_rpm-1.2-1.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + dnf_package.run_action(:install) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + 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 eql("chef_rpm-1.2-1.x86_64") + end + end + + context "multipackage with arches" do + it "installs two rpms" do + flush_cache + dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + 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.10-1.x86_64/) + expect(shell_out("rpm -q 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.x86_64.rpm", "chef_rpm-1.10-1.i686.rpm") + flush_cache + dnf_package.package_name([ "chef_rpm.x86_64", "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.x86_64.rpm") + dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + 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.10-1.x86_64/) + expect(shell_out("rpm -q 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") + dnf_package.package_name([ "chef_rpm.x86_64", "chef_rpm.i686" ] ) + 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.10-1.x86_64/) + expect(shell_out("rpm -q 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 + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + 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.10-1.x86_64/) + expect(shell_out("rpm -q 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.x86_64.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + 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.10-1.x86_64/) + expect(shell_out("rpm -q 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.x86_64.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + 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.10-1.x86_64/) + expect(shell_out("rpm -q 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.x86_64.rpm", "chef_rpm-1.10-1.i686.rpm") + dnf_package.package_name(%w{chef_rpm chef_rpm} ) + dnf_package.arch(%w{x86_64 i686}) + 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.x86_64.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 eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + dnf_package.version("1.2") + dnf_package.run_action(:install) + dnf_package.allow_downgrade true + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + end + + it "downgrades the package" do + preinstall("chef_rpm-1.10-1.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + end + + it "upgrades the package" do + preinstall("chef_rpm-1.2-1.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.10-1.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + end + + it "is idempotent when the package is already installed" do + preinstall("chef_rpm-1.2-1.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + dnf_package.package_name("#{CHEF_SPEC_ASSETS}/yumrepo/chef_rpm-1.2-1.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + 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.x86_64.rpm") + dnf_package.run_action(:upgrade) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.x86_64") + 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 chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "does not remove the package twice" do + preinstall("chef_rpm-1.10-1.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("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 chef_rpm").stdout.chomp).to eql("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.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the i686 package is installed" 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 chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the prior version i686 package is installed" 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 chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + end + + context "with 64-bit arch" do + let(:package_name) { "chef_rpm.x86_64" } + 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 chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + + it "removes the package if the package is installed" do + preinstall("chef_rpm-1.10-1.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("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.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("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") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be false + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.2-1.i686") + end + end + + context "with 32-bit arch" 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.x86_64.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 chef_rpm").stdout.chomp).to eql("chef_rpm-1.10-1.x86_64") + 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.x86_64.rpm") + dnf_package.run_action(:remove) + expect(dnf_package.updated_by_last_action?).to be true + expect(shell_out("rpm -q chef_rpm").stdout.chomp).to eql("package chef_rpm is not installed") + end + end + end +end diff --git a/spec/functional/resource/dpkg_package_spec.rb b/spec/functional/resource/dpkg_package_spec.rb index d65256231b..1988fd0c7d 100644 --- a/spec/functional/resource/dpkg_package_spec.rb +++ b/spec/functional/resource/dpkg_package_spec.rb @@ -27,11 +27,11 @@ describe Chef::Resource::DpkgPackage, :requires_root, :debian_family_only, arch: let(:test1_1) { File.join(apt_data, "chef-integration-test_1.1-1_amd64.deb") } let(:test2_0) { File.join(apt_data, "chef-integration-test2_1.0-1_amd64.deb") } - let(:run_context) { + let(:run_context) do node = TEST_NODE.dup events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) - } + end let(:dpkg_package) { Chef::Resource::DpkgPackage.new(test1_0, run_context) } diff --git a/spec/functional/resource/dsc_resource_spec.rb b/spec/functional/resource/dsc_resource_spec.rb index 8e4940d475..bb3cf2157d 100644 --- a/spec/functional/resource/dsc_resource_spec.rb +++ b/spec/functional/resource/dsc_resource_spec.rb @@ -21,17 +21,17 @@ require "spec_helper" describe Chef::Resource::DscResource, :windows_powershell_dsc_only do let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } - let(:node) { + let(:node) do Chef::Node.new.tap do |n| n.consume_external_attrs(OHAI_SYSTEM.data, {}) end - } + end let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } - let(:new_resource) { + let(:new_resource) 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 @@ -77,7 +77,7 @@ describe Chef::Resource::DscResource, :windows_powershell_dsc_only do new_resource.run_action(:run) expect(new_resource).to be_updated reresource = - Chef::Resource::DscResource.new("dsc_resource_retest", run_context) + Chef::Resource::DscResource.new("dsc_resource_retest", run_context) reresource.resource :File reresource.property :Contents, test_text reresource.property :DestinationPath, tmp_file_name diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb index 42c23a695f..ce92c468f0 100644 --- a/spec/functional/resource/dsc_script_spec.rb +++ b/spec/functional/resource/dsc_script_spec.rb @@ -65,28 +65,27 @@ 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" } let(:dsc_env_value1) { "value1" } let(:env_value2) { "value2" } - let(:dsc_test_run_context) { + let(:dsc_test_run_context) do node = Chef::Node.new + 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 let(:dsc_test_resource_name) { "DSCTest" } - let(:dsc_test_resource_base) { + let(:dsc_test_resource_base) do Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) - } + end let(:test_registry_key) { 'HKEY_LOCAL_MACHINE\Software\Chef\Spec\Functional\Resource\dsc_script_spec' } let(:test_registry_value) { "Registration" } let(:test_registry_data1) { "LL927" } @@ -94,7 +93,8 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do let(:reg_key_name_param_name) { "testregkeyname" } let(:reg_key_value_param_name) { "testregvaluename" } let(:registry_embedded_parameters) { "$#{reg_key_name_param_name} = '#{test_registry_key}';$#{reg_key_value_param_name} = '#{test_registry_value}'" } - let(:dsc_reg_code) { <<-EOH + let(:dsc_reg_code) do + <<-EOH #{registry_embedded_parameters} Registry "ChefRegKey" { @@ -104,14 +104,15 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do Ensure = 'Present' } EOH - } + end let(:dsc_code) { dsc_reg_code } - let(:dsc_reg_script) { <<-EOH + let(:dsc_reg_script) do + <<-EOH param($testregkeyname, $testregvaluename) #{dsc_reg_code} EOH - } + end let(:dsc_user_prefix) { "dsc" } let(:dsc_user_suffix) { "chefx" } @@ -128,7 +129,8 @@ EOH let(:dsc_user_param_code) { "\"$(#{dsc_user_prefix_param_code})_usr_$(#{dsc_user_suffix_param_code})\"" } let(:config_flags) { nil } - let(:config_params) { <<-EOH + let(:config_params) do + <<-EOH [CmdletBinding()] param @@ -137,14 +139,15 @@ EOH $#{dsc_user_suffix_param_name} ) EOH - } + end let(:config_param_section) { "" } let(:dsc_user_code) { "'#{dsc_user}'" } let(:dsc_user_prefix_code) { dsc_user_prefix } let(:dsc_user_suffix_code) { dsc_user_suffix } let(:dsc_script_environment_attribute) { nil } - let(:dsc_user_resources_code) { <<-EOH + let(:dsc_user_resources_code) do + <<-EOH #{config_param_section} node localhost { @@ -164,9 +167,9 @@ User dsctestusercreate } } EOH - } + end - let(:dsc_user_config_data) { + let(:dsc_user_config_data) do <<-EOH @{ AllNodes = @( @@ -178,13 +181,14 @@ 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(:exception_message_signature) { "LL927-LL928" } - let(:dsc_environment_config) {<<-EOH + 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}' @@ -196,21 +200,21 @@ environment "whatsmydir" Ensure = 'Present' } EOH - } + end - let(:dsc_config_name) { + let(:dsc_config_name) do dsc_test_resource_base.name - } - let(:dsc_resource_from_code) { + end + let(:dsc_resource_from_code) do dsc_test_resource_base.code(dsc_code) dsc_test_resource_base - } + end let(:config_name_value) { dsc_test_resource_base.name } - let(:dsc_resource_from_path) { + let(:dsc_resource_from_path) do dsc_test_resource_base.command(create_config_script_from_code(dsc_code, config_name_value)) dsc_test_resource_base - } + end before(:each) do test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) @@ -469,6 +473,7 @@ EOF end it "allows the use of ps_credential" do + skip("Skipped until we can adjust the test cert to meet the WMF 5 cert requirements.") expect(user_exists?(dsc_user)).to eq(false) powershell_script_resource.run_action(:run) expect(File).to exist(configuration_data_path) diff --git a/spec/functional/resource/env_spec.rb b/spec/functional/resource/env_spec.rb deleted file mode 100755 index 83328a6738..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) { - 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) - } - let(:test_resource) { - Chef::Resource::Env.new("unknown", test_run_context) - } - - 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 c5978da519..c0956c5594 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 2014-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,11 +21,11 @@ require "functional/resource/base" require "timeout" describe Chef::Resource::Execute do - let(:resource) { + let(:resource) do resource = Chef::Resource::Execute.new("foo_resource", run_context) resource.command("echo hello") resource - } + end describe "when guard is ruby block" do it "guard can still run" do @@ -41,17 +41,17 @@ describe Chef::Resource::Execute do end let(:guard) { "ruby -e 'exit 0'" } - let!(:guard_resource) { + let!(:guard_resource) do interpreter = Chef::GuardInterpreter::ResourceGuardInterpreter.new(resource, guard, nil) interpreter.send(:get_interpreter_resource, resource) - } + end it "executes the guard and not the regular resource" do expect_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:get_interpreter_resource).and_return(guard_resource) # why_run mode doesn't disable the updated_by_last_action logic, so we really have to look at the provider action # to see if why_run correctly disabled the resource. It should shell_out! for the guard but not the resource. - expect_any_instance_of(Chef::Provider::Execute).to receive(:shell_out!).once + expect_any_instance_of(Chef::Provider::Execute).to receive(:shell_out_with_systems_locale!).once resource.only_if guard resource.run_action(:run) @@ -137,6 +137,18 @@ describe Chef::Resource::Execute do 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/group_spec.rb b/spec/functional/resource/group_spec.rb index a5de63b7c6..ea9aa5c2b7 100644 --- a/spec/functional/resource/group_spec.rb +++ b/spec/functional/resource/group_spec.rb @@ -81,25 +81,39 @@ 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 + def node + node = Chef::Node.new + node.consume_external_attrs(ohai.data, {}) + node + end + def user(username) - usr = Chef::Resource::User.new("#{username}", run_context) + usr = Chef::Resource.resource_for_node(:user, node).new(username, run_context) if ohai[:platform_family] == "windows" usr.password("ComplexPass11!") end usr end - def create_user(username) - user(username).run_action(:create) if ! windows_domain_user?(username) + def create_user(username, uid = nil) + if ! 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) - user(username).run_action(:remove) if ! windows_domain_user?(username) + if ! 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) + end # TODO: User shouldn't exist end @@ -159,8 +173,11 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte 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 @@ -282,12 +299,13 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte let(:group_name) { "group#{SecureRandom.random_number(9999)}" } let(:included_members) { nil } let(:excluded_members) { nil } - let(:group_resource) { + let(:group_resource) do group = Chef::Resource::Group.new(group_name, run_context) group.members(included_members) group.excluded_members(excluded_members) + group.gid(30000) unless ohai[:platform_family] == "mac_os_x" group - } + end it "append should be false by default" do expect(group_resource.append).to eq(false) @@ -305,10 +323,11 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte end describe "when group name is length 256", :windows_only do - let!(:group_name) { "theoldmanwalkingdownthestreetalwayshadagood\ + let!(:group_name) do + "theoldmanwalkingdownthestreetalwayshadagood\ smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface\ theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\ -downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree" } +downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree" end it "should create a group" do group_resource.run_action(:create) @@ -316,18 +335,6 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree" } end end - describe "when group name length is more than 256", :windows_only do - let!(:group_name) { "theoldmanwalkingdownthestreetalwayshadagood\ -smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface\ -theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\ -downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" } - - 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 @@ -339,6 +346,25 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" } 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 @@ -405,6 +431,7 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" } 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 diff --git a/spec/functional/resource/ifconfig_spec.rb b/spec/functional/resource/ifconfig_spec.rb index a30dcff641..f32ed069b5 100644 --- a/spec/functional/resource/ifconfig_spec.rb +++ b/spec/functional/resource/ifconfig_spec.rb @@ -16,11 +16,12 @@ # limitations under the License. # +require "spec_helper" require "functional/resource/base" require "chef/mixin/shell_out" # run this test only for following platforms. -include_flag = !(%w{ubuntu centos aix}.include?(ohai[:platform])) +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, :skip_travis, :external => include_flag do # This test does not work in travis because there is no eth0 @@ -51,11 +52,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").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 diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb index 6dd6ad8422..2b8b509730 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 2011-2017, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +20,14 @@ require "spec_helper" if windows? 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. @@ -62,6 +64,18 @@ describe Chef::Resource::Link do end end + def node + node = Chef::Node.new + node.consume_external_attrs(ohai.data, {}) + node + end + + def user(user) + usr = Chef::Resource.resource_for_node(:user, node).new(user, run_context) + usr.password("ComplexPass11!") if windows? + usr + end + def cleanup_link(path) if windows? && File.directory?(path) # If the link target is a directory rm_rf doesn't work all the @@ -108,12 +122,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.kind_of?(String) + Chef::ReservedNames::Win32::Security::SID.from_account(value) + elsif value.kind_of?(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 +174,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 +186,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 +207,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 +228,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 +250,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,7 +272,7 @@ 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 @@ -244,7 +297,7 @@ 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 @@ -291,13 +344,23 @@ describe Chef::Resource::Link do expect(File.exists?(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) @@ -388,6 +451,14 @@ describe Chef::Resource::Link do symlink(other_dir, target_file) end include_context "create symbolic link succeeds" + include_context "delete succeeds" + end + context "and the link already exists and points at the target" do + before do + symlink(to, target_file) + end + include_context "create symbolic link is noop" + include_context "delete succeeds" end end context "when the link destination is a symbolic link" do @@ -406,6 +477,19 @@ describe Chef::Resource::Link do include_context "create symbolic link succeeds" include_context "delete is noop" end + context "and the destination itself has another symbolic link" do + context "to a link that exist" do + before do + symlink(to, target_file) + end + include_context "create symbolic link is noop" + include_context "delete succeeds" + end + context "to a link that does not exist" do + include_context "create symbolic link succeeds" + include_context "delete is noop" + end + end end context "to a file that does not exist" do before(:each) do @@ -418,6 +502,19 @@ describe Chef::Resource::Link do include_context "create symbolic link succeeds" include_context "delete is noop" end + context "and the destination itself has another symbolic link" do + context "to a link that exist" do + before do + symlink(to, target_file) + end + include_context "create symbolic link is noop" + include_context "delete succeeds" + end + context "to a link that does not exist" do + include_context "create symbolic link succeeds" + include_context "delete is noop" + end + end end end context "when the link destination does not exist" do @@ -559,7 +656,7 @@ 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("OS X/FreeBSD/AIX/Solaris symlink? and readlink working on hard links to symlinks") if os_x? || 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. @@ -578,14 +675,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("OS X/FreeBSD/AIX/Solaris fails to create hardlinks to broken symlinks") if os_x? || freebsd? || aix? || solaris? resource.run_action(:create) - # Windows and Unix have different definitions of exists? here, and that's OK. - if windows? - expect(File.exists?(target_file)).to be_truthy - else - expect(File.exists?(target_file)).to be_falsey - end + expect(File.exists?(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 @@ -604,10 +696,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/mount_spec.rb b/spec/functional/resource/mount_spec.rb index c756b0d3d4..c98d6cec25 100644 --- a/spec/functional/resource/mount_spec.rb +++ b/spec/functional/resource/mount_spec.rb @@ -22,7 +22,7 @@ require "chef/mixin/shell_out" require "tmpdir" # run this test only for following platforms. -include_flag = !(%w{ubuntu centos aix solaris2}.include?(ohai[:platform])) +include_flag = !(%w{debian rhel amazon aix solaris2}.include?(ohai[:platform_family])) 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 @@ -35,15 +35,19 @@ describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => inclu 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. diff --git a/spec/functional/resource/msu_package_spec.rb b/spec/functional/resource/msu_package_spec.rb new file mode 100644 index 0000000000..23342be6ae --- /dev/null +++ b/spec/functional/resource/msu_package_spec.rb @@ -0,0 +1,84 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# Copyright:: Copyright (c) 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 "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(:new_resource) { Chef::Resource::CabPackage.new("windows_test_pkg") } + let(:cab_provider) do + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + 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 + 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_name}~/ } + 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_name}~/ } + 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 + + 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 9ce989d8df..06bccfc398 100644 --- a/spec/functional/resource/ohai_spec.rb +++ b/spec/functional/resource/ohai_spec.rb @@ -19,18 +19,18 @@ require "spec_helper" describe Chef::Resource::Ohai do - let(:ohai) { + let(:ohai) do OHAI_SYSTEM - } + end let(:node) { Chef::Node.new } - let(:run_context) { + let(:run_context) do node.default[:platform] = ohai[:platform] node.default[:platform_version] = ohai[:platform_version] events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) - } + end shared_examples_for "reloaded :uptime" do it "should reload :uptime" do @@ -51,11 +51,11 @@ describe Chef::Resource::Ohai do end describe "when reloading only uptime" do - let(:ohai_resource) { + let(:ohai_resource) do r = Chef::Resource::Ohai.new("reload all", run_context) r.plugin("uptime") r - } + end it_behaves_like "reloaded :uptime" end diff --git a/spec/functional/resource/powershell_script_spec.rb b/spec/functional/resource/powershell_script_spec.rb index af345b0ea4..bbd304fd06 100644 --- a/spec/functional/resource/powershell_script_spec.rb +++ b/spec/functional/resource/powershell_script_spec.rb @@ -36,8 +36,6 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do 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_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" } diff --git a/spec/functional/resource/reboot_spec.rb b/spec/functional/resource/reboot_spec.rb index 3cf7f58e55..c264b122a7 100644 --- a/spec/functional/resource/reboot_spec.rb +++ b/spec/functional/resource/reboot_spec.rb @@ -80,9 +80,9 @@ describe Chef::Resource::Reboot do it "should have modified the run context correctly" do # this doesn't actually test the flow of Chef::Client#do_run, unfortunately. - expect { + expect do resource.run_action(:reboot_now) - }.to throw_symbol(:end_client_run_early) + end.to throw_symbol(:end_client_run_early) test_reboot_action(resource) end diff --git a/spec/functional/resource/registry_spec.rb b/spec/functional/resource/registry_spec.rb index e64b6697c5..7318086034 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 2011-2018, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -112,9 +112,9 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true 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) @@ -124,7 +124,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do @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) + @new_resource.cookbook_version(@cookbook_version) end after (:all) do @@ -153,6 +153,16 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do 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.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) + 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" }]) @@ -187,13 +197,37 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do 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.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" }]) @@ -210,7 +244,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do @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 @@ -260,7 +294,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true 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.recursive(false) @@ -268,6 +302,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do 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" }]) @@ -275,6 +310,34 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do @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 @@ -320,13 +383,37 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do 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.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" }]) @@ -343,7 +430,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do @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 @@ -371,7 +458,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true 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.recursive(false) @@ -379,6 +466,7 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do 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" }]) @@ -386,6 +474,34 @@ describe Chef::Resource::RegistryKey, :windows_only, :broken => true do @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 diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb index b394bd0240..8e484b2968 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 2011-2018, 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] = 0 + Chef::Config[:http_retry_count] = 0 end after(:each) do @@ -56,7 +59,7 @@ describe Chef::Resource::RemoteFile do context "when fetching files over HTTP" do before(:all) do - start_tiny_server + start_tiny_server(RequestTimeout: 1) end after(:all) do @@ -103,10 +106,11 @@ describe Chef::Resource::RemoteFile do 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) end @@ -123,9 +127,180 @@ 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).gsub(/\//, "\\") } + 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.gsub(/\//, '\\')}\" /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(:all) do - start_tiny_server + start_tiny_server(RequestTimeout: 1) end after(:all) do @@ -232,7 +407,16 @@ describe Chef::Resource::RemoteFile do end it "should not create the file" do - expect { resource.run_action(:create) }.to raise_error + # 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(File).not_to exist(path) end end diff --git a/spec/functional/resource/rpm_spec.rb b/spec/functional/resource/rpm_spec.rb index ce9332e4ed..17d0bf9e3c 100644 --- a/spec/functional/resource/rpm_spec.rb +++ b/spec/functional/resource/rpm_spec.rb @@ -21,7 +21,7 @@ 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]) +exclude_test = !%w{aix rhel fedora suse}.include?(ohai[:platform_family]) describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test do include Chef::Mixin::ShellOut @@ -32,37 +32,34 @@ describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test d 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 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") 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 f270043f2c..b9a39255f4 100644 --- a/spec/functional/resource/template_spec.rb +++ b/spec/functional/resource/template_spec.rb @@ -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 @@ -110,7 +111,7 @@ describe Chef::Resource::Template do context "using single helper syntax referencing @node" do before do - node.set[:helper_test_attr] = "value from helper method" + node.normal[:helper_test_attr] = "value from helper method" resource.helper(:helper_method) { "#{@node[:helper_test_attr]}" } end @@ -131,7 +132,7 @@ describe Chef::Resource::Template do context "using an inline block referencing @node" do before do - node.set[:helper_test_attr] = "value from helper method" + node.normal[:helper_test_attr] = "value from helper method" resource.helpers do def helper_method @@ -168,7 +169,7 @@ describe Chef::Resource::Template do end before do - node.set[:helper_test_attr] = "value from helper method" + node.normal[:helper_test_attr] = "value from helper method" resource.helpers(ExampleModuleReferencingATNode) end @@ -209,4 +210,36 @@ describe Chef::Resource::Template do 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/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb index 5d904a980b..ed96e31bac 100644 --- a/spec/functional/resource/user/dscl_spec.rb +++ b/spec/functional/resource/user/dscl_spec.rb @@ -28,11 +28,9 @@ describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metada include Chef::Mixin::ShellOut def clean_user - begin - shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'") - rescue Mixlib::ShellOut::ShellCommandFailed + shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'") + rescue Mixlib::ShellOut::ShellCommandFailed # Raised when the user is already cleaned - end end def user_should_exist @@ -76,7 +74,7 @@ describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metada let(:iterations) { nil } let(:user_resource) do - r = Chef::Resource::User.new("TEST USER RESOURCE", run_context) + r = Chef::Resource::User::DsclUser.new("TEST USER RESOURCE", run_context) r.username(username) r.uid(uid) r.gid(gid) @@ -123,7 +121,7 @@ describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metada end describe "when password is being set via shadow hash" do - let(:password) { + let(:password) do if node[:platform_version].start_with?("10.7.") # On Mac 10.7 we only need to set the password "c9b3bd1a0cde797eef0eff16c580dab996ba3a21961cccc\ @@ -139,7 +137,7 @@ b1d4880833aa7a190afc13e2bf0936b8\ c5adbbac718b7eb99463a7b679571e0f\ 1c9fef2ef08d0b9e9c2bcf644eed2ffc" end - } + end let(:iterations) { 25000 } let(:salt) { "9e2e7d5ee473b496fd24cf0bbfcaedfcb291ee21740e570d1e917e874f8788ca" } diff --git a/spec/functional/resource/user/useradd_spec.rb b/spec/functional/resource/user/useradd_spec.rb index 43c26ac006..175809b5c0 100644 --- a/spec/functional/resource/user/useradd_spec.rb +++ b/spec/functional/resource/user/useradd_spec.rb @@ -21,19 +21,26 @@ require "spec_helper" require "functional/resource/base" require "chef/mixin/shell_out" -def user_provider_for_platform - case ohai[:platform] +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 - else - Chef::Provider::User::Useradd + 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_for_platform }, +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 @@ -81,12 +88,13 @@ describe Chef::Provider::User::Useradd, metadata do end def try_cleanup - ["/home/cheftestfoo", "/home/cheftestbar"].each do |f| + ["/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 = Chef::Resource::User.new("DELETE USER", run_context) + r = resource_for_platform("DELETE USER", run_context) + r.manage_home true r.username("cf-test") r.run_action(:remove) end @@ -111,7 +119,7 @@ describe Chef::Provider::User::Useradd, metadata do break if status.exitstatus != 8 sleep 1 - max_retries = max_retries - 1 + max_retries -= 1 rescue UserNotFound break end @@ -134,10 +142,7 @@ describe Chef::Provider::User::Useradd, metadata do Chef::RunContext.new(node, {}, events) end - let(:username) do - "cf-test" - end - + let(:username) { "cf-test" } let(:uid) { nil } let(:home) { nil } let(:manage_home) { false } @@ -146,7 +151,7 @@ describe Chef::Provider::User::Useradd, metadata do let(:comment) { nil } let(:user_resource) do - r = Chef::Resource::User.new("TEST USER RESOURCE", run_context) + r = resource_for_platform("TEST USER RESOURCE", run_context) r.username(username) r.uid(uid) r.home(home) @@ -242,15 +247,8 @@ describe Chef::Provider::User::Useradd, metadata do expect(pw_entry.home).to eq(home) end - if %w{rhel fedora wrlinux}.include?(OHAI_SYSTEM["platform_family"]) - # Inconsistent behavior. See: CHEF-2205 - it "creates the home dir when not explicitly asked to on RHEL (XXX)" do - expect(File).to exist(home) - end - else - it "does not create the home dir without `manage_home'" do - expect(File).not_to exist(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 @@ -260,6 +258,14 @@ describe Chef::Provider::User::Useradd, metadata 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 @@ -310,8 +316,8 @@ describe Chef::Provider::User::Useradd, metadata do let(:existing_comment) { nil } let(:existing_user) do - r = Chef::Resource::User.new("TEST USER RESOURCE", run_context) - # username is identity attr, must match. + 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) @@ -631,12 +637,23 @@ describe Chef::Provider::User::Useradd, metadata do 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: + if %w{opensuse}.include?(OHAI_SYSTEM["platform_family"]) || + (%w{suse}.include?(OHAI_SYSTEM["platform_family"]) && + OHAI_SYSTEM["platform_version"].to_f < 12.0) + # suse 11.x 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 + elsif %w{rhel}.include?(OHAI_SYSTEM["platform_family"]) && + (Chef::VersionConstraint.new("~> 6.8").include?(OHAI_SYSTEM["platform_version"].to_f) || Chef::VersionConstraint.new("~> 7.3").include?(OHAI_SYSTEM["platform_version"].to_f)) + # RHEL 6.8 and 7.3 ship with a fixed `usermod` command + # Reference: https://access.redhat.com/errata/RHBA-2016:0864 + # Reference: https://access.redhat.com/errata/RHBA-2016:2322 + it "errors out trying to unlock the user" do + expect(@error).to be_a(Mixlib::ShellOut::ShellCommandFailed) + expect(@error.message).to include("You should set a password") + end else # borked on all other platforms: diff --git a/spec/functional/resource/user/windows_spec.rb b/spec/functional/resource/user/windows_spec.rb index f61a51c636..56ae962ee4 100644 --- a/spec/functional/resource/user/windows_spec.rb +++ b/spec/functional/resource/user/windows_spec.rb @@ -31,6 +31,7 @@ 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| @@ -45,6 +46,7 @@ describe Chef::Provider::User::Windows, :windows_only do before do delete_user(username) + allow(run_context).to receive(:logger).and_return(logger) end describe "action :create" do @@ -69,7 +71,7 @@ describe Chef::Provider::User::Windows, :windows_only do 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_env_spec.rb b/spec/functional/resource/windows_env_spec.rb new file mode 100644 index 0000000000..a6c6b39970 --- /dev/null +++ b/spec/functional/resource/windows_env_spec.rb @@ -0,0 +1,285 @@ +# +# 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::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 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 + 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}") + 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_path_spec.rb b/spec/functional/resource/windows_path_spec.rb new file mode 100644 index 0000000000..912abe6b24 --- /dev/null +++ b/spec/functional/resource/windows_path_spec.rb @@ -0,0 +1,64 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# Copyright:: Copyright (c) 2017 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" } + + 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_service_spec.rb b/spec/functional/resource/windows_service_spec.rb index b4af1e9e6a..531f9e9250 100644 --- a/spec/functional/resource/windows_service_spec.rb +++ b/spec/functional/resource/windows_service_spec.rb @@ -27,19 +27,19 @@ describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_ let(:qualified_username) { "#{ENV['COMPUTERNAME']}\\#{username}" } let(:password) { "1a2b3c4X!&narf" } - let(:user_resource) { - r = Chef::Resource::User.new(username, run_context) + let(:user_resource) do + r = Chef::Resource::User::WindowsUser.new(username, run_context) r.username(username) r.password(password) r.comment("temp spec user") r - } + end - let(:global_service_file_path) { + let(:global_service_file_path) do "#{ENV['WINDIR']}\\temp\\#{File.basename(test_service[:service_file_path])}" - } + end - let(:service_params) { + let(:service_params) do id = "#{$$}_#{rand(1000)}" @@ -51,19 +51,19 @@ describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_ service_description: "Test service for running the windows_service functional spec.", service_file_path: global_service_file_path, } ) - } + end - let(:manager) { + let(:manager) do Chef::Application::WindowsServiceManager.new(service_params) - } + end - let(:service_resource) { + 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]) } r - } + end - before { + before do user_resource.run_action(:create) # the service executable has to be outside the current user's home @@ -81,13 +81,13 @@ describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_ file.run_action(:create) manager.run(%w{--action install}) - } + end - after { + after do user_resource.run_action(:remove) manager.run(%w{--action uninstall}) File.delete(global_service_file_path) - } + end describe "logon as a service" do it "successfully runs a service as another user" do diff --git a/spec/functional/resource/windows_task_spec.rb b/spec/functional/resource/windows_task_spec.rb new file mode 100644 index 0000000000..621802bd44 --- /dev/null +++ b/spec/functional/resource/windows_task_spec.rb @@ -0,0 +1,1454 @@ +# +# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>) +# Copyright:: Copyright (c) 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 "chef/provider/windows_task" + +describe Chef::Resource::WindowsTask, :windows_only do + let(:task_name) { "chef-client" } + let(:new_resource) { Chef::Resource::WindowsTask.new(task_name) } + let(:windows_task_provider) do + node = Chef::Node.new + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + Chef::Provider::WindowsTask.new(new_resource, run_context) + end + + describe "action :create" do + after { delete_task } + 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 accespts 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("chef-client") + 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 accespts 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("chef-client") + 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("chef-client") + 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 accespts 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("chef-client") + 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 modifer 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 modifer 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("chef-client") + 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 accespts 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("chef-client") + 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 accespts 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("chef-client") + 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("chef-client") + 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("chef-client") + 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("chef-client") + 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("chef-client") + 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 accespts 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 seprated 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(ArgumentError) + 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(ArgumentError) + 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 thrid week of month" do + subject.frequency_modifier "first, second, third" + subject.day "Mon, Fri" + expect { subject.after_created }.not_to raise_error(ArgumentError) + 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("chef-client") + 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(ArgumentError) + 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("chef-client") + 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 accespts 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(ArgumentError) + 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("chef-client") + 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(ArgumentError) + 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("chef-client") + 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(ArgumentError) + 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("chef-client") + 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(ArgumentError) + 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("chef-client") + 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 accespts 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("chef-client") + 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("chef-client") + 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("chef-client") + 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("chef-client") + 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 accespts 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("chef-client") + 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 accespts 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("chef-client") + 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("chef-client") + 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("chef-client") + 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("chef-client") + 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("chef-client") + 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 accespts 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("chef-client") + 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("chef-client") + 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 accespts 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("chef-client") + 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("chef-client") + 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 accespts 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("chef-client") + 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("chef-client") + 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 accespts 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("chef-client") + 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 "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 accespts 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("chef-client") + 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 + 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 accespts 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 accespts 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 accespts 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 accespts 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 accespts 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 accespts 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 accespts 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 accespts 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 accespts this + new_resource + end + + context "when start_day is passed with frequency :onstart" do + it "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 "Administrator" + subject.frequency :onstart + expect { subject.after_created }.to raise_error(%q{Cannot specify a user other than the system users without specifying a password!. Valid passwordless users: 'NT AUTHORITY\SYSTEM', 'SYSTEM', 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', 'USERS'}) + end + end + + context "when interactive_enabled is passed for a System user without password" do + it "raises error" do + subject.interactive_enabled true + subject.frequency :onstart + expect { subject.after_created }.to raise_error("Please provide the password when attempting to set interactive/non-interactive.") + 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 accespts 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 + 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 + 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/yum_package_spec.rb b/spec/functional/resource/yum_package_spec.rb new file mode 100644 index 0000000000..7c45a64ae5 --- /dev/null +++ b/spec/functional/resource/yum_package_spec.rb @@ -0,0 +1,957 @@ +# +# Copyright:: Copyright 2016-2018, Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +require "functional/resource/base" +require "chef/mixin/shell_out" + +# run this test only for following platforms. +exclude_test = !(%w{rhel fedora}.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(: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}") + 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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 raises an error", not_rhel5: true do + preinstall("chef_rpm-1.10-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") + expect { yum_package.run_action(:install) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) + 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 + 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", not_rhel5: true 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 neeed 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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", not_rhel5: true 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 |