diff options
Diffstat (limited to 'spec')
41 files changed, 830 insertions, 303 deletions
diff --git a/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file b/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/spec/data/run_context/cookbooks/dependency1/attributes/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file b/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/spec/data/run_context/cookbooks/dependency1/definitions/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file b/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/spec/data/run_context/cookbooks/dependency1/libraries/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file b/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/spec/data/run_context/cookbooks/dependency1/providers/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file b/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/spec/data/run_context/cookbooks/dependency1/recipes/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file b/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file new file mode 100644 index 0000000000..60fee07cc6 --- /dev/null +++ b/spec/data/run_context/cookbooks/dependency1/resources/unparsed_file @@ -0,0 +1 @@ +raise "this should not be parsed by the loader" diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb index 4464b6ed69..d86a904098 100644 --- a/spec/functional/resource/link_spec.rb +++ b/spec/functional/resource/link_spec.rb @@ -417,11 +417,11 @@ describe Chef::Resource::Link do it_behaves_like "a securable resource without existing target" do let(:path) { target_file } - def allowed_acl(sid, expected_perms) + def allowed_acl(sid, expected_perms, _flags = 0) [ ACE.access_allowed(sid, expected_perms[:specific]) ] end - def denied_acl(sid, expected_perms) + def denied_acl(sid, expected_perms, _flags = 0) [ ACE.access_denied(sid, expected_perms[:specific]) ] end diff --git a/spec/functional/resource/windows_task_spec.rb b/spec/functional/resource/windows_task_spec.rb index b0c6998d77..fa51ad3f8a 100644 --- a/spec/functional/resource/windows_task_spec.rb +++ b/spec/functional/resource/windows_task_spec.rb @@ -18,6 +18,7 @@ require "spec_helper" require "chef/provider/windows_task" +require "chef/dist" describe Chef::Resource::WindowsTask, :windows_only do # resource.task.application_name will default to task_name unless resource.command is set @@ -45,37 +46,37 @@ describe Chef::Resource::WindowsTask, :windows_only do context "With Arguments" do it "creates scheduled task and sets command arguments" do - subject.command "chef-client -W" + subject.command "#{Chef::Dist::CLIENT} -W" call_for_create_action # loading current resource again to check new task is creted and it matches task parameters current_resource = call_for_load_current_resource expect(current_resource.exists).to eq(true) - expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.application_name).to eq(Chef::Dist::CLIENT) expect(current_resource.task.parameters).to eq("-W") end it "does not converge the resource if it is already converged" do - subject.command "chef-client -W" + subject.command "#{Chef::Dist::CLIENT} -W" subject.run_action(:create) - subject.command "chef-client -W" + subject.command "#{Chef::Dist::CLIENT} -W" subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end it "creates scheduled task and sets command arguments when arguments inclusive single quotes" do - subject.command "chef-client -W -L 'C:\\chef\\chef-ad-join.log'" + subject.command "#{Chef::Dist::CLIENT} -W -L 'C:\\chef\\chef-ad-join.log'" call_for_create_action # loading current resource again to check new task is creted and it matches task parameters current_resource = call_for_load_current_resource expect(current_resource.exists).to eq(true) - expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.application_name).to eq(Chef::Dist::CLIENT) expect(current_resource.task.parameters).to eq("-W -L 'C:\\chef\\chef-ad-join.log'") end it "does not converge the resource if it is already converged" do - subject.command "chef-client -W -L 'C:\\chef\\chef-ad-join.log'" + subject.command "#{Chef::Dist::CLIENT} -W -L 'C:\\chef\\chef-ad-join.log'" subject.run_action(:create) - subject.command "chef-client -W -L 'C:\\chef\\chef-ad-join.log'" + subject.command "#{Chef::Dist::CLIENT} -W -L 'C:\\chef\\chef-ad-join.log'" subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end @@ -135,19 +136,19 @@ describe Chef::Resource::WindowsTask, :windows_only do context "Without Arguments" do it "creates scheduled task and sets command arguments" do - subject.command "chef-client" + subject.command Chef::Dist::CLIENT call_for_create_action # loading current resource again to check new task is creted and it matches task parameters current_resource = call_for_load_current_resource expect(current_resource.exists).to eq(true) - expect(current_resource.task.application_name).to eq("chef-client") + expect(current_resource.task.application_name).to eq(Chef::Dist::CLIENT) expect(current_resource.task.parameters).to be_empty end it "does not converge the resource if it is already converged" do - subject.command "chef-client" + subject.command Chef::Dist::CLIENT subject.run_action(:create) - subject.command "chef-client" + subject.command Chef::Dist::CLIENT subject.run_action(:create) expect(subject).not_to be_updated_by_last_action end @@ -1283,6 +1284,57 @@ describe Chef::Resource::WindowsTask, :windows_only do expect(subject).not_to be_updated_by_last_action end end + + context "when start_when_available is passed" do + subject do + new_resource = Chef::Resource::WindowsTask.new(task_name, run_context) + new_resource.command task_name + new_resource.run_level :highest + new_resource.execution_time_limit = 259200 / 60 # converting "PT72H" into minutes and passing here since win32-taskscheduler accespts this + new_resource + end + + it "sets start_when_available to true" do + subject.frequency :minute + subject.start_when_available true + call_for_create_action + # loading current resource again to check new task is creted and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:start_when_available]).to eql(true) + end + + it "sets start_when_available to false" do + subject.frequency :minute + subject.start_when_available false + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:start_when_available]).to eql(false) + end + + it "sets the default if start_when_available is nil" do + subject.frequency :minute + subject.start_when_available nil + call_for_create_action + # loading current resource again to check new task is created and it matches task parameters + current_resource = call_for_load_current_resource + expect(current_resource.exists).to eq(true) + expect(current_resource.task.settings[:start_when_available]).to eql(false) + end + + it "does not converge the resource if it is already converged" do + subject.frequency :minute + subject.start_when_available true + subject.run_action(:create) + subject.frequency :minute + subject.start_when_available true + subject.disallow_start_if_on_batteries false + subject.run_action(:create) + expect(subject).not_to be_updated_by_last_action + end + end end context "task_name with parent folder" do diff --git a/spec/functional/shell_spec.rb b/spec/functional/shell_spec.rb index 3990f1afe0..dd0455fc9e 100644 --- a/spec/functional/shell_spec.rb +++ b/spec/functional/shell_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2012-2017, Chef Software Inc. +# Copyright:: Copyright 2012-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,8 +82,7 @@ describe Shell do require "pty" config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA) - path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__) - reader, writer, pid = PTY.spawn("#{path_to_chef_shell} -c #{config} #{options}") + reader, writer, pid = PTY.spawn("bundle exec chef-shell -c #{config} #{options}") read_until(reader, "chef (#{Chef::VERSION})>") yield reader, writer if block_given? writer.puts('"done"') diff --git a/spec/functional/version_spec.rb b/spec/functional/version_spec.rb index d968c36e8c..b12d235405 100644 --- a/spec/functional/version_spec.rb +++ b/spec/functional/version_spec.rb @@ -1,6 +1,6 @@ # # Author:: Serdar Sutay (<dan@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ describe "Chef Versions" do binaries.each do |binary| it "#{binary} version should be sane" do - expect(shell_out!("ruby #{File.join("bin", binary)} -v", cwd: chef_dir).stdout.chomp).to match(/.*: #{Chef::VERSION}/) + expect(shell_out!("bundle exec #{binary} -v", cwd: chef_dir).stdout.chomp).to match(/.*: #{Chef::VERSION}/) end end diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index 006839be3f..cde25662c1 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -45,10 +45,8 @@ describe "chef-client" do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } - let(:chef_solo) { "ruby '#{chef_dir}/chef-solo' --legacy-mode --minimal-ohai" } - - let(:critical_env_vars) { %w{_ORIGINAL_GEM_PATH GEM_PATH GEM_HOME GEM_ROOT BUNDLE_BIN_PATH BUNDLE_GEMFILE RUBYLIB RUBYOPT RUBY_ENGINE RUBY_ROOT RUBY_VERSION PATH}.map { |o| "#{o}=#{ENV[o]}" } .join(" ") } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } + let(:chef_solo) { "bundle exec chef-solo --legacy-mode --minimal-ohai" } when_the_repository "has a cookbook with a no-op recipe" do before { file "cookbooks/x/recipes/default.rb", "" } @@ -62,22 +60,6 @@ describe "chef-client" do shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", cwd: chef_dir) end - it "should complete successfully with no other environment variables", skip: (Chef::Platform.windows?) do - file "config/client.rb", <<~EOM - local_mode true - cookbook_path "#{path_to('cookbooks')}" - EOM - - begin - result = shell_out("env -i #{critical_env_vars} #{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", cwd: chef_dir) - result.error! - rescue - Chef::Log.info "Bare invocation will have the following load-path." - Chef::Log.info shell_out!("env -i #{critical_env_vars} ruby -e 'puts $:'").stdout - raise - end - end - it "should complete successfully with --no-listen" do file "config/client.rb", <<~EOM local_mode true diff --git a/spec/integration/client/exit_code_spec.rb b/spec/integration/client/exit_code_spec.rb index 2e29502070..6600a65c9f 100644 --- a/spec/integration/client/exit_code_spec.rb +++ b/spec/integration/client/exit_code_spec.rb @@ -21,7 +21,7 @@ describe "chef-client" do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --no-fork --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --no-fork --minimal-ohai" } let(:critical_env_vars) { %w{PATH RUBYOPT BUNDLE_GEMFILE GEM_PATH}.map { |o| "#{o}=#{ENV[o]}" } .join(" ") } diff --git a/spec/integration/client/ipv6_spec.rb b/spec/integration/client/ipv6_spec.rb index 04154c296f..b97eb4e8b4 100644 --- a/spec/integration/client/ipv6_spec.rb +++ b/spec/integration/client/ipv6_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -75,7 +75,7 @@ describe "chef-client" do let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") } - let(:chef_client_cmd) { %Q{ruby '#{chef_dir}/chef-client' --minimal-ohai -c "#{path_to('config/client.rb')}" -lwarn} } + let(:chef_client_cmd) { %Q{bundle exec chef-client --minimal-ohai -c "#{path_to('config/client.rb')}" -lwarn} } after do FileUtils.rm_rf(cache_path) diff --git a/spec/integration/recipes/accumulator_spec.rb b/spec/integration/recipes/accumulator_spec.rb index 65a05fcdc5..d19d8637bb 100644 --- a/spec/integration/recipes/accumulator_spec.rb +++ b/spec/integration/recipes/accumulator_spec.rb @@ -16,7 +16,7 @@ describe "Accumulators" do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } let(:aliases_temppath) do t = Tempfile.new("chef_accumulator_test") diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb index 2f4ef92f31..6bc857df48 100644 --- a/spec/integration/recipes/lwrp_inline_resources_spec.rb +++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb @@ -16,7 +16,7 @@ describe "LWRPs with inline resources" do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } context "with a use_inline_resources provider with 'def action_a' instead of action :a" do class LwrpInlineResourcesTest < Chef::Resource diff --git a/spec/integration/recipes/lwrp_spec.rb b/spec/integration/recipes/lwrp_spec.rb index b5af6978ac..ce2861d43b 100644 --- a/spec/integration/recipes/lwrp_spec.rb +++ b/spec/integration/recipes/lwrp_spec.rb @@ -16,7 +16,7 @@ describe "LWRPs" do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "has a cookbook named l-w-r-p" do before do diff --git a/spec/integration/recipes/notifies_spec.rb b/spec/integration/recipes/notifies_spec.rb index 0df7aa311f..860a109e4d 100644 --- a/spec/integration/recipes/notifies_spec.rb +++ b/spec/integration/recipes/notifies_spec.rb @@ -6,7 +6,7 @@ describe "notifications" do include Chef::Mixin::ShellOut let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "notifies a nameless resource" do before do diff --git a/spec/integration/recipes/notifying_block_spec.rb b/spec/integration/recipes/notifying_block_spec.rb index 6c50854038..753e81dadb 100644 --- a/spec/integration/recipes/notifying_block_spec.rb +++ b/spec/integration/recipes/notifying_block_spec.rb @@ -1,6 +1,6 @@ # # Author:: John Keiser (<jkeiser@chef.io>) -# Copyright:: Copyright 2013-2016, Chef Software Inc. +# Copyright:: Copyright 2013-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ describe "notifying_block" do include Chef::Mixin::ShellOut let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "notifying_block test one" do before do diff --git a/spec/integration/recipes/remote_directory.rb b/spec/integration/recipes/remote_directory.rb index a0e3e23ef3..6f67a38fc8 100644 --- a/spec/integration/recipes/remote_directory.rb +++ b/spec/integration/recipes/remote_directory.rb @@ -16,7 +16,7 @@ describe Chef::Resource::RemoteDirectory do # machine that has omnibus chef installed. In that case we need to ensure # we're running `chef-client` from the source tree and not the external one. # cf. CHEF-4914 - let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } + let(:chef_client) { "bundle exec chef-client --minimal-ohai" } when_the_repository "has a cookbook with a source_dir with two subdirectories, each with one file and subdir in a different alphabetical order" do before do diff --git a/spec/integration/solo/solo_spec.rb b/spec/integration/solo/solo_spec.rb index efd889c5d0..3d2efe703c 100644 --- a/spec/integration/solo/solo_spec.rb +++ b/spec/integration/solo/solo_spec.rb @@ -16,7 +16,7 @@ describe "chef-solo" do let(:cookbook_ancient_100_metadata_rb) { cb_metadata("ancient", "1.0.0") } - let(:chef_solo) { "ruby bin/chef-solo --legacy-mode --minimal-ohai" } + let(:chef_solo) { "bundle exec chef-solo --legacy-mode --minimal-ohai" } when_the_repository "creates nodes" do let(:nodes_dir) { File.join(@repository_dir, "nodes") } @@ -26,7 +26,7 @@ describe "chef-solo" do file "config/solo.rb", <<~EOM chef_repo_path "#{@repository_dir}" EOM - result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -l debug", cwd: chef_dir) + result = shell_out("bundle exec chef-solo -c \"#{path_to('config/solo.rb')}\" -l debug", cwd: chef_dir) result.error! end @@ -163,7 +163,7 @@ describe "chef-solo" do ruby_block "sleeping" do block do retries = 200 - while IO.read(Chef::Config[:log_location]) !~ /.* client .* is running, will wait for it to finish and then run./ + while IO.read(Chef::Config[:log_location]) !~ /.* is running, will wait for it to finish and then run./ sleep 0.1 raise "we ran out of retries" if ( retries -= 1 ) <= 0 end @@ -207,7 +207,7 @@ describe "chef-solo" do run_log = File.read(path_to("logs/runs.log")) # second run should have a message which indicates it's waiting for the first run - expect(run_log).to match(/.* client .* is running, will wait for it to finish and then run./) + expect(run_log).to match(/.* is running, will wait for it to finish and then run./) # both of the runs should succeed expect(run_log.lines.reject { |l| !l.include? "Run complete in" }.length).to eq(2) diff --git a/spec/support/shared/functional/directory_resource.rb b/spec/support/shared/functional/directory_resource.rb index 5e5e2bb360..4fb08479e6 100644 --- a/spec/support/shared/functional/directory_resource.rb +++ b/spec/support/shared/functional/directory_resource.rb @@ -65,18 +65,20 @@ shared_examples_for "a directory resource" do end # Set up the context for security tests - def allowed_acl(sid, expected_perms) - [ - ACE.access_allowed(sid, expected_perms[:specific]), - ACE.access_allowed(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE)), - ] + def allowed_acl(sid, expected_perms, flags = 0) + acl = [ ACE.access_allowed(sid, expected_perms[:specific], flags) ] + if expected_perms[:generic] + acl << ACE.access_allowed(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::SUBFOLDERS_AND_FILES_ONLY)) + end + acl end - def denied_acl(sid, expected_perms) - [ - ACE.access_denied(sid, expected_perms[:specific]), - ACE.access_denied(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE)), - ] + def denied_acl(sid, expected_perms, flags = 0) + acl = [ ACE.access_denied(sid, expected_perms[:specific], flags) ] + if expected_perms[:generic] + acl << ACE.access_denied(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::SUBFOLDERS_AND_FILES_ONLY)) + end + acl end def parent_inheritable_acls diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb index 8ae5db6a57..db947614b3 100644 --- a/spec/support/shared/functional/file_resource.rb +++ b/spec/support/shared/functional/file_resource.rb @@ -899,11 +899,11 @@ shared_examples_for "a configured file resource" do end # Set up the context for security tests - def allowed_acl(sid, expected_perms) + def allowed_acl(sid, expected_perms, _flags = 0) [ ACE.access_allowed(sid, expected_perms[:specific]) ] end - def denied_acl(sid, expected_perms) + def denied_acl(sid, expected_perms, _flags = 0) [ ACE.access_denied(sid, expected_perms[:specific]) ] end diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb index 2abae030c2..18e7243453 100644 --- a/spec/support/shared/functional/securable_resource.rb +++ b/spec/support/shared/functional/securable_resource.rb @@ -117,8 +117,7 @@ shared_context "use Windows permissions", :windows_only do let(:expected_write_perms) do { - generic: Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE, - specific: Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE, + specific: Chef::ReservedNames::Win32::API::Security::WRITE, } end @@ -136,6 +135,8 @@ shared_context "use Windows permissions", :windows_only do } end + let (:write_flag) { 3 } + RSpec::Matchers.define :have_expected_properties do |mask, type, flags| match do |ace| ace.mask == mask && @@ -363,78 +364,108 @@ shared_examples_for "a securable resource without existing target" do expect(descriptor.group).to eq(arbitrary_non_default_group) end - describe "with rights and deny_rights attributes" do - - it "correctly sets :read rights" do - resource.rights(:read, "Guest") - resource.run_action(:create) - expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_read_perms)) + describe "#allowed_acl" do + context "correctly sets" do + + it ":read rights" do + resource.rights(:read, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_read_perms)) + end + + it ":read_execute rights" do + resource.rights(:read_execute, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_read_execute_perms)) + end + + it ":write rights" do + resource.rights(:write, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_write_perms, write_flag)) + end + + it ":modify rights" do + resource.rights(:modify, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_modify_perms)) + end + + it ":full_control rights" do + resource.rights(:full_control, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_full_control_perms)) + end + + it "multiple rights" do + resource.rights(:read, "Everyone") + resource.rights(:modify, "Guest") + resource.run_action(:create) + + expect(explicit_aces).to eq( + allowed_acl(SID.Everyone, expected_read_perms) + + allowed_acl(SID.Guest, expected_modify_perms) + ) + end end + end - it "correctly sets :read_execute rights" do - resource.rights(:read_execute, "Guest") - resource.run_action(:create) - expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_read_execute_perms)) - end - - it "correctly sets :write rights" do - resource.rights(:write, "Guest") - resource.run_action(:create) - expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_write_perms)) - end - - it "correctly sets :modify rights" do - resource.rights(:modify, "Guest") - resource.run_action(:create) - expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_modify_perms)) - end - - it "correctly sets :full_control rights" do - resource.rights(:full_control, "Guest") - resource.run_action(:create) - expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_full_control_perms)) - end - - it "correctly sets deny_rights" do - # deny is an ACE with full rights, but is a deny type ace, not an allow type - resource.deny_rights(:full_control, "Guest") - resource.run_action(:create) - expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_full_control_perms)) - end - - it "Sets multiple rights" do - resource.rights(:read, "Everyone") - resource.rights(:modify, "Guest") - resource.run_action(:create) - - expect(explicit_aces).to eq( - allowed_acl(SID.Everyone, expected_read_perms) + - allowed_acl(SID.Guest, expected_modify_perms) - ) - end - - it "Sets deny_rights ahead of rights" do - resource.rights(:read, "Everyone") - resource.deny_rights(:modify, "Guest") - resource.run_action(:create) - - expect(explicit_aces).to eq( - denied_acl(SID.Guest, expected_modify_perms) + - allowed_acl(SID.Everyone, expected_read_perms) - ) - end - - it "Sets deny_rights ahead of rights when specified in reverse order" do - resource.deny_rights(:modify, "Guest") - resource.rights(:read, "Everyone") - resource.run_action(:create) - - expect(explicit_aces).to eq( - denied_acl(SID.Guest, expected_modify_perms) + - allowed_acl(SID.Everyone, expected_read_perms) - ) + describe "#denied_acl" do + context "correctly sets" do + + it ":read rights" do + resource.deny_rights(:read, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_read_perms)) + end + + it ":read_execute rights" do + resource.deny_rights(:read_execute, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_read_execute_perms)) + end + + it ":write rights" do + resource.deny_rights(:write, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_write_perms, write_flag)) + end + + it ":modify rights" do + resource.deny_rights(:modify, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_modify_perms)) + end + + it ":full_control rights" do + # deny is an ACE with full rights, but is a deny type ace, not an allow type + resource.deny_rights(:full_control, "Guest") + resource.run_action(:create) + expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_full_control_perms)) + end + + it "deny_rights ahead of rights" do + resource.rights(:read, "Everyone") + resource.deny_rights(:modify, "Guest") + resource.run_action(:create) + + expect(explicit_aces).to eq( + denied_acl(SID.Guest, expected_modify_perms) + + allowed_acl(SID.Everyone, expected_read_perms) + ) + end + + it "deny_rights ahead of rights when specified in reverse order" do + resource.deny_rights(:modify, "Guest") + resource.rights(:read, "Everyone") + resource.run_action(:create) + + expect(explicit_aces).to eq( + denied_acl(SID.Guest, expected_modify_perms) + + allowed_acl(SID.Everyone, expected_read_perms) + ) + end end - end context "with a mode attribute" do diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb index b6851f2d0e..5fc9de4de7 100644 --- a/spec/support/shared/integration/integration_helper.rb +++ b/spec/support/shared/integration/integration_helper.rb @@ -19,7 +19,6 @@ require "tmpdir" require "fileutils" -require "chef_core/text" require "chef/config" require "chef/json_compat" require "chef/server_api" diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb index a7c7af92f6..b3039f9be4 100644 --- a/spec/support/shared/unit/provider/file.rb +++ b/spec/support/shared/unit/provider/file.rb @@ -76,6 +76,7 @@ def setup_symlink allow(File).to receive(:directory?).with(path).and_return(false) allow(File).to receive(:writable?).with(path).and_return(true) allow(file_symlink_class).to receive(:symlink?).with(path).and_return(true) + allow(file_symlink_class).to receive(:realpath).with(path).and_return(path) end allow(File).to receive(:directory?).with(enclosing_directory).and_return(true) end diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index 8daba1d660..df1b6c99e2 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -326,7 +326,7 @@ describe Chef::Application::Client, "reconfigure" do Chef::Config[:interval] = 600 allow(ChefConfig).to receive(:windows?).and_return(false) expect(Chef::Application).to receive(:fatal!).with( - /Unforked .* interval runs are disabled in .* 12\. + /Unforked .* interval runs are disabled by default\. Configuration settings: interval = 600 seconds Enable .* interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options\./ diff --git a/spec/unit/application/exit_code_spec.rb b/spec/unit/application/exit_code_spec.rb index e8a0072ff3..6800ad0de5 100644 --- a/spec/unit/application/exit_code_spec.rb +++ b/spec/unit/application/exit_code_spec.rb @@ -70,7 +70,7 @@ describe Chef::Application::ExitCode do it "does write a warning on non-standard exit codes" do expect(Chef::Log).to receive(:warn).with( - /^Chef attempted to exit with a non-standard exit code of 151/) + /attempted to exit with a non-standard exit code of 151/) expect(exit_codes.normalize_exit_code(151)).to eq(1) end diff --git a/spec/unit/application/knife_spec.rb b/spec/unit/application/knife_spec.rb index 8a574b4d0f..f8f5560597 100644 --- a/spec/unit/application/knife_spec.rb +++ b/spec/unit/application/knife_spec.rb @@ -75,11 +75,7 @@ describe Chef::Application::Knife do expect(@knife).to receive(:exit).with(0) @knife.run end - if windows? - expect(Chef::Config[:color]).to be_truthy - else - expect(Chef::Config[:color]).to be_truthy - end + expect(Chef::Config[:color]).to be_truthy end context "when given fips flags" do diff --git a/spec/unit/application/solo_spec.rb b/spec/unit/application/solo_spec.rb index 1a4961f025..74f71a9115 100644 --- a/spec/unit/application/solo_spec.rb +++ b/spec/unit/application/solo_spec.rb @@ -64,7 +64,7 @@ describe Chef::Application::Solo do it "should terminate with message" do expect(Chef::Application).to receive(:fatal!).with( - /Unforked .* interval runs are disabled in .* 12\. + /Unforked .* interval runs are disabled by default\. Configuration settings: interval = 600 seconds Enable .* interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options\./ diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index e76e21bddc..10dff0e250 100644 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -87,6 +87,13 @@ describe Chef::Application do @app.run end + describe "when enforce_license is set to true" do + it "should check the license acceptance" do + expect(@app).to receive(:check_license_acceptance) + @app.run(enforce_license: true) + end + end + it "should run the actual application" do expect(@app).to receive(:run_application).and_return(true) @app.run diff --git a/spec/unit/knife/bootstrap/train_connector_spec.rb b/spec/unit/knife/bootstrap/train_connector_spec.rb new file mode 100644 index 0000000000..385a192648 --- /dev/null +++ b/spec/unit/knife/bootstrap/train_connector_spec.rb @@ -0,0 +1,216 @@ +# +# Copyright:: Copyright (c) 2019 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 "ostruct" +require "chef/knife/bootstrap/train_connector" + +describe Chef::Knife::Bootstrap::TrainConnector do + let(:protocol) { "mock" } + let(:family) { "unknown" } + let(:release) { "unknown" } # version + let(:name) { "unknown" } + let(:arch) { "x86_64" } + let(:connection_opts) { {} } # connection opts + let(:host_url) { "mock://user1@example.com" } + let(:mock_connection) { true } + + subject do + # Example groups can still override by setting explicitly it in 'connection_opts' + tc = Chef::Knife::Bootstrap::TrainConnector.new(host_url, protocol, connection_opts) + tc + end + + before(:each) do + if mock_connection + subject.connect! + subject.connection.mock_os( + family: family, + name: name, + release: release, + arch: arch + ) + end + end + + describe "platform helpers" do + context "on linux" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "reports that it is linux and unix, because that is how train classifies it" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq true + expect(subject.windows?).to eq false + end + end + context "on unix" do + let(:family) { "os" } + let(:name) { "mac_os_x" } + it "reports only a unix OS" do + expect(subject.unix?).to eq true + expect(subject.linux?).to eq false + expect(subject.windows?).to eq false + end + end + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "reports only a windows OS" do + expect(subject.unix?).to eq false + expect(subject.linux?).to eq false + expect(subject.windows?).to eq true + end + end + end + + describe "#connect!" do + it "establishes the connection to the remote host by waiting for it" do + expect(subject.connection).to receive(:wait_until_ready) + subject.connect! + end + end + + describe "#initialize" do + let(:mock_connection) { false } + + context "when provided target is a proper URL" do + let(:protocol) { "ssh" } + let(:host_url) { "mock://user1@localhost:2200" } + it "correctly configures the instance from the URL" do + expect(subject.config[:backend]).to eq "mock" + expect(subject.config[:port]).to eq 2200 + expect(subject.config[:host]).to eq "localhost" + expect(subject.config[:user]).to eq "user1" + end + + context "and conflicting options are given" do + let(:connection_opts) { { user: "user2", host: "example.com", port: 15 } } + it "resolves them from the URI" do + expect(subject.config[:backend]).to eq "mock" + expect(subject.config[:port]).to eq 2200 + expect(subject.config[:host]).to eq "localhost" + expect(subject.config[:user]).to eq "user1" + end + end + end + + context "when provided target is just a hostname" do + let(:host_url) { "localhost" } + let(:protocol) { "mock" } + it "correctly sets backend protocol from the default" do + expect(subject.config[:backend]).to eq "mock" + end + + context "and options have been provided that are supported by the transport" do + let(:protocol) { "ssh" } + let(:connection_opts) { { port: 15, user: "user2" } } + + it "sets hostname and transport from arguments and provided fields from options" do + expect(subject.config[:backend]).to eq "ssh" + expect(subject.config[:host]).to eq "localhost" + expect(subject.config[:user]).to eq "user2" + expect(subject.config[:port]).to eq 15 + end + + end + + end + + context "when provided target is just a an IP address" do + let(:host_url) { "127.0.0.1" } + let(:protocol) { "mock" } + it "correctly sets backend protocol from the default" do + expect(subject.config[:backend]).to eq "mock" + end + end + end + + describe "#temp_dir" do + context "under windows" do + let(:family) { "windows" } + let(:name) { "windows" } + + it "uses the windows command to create the temp dir" do + expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_WIN_COMMAND + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "C:/a/path") + expect(subject.temp_dir).to eq "C:/a/path" + end + + end + context "under linux and unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "uses the *nix command to create the temp dir and sets ownership to logged-in user" do + expected_command = Chef::Knife::Bootstrap::TrainConnector::MKTEMP_NIX_COMMAND + expect(subject).to receive(:run_command!).with(expected_command) + .and_return double("result", stdout: "/a/path") + expect(subject).to receive(:run_command!).with("chown user1 '/a/path'") + expect(subject.temp_dir).to eq "/a/path" + end + + end + end + context "#upload_file_content!" do + it "creates a local file with expected content and uploads it" do + expect(subject).to receive(:upload_file!) do |local_path, remote_path| + expect(File.read(local_path)).to eq "test data" + expect(remote_path).to eq "/target/path" + end + subject.upload_file_content!("test data", "/target/path") + end + end + + context "del_file" do + context "on windows" do + let(:family) { "windows" } + let(:name) { "windows" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/Test-Path "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + context "on unix-like" do + let(:family) { "debian" } + let(:name) { "ubuntu" } + it "deletes the file with a windows command" do + expect(subject).to receive(:run_command!) do |cmd, &_handler| + expect(cmd).to match(/rm -f "deleteme\.txt".*/) + end + subject.del_file!("deleteme.txt") + end + end + end + + context "#run_command!" do + it "raises a RemoteExecutionFailed when the remote execution failed" do + command_result = double("results", stdout: "", stderr: "failed", exit_status: 1) + expect(subject).to receive(:run_command).and_return command_result + + expect { subject.run_command!("test") }.to raise_error do |e| + expect(e.hostname).to eq subject.hostname + expect(e.class).to eq Chef::Knife::Bootstrap::RemoteExecutionFailed + expect(e.stderr).to eq "failed" + expect(e.stdout).to eq "" + expect(e.exit_status).to eq 1 + end + end + end + +end diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index f54c8ac1d6..b8141d496f 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -25,23 +25,34 @@ describe Chef::Knife::Bootstrap do let(:bootstrap_template) { nil } let(:stderr) { StringIO.new } let(:bootstrap_cli_options) { [ ] } - let(:base_os) { :linux } - let(:target_host) { double("TargetHost") } + let(:linux_test) { true } + let(:windows_test) { false } + let(:linux_test) { false } + let(:unix_test) { false } + let(:ssh_test) { false } + + let(:connection) do + double("TrainConnector", + windows?: windows_test, + linux?: linux_test, + unix?: unix_test) end let(:knife) do Chef::Log.logger = Logger.new(StringIO.new) Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil? + expect(LicenseAcceptance::Acceptor).to receive(:check_and_persist!) k = Chef::Knife::Bootstrap.new(bootstrap_cli_options) allow(k.ui).to receive(:stderr).and_return(stderr) allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) - allow(k).to receive(:target_host).and_return target_host + allow(k).to receive(:connection).and_return connection k.merge_configs k end - before do - allow(target_host).to receive(:base_os).and_return base_os + it "fails when LicenseAcceptance fails" do + expect(LicenseAcceptance::Acceptor).to receive(:check_and_persist!).and_raise("foo") + expect { k = Chef::Knife::Bootstrap.new(bootstrap_cli_options) }.to raise_error("foo") end context "#bootstrap_template" do @@ -318,9 +329,10 @@ describe Chef::Knife::Bootstrap do describe "specifying no_proxy with various entries" do subject(:knife) do + expect(LicenseAcceptance::Acceptor).to receive(:check_and_persist!) k = described_class.new Chef::Config[:knife][:bootstrap_template] = template_file - allow(k).to receive(:target_host).and_return target_host + allow(k).to receive(:connection).and_return connection k.parse_options(options) k.merge_configs k @@ -956,6 +968,7 @@ describe Chef::Knife::Bootstrap do sudo: false, verify_host_key: false, port: 9999, + non_interactive: true, } end @@ -1007,6 +1020,7 @@ describe Chef::Knife::Bootstrap do sudo: true, # ccli verify_host_key: false, # Config port: 12, # cli + non_interactive: true, } end @@ -1055,6 +1069,7 @@ describe Chef::Knife::Bootstrap do sudo_options: "-H", sudo_password: "blah", verify_host_key: true, + non_interactive: true, } end it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do @@ -1074,6 +1089,7 @@ describe Chef::Knife::Bootstrap do keys_only: false, sudo: false, verify_host_key: true, + non_interactive: true, } end it "populates appropriate defaults" do @@ -1425,13 +1441,13 @@ describe Chef::Knife::Bootstrap do before do knife.config[:ssh_forward_agent] = true end - it "returns a configuration hash with forward_agent set to true" do - expect(knife.ssh_opts).to eq({ forward_agent: true }) + it "returns a configuration hash with forward_agent set to true. non-interactive is always true" do + expect(knife.ssh_opts).to eq({ forward_agent: true, non_interactive: true }) end end context "when ssh_forward_agent is not set" do - it "returns a configuration hash with forward_agent set to false" do - expect(knife.ssh_opts).to eq({ forward_agent: false }) + it "returns a configuration hash with forward_agent set to false. non-interactive is always true" do + expect(knife.ssh_opts).to eq({ forward_agent: false, non_interactive: true }) end end end @@ -1566,7 +1582,7 @@ describe Chef::Knife::Bootstrap do end it "performs the steps we expect to run a bootstrap" do - expect(knife).to receive(:warn_and_map_deprecated_flags).ordered + expect(knife).to receive(:verify_deprecated_flags!).ordered expect(knife).to receive(:validate_name_args!).ordered expect(knife).to receive(:validate_protocol!).ordered expect(knife).to receive(:validate_first_boot_attributes!).ordered @@ -1578,7 +1594,7 @@ describe Chef::Knife::Bootstrap do expect(knife).to receive(:render_template).and_return "content" expect(knife).to receive(:upload_bootstrap).with("content").and_return "/remote/path.sh" expect(knife).to receive(:perform_bootstrap).with("/remote/path.sh") - expect(target_host).to receive(:del_file) # Make sure cleanup happens + expect(connection).to receive(:del_file!) # Make sure cleanup happens knife.run @@ -1588,33 +1604,35 @@ describe Chef::Knife::Bootstrap do end end - describe "#warn_and_map_deprecated_flags" do + describe "#verify_deprecated_flags!" do before do Chef::Config[:silence_deprecation_warnings] = false end context "when a deprecated CLI flag is given on the CLI" do - before do - knife.config[:ssh_user] = "sshuser" - knife.merge_configs - end + let(:bootstrap_cli_options) { %w{--ssh-user sshuser} } it "maps the key value to the new key and points the human to the new flag" do - expect(knife.ui).to receive(:warn).with(/--ssh-user USER is deprecated. Use --connection-user USERNAME instead./) - knife.warn_and_map_deprecated_flags + expect(knife.ui).to receive(:warn).with(/You provided --ssh-user. This flag is deprecated. Please use '--connection-user USERNAME' instead./) + knife.verify_deprecated_flags! expect(knife.config[:connection_user]).to eq "sshuser" end end context "when a deprecated CLI flag is given on the CLI, along with its replacement" do - before do - knife.config[:ssh_user] = "sshuser" - knife.config[:connection_user] = "real-user" - knife.merge_configs + let(:bootstrap_cli_options) { %w{--connection-user a --ssh-user b} } + + it "informs the human that both are provided and exits" do + expect(knife.ui).to receive(:error).with(/You provided both --connection-user and --ssh-user.*Please use.*/m) + expect { knife.verify_deprecated_flags! }.to raise_error SystemExit end - it "warns that both are provided and takes the non-deprecated value" do - expect(knife.ui).to receive(:warn).with(/You provided both --connection-user and --ssh-user.*'--connection-user real-user'/m) - knife.warn_and_map_deprecated_flags - expect(knife.config[:connection_user]).to eq "real-user" + end + + context "when a deprecated boolean CLI flag is given on the CLI, and its non-boolean replacement is used" do + let(:bootstrap_cli_options) { %w{--prerelease} } + it "correctly maps the old boolean value to the new value" do + expect(knife.ui).to receive(:warn) + knife.verify_deprecated_flags! + expect(knife.config[:channel]).to eq "current" end end end @@ -1687,14 +1705,14 @@ describe Chef::Knife::Bootstrap do let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message") } before do - allow(target_host).to receive(:hostname).and_return "testhost" + allow(connection).to receive(:hostname).and_return "testhost" end it "runs the remote script and logs the output" do expect(knife.ui).to receive(:info).with(/Bootstrapping.*/) expect(knife).to receive(:bootstrap_command) .with("/path.sh") .and_return("sh /path.sh") - expect(target_host) + expect(connection) .to receive(:run_command) .with("sh /path.sh") .and_yield("output here") @@ -1710,7 +1728,7 @@ describe Chef::Knife::Bootstrap do expect(knife).to receive(:bootstrap_command) .with("/path.sh") .and_return("sh /path.sh") - expect(target_host).to receive(:run_command).with("sh /path.sh").and_return result_mock + expect(connection).to receive(:run_command).with("sh /path.sh").and_return result_mock expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit) end end @@ -1738,13 +1756,11 @@ describe Chef::Knife::Bootstrap do context "when an auth failure occurs" do let(:expected_error) do - # TODO This is awkward and ugly. Requires some refactor of chef_core/error - # to make it not so. See comment in rescue block of connect! for details. - e = RuntimeError.new - interim = RuntimeError.new + e = Train::Error.new actual = Net::SSH::AuthenticationFailed.new - allow(interim).to receive(:cause).and_return(actual) - allow(e).to receive(:cause).and_return(interim) + # Simulate train's nested error - they wrap + # ssh/network errors in a TrainError. + allow(e).to receive(:cause).and_return(actual) e end @@ -1754,7 +1770,7 @@ describe Chef::Knife::Bootstrap do context "and password auth was used" do before do - knife.config[:connection_password] = "tryme" + allow(connection).to receive(:password_auth?).and_return true end it "re-raises the error so as not to resubmit the same failing password" do @@ -1765,8 +1781,8 @@ describe Chef::Knife::Bootstrap do context "and password auth was not used" do before do - knife.config.delete :connection_password - allow(target_host).to receive(:user).and_return "testuser" + allow(connection).to receive(:password_auth?).and_return false + allow(connection).to receive(:user).and_return "testuser" end it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password" do @@ -1793,7 +1809,7 @@ describe Chef::Knife::Bootstrap do describe "#bootstrap_context" do context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } it "creates a WindowsBootstrapContext" do require "chef/knife/core/windows_bootstrap_context" expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::WindowsBootstrapContext @@ -1801,7 +1817,7 @@ describe Chef::Knife::Bootstrap do end context "under linux" do - let(:base_os) { :linux } + let(:linux_test) { true } it "creates a BootstrapContext" do require "chef/knife/core/bootstrap_context" expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::BootstrapContext @@ -1841,25 +1857,25 @@ describe Chef::Knife::Bootstrap do describe "#upload_bootstrap" do before do - allow(target_host).to receive(:temp_dir).and_return(temp_dir) - allow(target_host).to receive(:normalize_path) { |a| a } + allow(connection).to receive(:temp_dir).and_return(temp_dir) + allow(connection).to receive(:normalize_path) { |a| a } end let(:content) { "bootstrap script content" } context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } let(:temp_dir) { "C:/Temp/bootstrap" } - it "creates a bat file in the temp dir provided by target_host, using given content" do - expect(target_host).to receive(:save_as_remote_file).with(content, "C:/Temp/bootstrap/bootstrap.bat") + it "creates a bat file in the temp dir provided by connection, using given content" do + expect(connection).to receive(:upload_file_content!).with(content, "C:/Temp/bootstrap/bootstrap.bat") expect(knife.upload_bootstrap(content)).to eq "C:/Temp/bootstrap/bootstrap.bat" end end context "under Linux" do - let(:base_os) { :linux } + let(:linux_test) { true } let(:temp_dir) { "/tmp/bootstrap" } - it "creates a 'sh file in the temp dir provided by target_host, using given content" do - expect(target_host).to receive(:save_as_remote_file).with(content, "/tmp/bootstrap/bootstrap.sh") + it "creates a 'sh file in the temp dir provided by connection, using given content" do + expect(connection).to receive(:upload_file_content!).with(content, "/tmp/bootstrap/bootstrap.sh") expect(knife.upload_bootstrap(content)).to eq "/tmp/bootstrap/bootstrap.sh" end end @@ -1867,14 +1883,14 @@ describe Chef::Knife::Bootstrap do describe "#bootstrap_command" do context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } it "prefixes the command to run under cmd.exe" do expect(knife.bootstrap_command("autoexec.bat")).to eq "cmd.exe /C autoexec.bat" end end context "under Linux" do - let(:base_os) { :linux } + let(:linux_test) { true } it "prefixes the command to run under sh" do expect(knife.bootstrap_command("bootstrap")).to eq "sh bootstrap" end @@ -1883,14 +1899,14 @@ describe Chef::Knife::Bootstrap do describe "#default_bootstrap_template" do context "under Windows" do - let(:base_os) { :windows } + let(:windows_test) { true } it "is windows-chef-client-msi" do expect(knife.default_bootstrap_template).to eq "windows-chef-client-msi" end end context "under Linux" do - let(:base_os) { :linux } + let(:linux_test) { true } it "is chef-full" do expect(knife.default_bootstrap_template).to eq "chef-full" end @@ -1899,15 +1915,15 @@ describe Chef::Knife::Bootstrap do describe "#do_connect" do let(:host_descriptor) { "example.com" } - let(:target_host) { double("TargetHost") } - let(:resolver_mock) { double("TargetResolver", targets: [ target_host ]) } + let(:connection) { double("TrainConnector") } + let(:connector_mock) { double("TargetResolver", targets: [ connection ]) } before do allow(knife).to receive(:host_descriptor).and_return host_descriptor end - it "resolves the target and connects it" do - expect(ChefCore::TargetResolver).to receive(:new).and_return resolver_mock - expect(target_host).to receive(:connect!) + it "creates a TrainConnector and connects it" do + expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return connection + expect(connection).to receive(:connect!) knife.do_connect({}) end end diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb index 17d18d084e..fe25dccfbf 100644 --- a/spec/unit/knife/client_create_spec.rb +++ b/spec/unit/knife/client_create_spec.rb @@ -28,7 +28,6 @@ describe Chef::Knife::ClientCreate do { "name" => "adam", "validator" => false, - "admin" => false, } end @@ -102,14 +101,9 @@ describe Chef::Knife::ClientCreate do expect(client.name).to eq("adam") end - it "by default it is not an admin" do - knife.run - expect(client.admin).to be_falsey - end - it "by default it is not a validator" do knife.run - expect(client.admin).to be_falsey + expect(client.validator).to be_falsey end it "by default it should set create_key to true" do @@ -136,17 +130,6 @@ describe Chef::Knife::ClientCreate do end end - describe "with -a or --admin" do - before do - knife.config[:admin] = true - end - - it "should create an admin client" do - knife.run - expect(client.admin).to be_truthy - end - end - describe "with -p or --public-key" do before do knife.config[:public_key] = "some_key" diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 7b12177ab9..d57e254793 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -46,21 +46,21 @@ describe Chef::Knife::Core::BootstrapContext do expect { described_class.new(config, run_list, chef_config) }.not_to raise_error end - it "runs chef with the first-boot.json with no environment specified" do - expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json" + it "runs chef with the first-boot.json with no environment other than chef-license acceptance specified" do + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept chef-client -j /etc/chef/first-boot.json" end describe "when in verbosity mode" do let(:config) { { verbosity: 2, color: true } } it "adds '-l debug' when verbosity is >= 2" do - expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug" + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept chef-client -j /etc/chef/first-boot.json -l debug" end end describe "when no color value has been set in config" do let(:config) { { color: false } } it "adds '--no-color' when color is false" do - expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json --no-color" + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept chef-client -j /etc/chef/first-boot.json --no-color" end end @@ -82,7 +82,7 @@ describe Chef::Knife::Core::BootstrapContext do describe "alternate chef-client path" do let(:chef_config) { { chef_client_path: "/usr/local/bin/chef-client" } } it "runs chef-client from another path when specified" do - expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json" + expect(bootstrap_context.start_chef).to eq "CHEF_LICENSE=accept /usr/local/bin/chef-client -j /etc/chef/first-boot.json" end end @@ -105,7 +105,7 @@ describe Chef::Knife::Core::BootstrapContext do describe "when bootstrapping into a specific environment" do let(:config) { { environment: "prodtastic", color: true } } it "starts chef in the configured environment" do - expect(bootstrap_context.start_chef).to eq("chef-client -j /etc/chef/first-boot.json -E prodtastic") + expect(bootstrap_context.start_chef).to eq("CHEF_LICENSE=accept chef-client -j /etc/chef/first-boot.json -E prodtastic") end end @@ -156,25 +156,6 @@ describe Chef::Knife::Core::BootstrapContext do end end - describe "when a bootstrap_version is specified" do - let(:chef_config) do - { - knife: { bootstrap_version: "11.12.4" }, - } - end - - it "should send the full version to the installer" do - expect(bootstrap_context.latest_current_chef_version_string).to eq("-v 11.12.4") - end - end - - describe "when a bootstrap_version is not specified" do - it "should send the latest current to the installer" do - # Intentionally hard coded in order not to replicate the logic. - expect(bootstrap_context.latest_current_chef_version_string).to eq("-v #{Chef::VERSION.to_i}") - end - end - describe "ssl_verify_mode" do it "isn't set in the config_content by default" do expect(bootstrap_context.config_content).not_to include("ssl_verify_mode") @@ -292,4 +273,28 @@ describe Chef::Knife::Core::BootstrapContext do end end + + describe "#version_to_install" do + context "when bootstrap_version is provided" do + let(:chef_config) { { knife: { bootstrap_version: "awesome" } } } + + it "returns bootstrap_version" do + expect(bootstrap_context.version_to_install).to eq "awesome" + end + end + + context "when bootstrap_version is not provided" do + let(:config) { { channel: "stable" } } + it "returns the currently running major version out of Chef::VERSION" do + expect(bootstrap_context.version_to_install).to eq Chef::VERSION.split(".").first + end + end + + context "and channel is other than stable" do + let(:config) { { channel: "unstable" } } + it "returns the version string 'latest'" do + expect(bootstrap_context.version_to_install).to eq "latest" + end + end + end end diff --git a/spec/unit/knife/core/windows_bootstrap_context_spec.rb b/spec/unit/knife/core/windows_bootstrap_context_spec.rb index 3ab173f316..561e43eefc 100644 --- a/spec/unit/knife/core/windows_bootstrap_context_spec.rb +++ b/spec/unit/knife/core/windows_bootstrap_context_spec.rb @@ -176,29 +176,30 @@ describe Chef::Knife::Core::WindowsBootstrapContext do end end - describe "latest_current_windows_chef_version_query" do - it "returns the major version of the current version of Chef" do - stub_const("Chef::VERSION", "15.1.2") - expect(bootstrap_context.latest_current_windows_chef_version_query).to eq("&v=15") - end - - end - describe "msi_url" do - context "when config option is not set" do + context "when msi_url config option is not set" do + let(:config) { { channel: "stable" } } before do - expect(bootstrap_context).to receive(:latest_current_windows_chef_version_query).and_return("&v=something") + expect(bootstrap_context).to receive(:version_to_install).and_return("something") end it "returns a chef.io msi url with minimal url parameters" do - reference_url = "https://www.chef.io/chef/download?p=windows&v=something" + reference_url = "https://www.chef.io/chef/download?p=windows&channel=stable&v=something" expect(bootstrap_context.msi_url).to eq(reference_url) end it "returns a chef.io msi url with provided url parameters substituted" do - reference_url = "https://www.chef.io/chef/download?p=windows&pv=machine&m=arch&DownloadContext=ctx&v=something" + reference_url = "https://www.chef.io/chef/download?p=windows&pv=machine&m=arch&DownloadContext=ctx&channel=stable&v=something" expect(bootstrap_context.msi_url("machine", "arch", "ctx")).to eq(reference_url) end + + context "when a channel is provided in config" do + let(:config) { { channel: "current" } } + it "returns a chef.io msi url with the requested channel" do + reference_url = "https://www.chef.io/chef/download?p=windows&channel=current&v=something" + expect(bootstrap_context.msi_url).to eq(reference_url) + end + end end context "when msi_url config option is set" do @@ -220,6 +221,8 @@ describe Chef::Knife::Core::WindowsBootstrapContext do let(:bootstrap) { Chef::Knife::Bootstrap.new(["--bootstrap-install-command", "chef-client"]) } before do bootstrap.config[:bootstrap_install_command] = "chef-client" + @old_env = ENV["CHEF_LICENSE"] + ENV["CHEF_LICENSE"] = "accept" end it "sets the bootstrap_install_command option under Chef::Config::Knife object" do @@ -229,6 +232,7 @@ describe Chef::Knife::Core::WindowsBootstrapContext do after do bootstrap.config.delete(:bootstrap_install_command) Chef::Config[:knife].delete(:bootstrap_install_command) + ENV["CHEF_LICENSE"] = @old_env end end @@ -245,6 +249,8 @@ describe Chef::Knife::Core::WindowsBootstrapContext do let(:bootstrap) { Chef::Knife::Bootstrap.new(["--bootstrap-install-command", "chef-client"]) } before do bootstrap.config[:bootstrap_install_command] = "chef-client" + @old_env = ENV["CHEF_LICENSE"] + ENV["CHEF_LICENSE"] = "accept" end it "sets the bootstrap_install_command option under Chef::Config::Knife object" do @@ -254,6 +260,7 @@ describe Chef::Knife::Core::WindowsBootstrapContext do after do bootstrap.config.delete(:bootstrap_install_command) Chef::Config[:knife].delete(:bootstrap_install_command) + ENV["CHEF_LICENSE"] = @old_env end end @@ -264,4 +271,12 @@ describe Chef::Knife::Core::WindowsBootstrapContext do end end end + + describe "#start_chef" do + it "the command includes the license acceptance environment variable" do + expect(bootstrap_context.start_chef).to match(/SET "CHEF_LICENSE=accept"\n/m) + end + + end + end diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb index f48ab4019c..6fcb831531 100644 --- a/spec/unit/knife_spec.rb +++ b/spec/unit/knife_spec.rb @@ -299,37 +299,26 @@ describe Chef::Knife do expect(Chef::Config[:log_level]).to eql(:warn) end - it "prefers the default value if no config or command line value is present and reports the source as default" do + it "prefers the default value from option definition if no config or command line value is present and reports the source as default" do knife_command = KnifeSpecs::TestYourself.new([]) # empty argv knife_command.configure_chef expect(knife_command.config[:opt_with_default]).to eq("default-value") + expect(knife_command.config_source(:opt_with_default)).to eq(:cli_default) end - it "prefers a value in Chef::Config[:knife] to the default" do + it "prefers a value in Chef::Config[:knife] to the default and reports the source as config" do Chef::Config[:knife][:opt_with_default] = "from-knife-config" knife_command = KnifeSpecs::TestYourself.new([]) # empty argv knife_command.configure_chef expect(knife_command.config[:opt_with_default]).to eq("from-knife-config") - expect(knife_command.config_source(:opt_with_default)).to eq (:config) - end - - it "correctly reports Chef::Config as the source when a a config entry comes from there" do - Chef::Config[:knife][:opt_with_default] = "from-knife-config" - knife_command = KnifeSpecs::TestYourself.new([]) # empty argv - knife_command.configure_chef - expect(knife_command.config_source(:opt_with_default)).to eq (:config) + expect(knife_command.config_source(:opt_with_default)).to eq(:config) end it "prefers a value from command line over Chef::Config and the default and reports the source as CLI" do knife_command = KnifeSpecs::TestYourself.new(["-D", "from-cli"]) knife_command.configure_chef expect(knife_command.config[:opt_with_default]).to eq("from-cli") - expect(knife_command.config_source(:opt_with_default)).to eq (:cli) - end - it "correctly reports CLI as the source when a config entry comes from the CLI" do - knife_command = KnifeSpecs::TestYourself.new(["-D", "from-cli"]) - knife_command.configure_chef - expect(knife_command.config_source(:opt_with_default)).to eq (:cli) + expect(knife_command.config_source(:opt_with_default)).to eq(:cli) end it "merges `listen` config to Chef::Config" do @@ -472,7 +461,7 @@ describe Chef::Knife do allow(knife).to receive(:username).and_return("sadpanda") knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action}) - expect(stderr.string).to match(%r{ERROR: There are proxy servers configured, your .* server may need to be added to NO_PROXY.}) + expect(stderr.string).to match(%r{ERROR: There are proxy servers configured, your server url may need to be added to NO_PROXY.}) expect(stderr.string).to match(%r{Response: y u no administrator}) end end @@ -508,9 +497,9 @@ describe Chef::Knife do allow(knife).to receive(:run).and_raise(Net::HTTPClientException.new("406 Not Acceptable", response)) knife.run_with_pretty_exceptions - expect(stderr.string).to match(/The request that .* sent was using API version 10000000/) - expect(stderr.string).to match(/The .* server you sent the request to supports a min API verson of 0 and a max API version of 1/) - expect(stderr.string).to match(/Please either update your .* client or server to be a compatible set/) + expect(stderr.string).to match(/The request that .* sent was using API version 10000000./) + expect(stderr.string).to match(/The server you sent the request to supports a min API verson of 0 and a max API version of 1./) + expect(stderr.string).to match(/Please either update your .* or the server to be a compatible set./) end it "formats 500s nicely" do @@ -588,7 +577,7 @@ describe Chef::Knife do expected_message = <<~MSG ERROR: Could not establish a secure connection to the server. Use `.* ssl check` to troubleshoot your SSL configuration. - If your .* Server uses a self-signed certificate, you can use + If your server uses a self-signed certificate, you can use `.* ssl fetch` to make .* trust the server's certificates. MSG expect(stderr.string).to match(expected_message) diff --git a/spec/unit/node_map_spec.rb b/spec/unit/node_map_spec.rb index 9c161f3893..7c867857dc 100644 --- a/spec/unit/node_map_spec.rb +++ b/spec/unit/node_map_spec.rb @@ -145,14 +145,14 @@ describe Chef::NodeMap do describe "deleting classes" do it "deletes a class and removes the mapping completely" do node_map.set(:thing, Bar) - expect( node_map.delete_class(Bar) ).to include({ thing: [{ klass: Bar, cookbook_override: false, core_override: false }] }) + expect( node_map.delete_class(Bar) ).to include({ thing: [{ klass: Bar, cookbook_override: false, core_override: false, target_mode: nil }] }) expect( node_map.get(node, :thing) ).to eql(nil) end it "deletes a class and leaves the mapping that still has an entry" do node_map.set(:thing, Bar) node_map.set(:thing, Foo) - expect( node_map.delete_class(Bar) ).to eql({ thing: [{ klass: Bar, cookbook_override: false, core_override: false }] }) + expect( node_map.delete_class(Bar) ).to eql({ thing: [{ klass: Bar, cookbook_override: false, core_override: false, target_mode: nil }] }) expect( node_map.get(node, :thing) ).to eql(Foo) end @@ -160,7 +160,7 @@ describe Chef::NodeMap do node_map.set(:thing1, Bar) node_map.set(:thing2, Bar) node_map.set(:thing2, Foo) - expect( node_map.delete_class(Bar) ).to eql({ thing1: [{ klass: Bar, cookbook_override: false, core_override: false }], thing2: [{ klass: Bar, cookbook_override: false, core_override: false }] }) + expect( node_map.delete_class(Bar) ).to eql({ thing1: [{ klass: Bar, cookbook_override: false, core_override: false, target_mode: nil }], thing2: [{ klass: Bar, cookbook_override: false, core_override: false, target_mode: nil }] }) expect( node_map.get(node, :thing1) ).to eql(nil) expect( node_map.get(node, :thing2) ).to eql(Foo) end @@ -210,6 +210,40 @@ describe Chef::NodeMap do end end + # When in target mode, only match when target_mode is explicitly supported + context "when target mode is enabled" do + before do + allow(Chef::Config).to receive(:target_mode?).and_return(true) + end + + it "returns the value when target_mode matches" do + node_map.set(:something, :network, target_mode: true) + expect(node_map.get(node, :something)).to eql(:network) + end + + it "returns nil when target_mode does not match" do + node_map.set(:something, :local, target_mode: false) + expect(node_map.get(node, :something)).to eql(nil) + end + end + + # When not in target mode, match regardless of target_mode filter + context "when target mode is not enabled" do + before do + allow(Chef::Config).to receive(:target_mode?).and_return(false) + end + + it "returns the value if target_mode matches" do + node_map.set(:something, :local, target_mode: true) + expect(node_map.get(node, :something)).to eql(:local) + end + + it "returns the value if target_mode does not match" do + node_map.set(:something, :local, target_mode: false) + expect(node_map.get(node, :something)).to eql(:local) + end + end + describe "locked mode" do context "while unlocked" do it "allows setting the same key twice" do diff --git a/spec/unit/provider/cron_spec.rb b/spec/unit/provider/cron_spec.rb index 98c525762b..6bfc18c359 100644 --- a/spec/unit/provider/cron_spec.rb +++ b/spec/unit/provider/cron_spec.rb @@ -690,33 +690,136 @@ describe Chef::Provider::Cron do end context "when there is a crontab with a matching and identical section" do - before :each do - @provider.cron_exists = true - allow(@provider).to receive(:cron_different?).and_return(false) - allow(@provider).to receive(:read_crontab).and_return(<<~CRONTAB) - 0 2 * * * /some/other/command + context "when environment variable is not used" do + before :each do + @provider.cron_exists = true + allow(@provider).to receive(:cron_different?).and_return(false) + allow(@provider).to receive(:read_crontab).and_return(<<~CRONTAB) + 0 2 * * * /some/other/command - # Chef Name: cronhole some stuff - * 5 * * * /bin/true + # Chef Name: cronhole some stuff + SHELL=/bash + * 5 * * * /bin/true - # Another comment - CRONTAB - end + # Another comment + CRONTAB + end - it "should not update the crontab" do - expect(@provider).not_to receive(:write_crontab) - @provider.run_action(:create) + it "should not update the crontab" do + expect(@provider).not_to receive(:write_crontab) + @provider.run_action(:create) + end + + it "should not mark the resource as updated" do + @provider.run_action(:create) + expect(@new_resource).not_to be_updated_by_last_action + end + + it "should log nothing changed" do + expect(logger).to receive(:trace).with("Found cron '#{@new_resource.name}'") + expect(logger).to receive(:trace).with("Skipping existing cron entry '#{@new_resource.name}'") + @provider.run_action(:create) + end end - it "should not mark the resource as updated" do - @provider.run_action(:create) - expect(@new_resource).not_to be_updated_by_last_action + context "when environment variable is used" do + before :each do + @provider.cron_exists = true + allow(@provider).to receive(:read_crontab).and_return(<<~CRONTAB) + 0 2 * * * /some/other/command + + # Chef Name: cronhole some stuff + SHELL=/bash + ENV=environment + 30 * * * * /bin/true + + # Another comment + CRONTAB + end + context "contains an entry that can also be specified as a `property`" do + before :each do + @new_resource.environment = { "SHELL" => "/bash", "ENV" => "environment" } + end + + it "should raise a warning for idempotency" do + expect(logger).to receive(:warn).with("cronhole some stuff: the environment property contains the 'SHELL' variable, which should be set separately as a property.") + @provider.run_action(:create) + end + + it "should not update the crontab" do + expect(@provider).not_to receive(:write_crontab) + @provider.run_action(:create) + end + + it "should not mark the resource as updated" do + expect(@new_resource).not_to be_updated_by_last_action + @provider.run_action(:create) + end + end + + context "contains an entry that cannot be specified as a `property`" do + before :each do + @new_resource.environment = { "ENV" => "environment" } + @new_resource.shell "/bash" + end + + it "should not raise a warning for idempotency" do + expect(logger).not_to receive(:warn).with("cronhole some stuff: the environment property contains the 'SHELL' variable, which should be set separately as a property.") + @provider.run_action(:create) + end + + it "should not update the crontab" do + expect(@provider).not_to receive(:write_crontab) + @provider.run_action(:create) + end + + it "should not mark the resource as updated" do + @provider.run_action(:create) + expect(@new_resource).not_to be_updated_by_last_action + end + end end - it "should log nothing changed" do - expect(logger).to receive(:trace).with("Found cron '#{@new_resource.name}'") - expect(logger).to receive(:trace).with("Skipping existing cron entry '#{@new_resource.name}'") - @provider.run_action(:create) + context "when environment variable is used with property" do + before :each do + @provider.cron_exists = true + allow(@provider).to receive(:read_crontab).and_return(<<~CRONTAB) + 0 2 * * * /some/other/command + + # Chef Name: cronhole some stuff + SHELL=/bash + ENV=environment + 30 * * * * /bin/true + + # Another comment + CRONTAB + end + + context "when environment variable is same as property" do + it "should throw an error" do + @new_resource.shell "/bash" + @new_resource.environment "SHELL" => "/bash" + expect do + @provider.run_action(:create) + end.to raise_error(Chef::Exceptions::Cron, /cronhole some stuff: the 'SHELL' property is set and environment property also contains the 'SHELL' variable. Remove the variable from the environment property./) + end + end + + context "when environment variable is different from property" do + it "should not update the crontab" do + @new_resource.shell "/bash" + @new_resource.environment "ENV" => "environment" + expect(@provider).not_to receive(:write_crontab) + @provider.run_action(:create) + end + + it "should not mark the resource as updated" do + @new_resource.shell "/bash" + @new_resource.environment "ENV" => "environment" + @provider.run_action(:create) + expect(@new_resource).not_to be_updated_by_last_action + end + end end end end diff --git a/spec/unit/resource/windows_task_spec.rb b/spec/unit/resource/windows_task_spec.rb index 96482d3d56..b152d879f6 100644 --- a/spec/unit/resource/windows_task_spec.rb +++ b/spec/unit/resource/windows_task_spec.rb @@ -61,6 +61,10 @@ describe Chef::Resource::WindowsTask, :windows_only do expect(resource.stop_if_going_on_batteries).to eql(false) end + it "sets the default value for start_when_available as false" do + expect(resource.start_when_available).to eql(false) + end + context "when frequency is not provided" do it "raises ArgumentError to provide frequency" do expect { resource.after_created }.to raise_error(ArgumentError, "Frequency needs to be provided. Valid frequencies are :minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle, :none." ) diff --git a/spec/unit/run_context/cookbook_compiler_spec.rb b/spec/unit/run_context/cookbook_compiler_spec.rb index c3a4c1b98f..3c585cf444 100644 --- a/spec/unit/run_context/cookbook_compiler_spec.rb +++ b/spec/unit/run_context/cookbook_compiler_spec.rb @@ -1,6 +1,6 @@ # # Author:: Daniel DeLeo (<dan@chef.io>) -# Copyright:: Copyright 2012-2018, Chef Software Inc. +# Copyright:: Copyright 2012-2019, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,6 +51,14 @@ describe Chef::RunContext::CookbookCompiler do Chef::RunContext::CookbookCompiler.new(run_context, run_list_expansion, events) end + describe "loading a cookbook fully" do + it "does not error" do + run_context.instance_variable_set(:@cookbook_compiler, compiler) + node.run_list("dependency1::default") + compiler.compile + end + end + describe "loading attribute files" do # Attribute files in the fixture data will append their diff --git a/spec/unit/train_transport_spec.rb b/spec/unit/train_transport_spec.rb new file mode 100644 index 0000000000..b56c7e1104 --- /dev/null +++ b/spec/unit/train_transport_spec.rb @@ -0,0 +1,79 @@ +# +# Author:: Bryan McLellan (<btm@loftninjas.org>) +# Copyright:: Copyright 2019, 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::TrainTransport do + describe "load_credentials" do + let(:transport) { Chef::TrainTransport.new } + let(:good_credentials) { { "switch.cisco.com" => { "user" => "cisco", "password" => "cisco", "enable_password" => "secret" } } } + + before do + allow(Chef::TrainTransport).to receive(:parse_credentials_file).and_return(good_credentials) + end + + it "matches credentials when they exist" do + expect(Chef::TrainTransport.load_credentials("switch.cisco.com")[:user]).to eq("cisco") + expect(Chef::TrainTransport.load_credentials("switch.cisco.com")[:password]).to eq("cisco") + expect(Chef::TrainTransport.load_credentials("switch.cisco.com")[:enable_password]).to eq("secret") + end + + it "returns nil if there is no match" do + expect(Chef::TrainTransport.load_credentials("router.unicorns.com")).to be_nil + end + + # [foo.example.org] => {"foo"=>{"example"=>{"org"=>{}}}} + # ['foo.example.org'] => {"foo.example.org"=>{}} + it "warns if the host has been split by toml" do + allow(Chef::TrainTransport).to receive(:parse_credentials_file).and_return({ "foo" => { "example" => { "org" => {} } } }) + expect(Chef::Log).to receive(:warn).with(/as a Hash/) + expect(Chef::Log).to receive(:warn).with(/Hostnames must be surrounded by single quotes/) + expect(Chef::TrainTransport.load_credentials("foo.example.org")).to be_nil + end + end + + describe "credentials_file_path" do + + context "when a file path is specified by a config" do + let(:cred_file_path) { "/somewhere/credentials" } + + before do + tm_config = double("Config Context", host: "foo.example.org", credentials_file: cred_file_path) + allow(Chef::Config).to receive(:target_mode).and_return(tm_config) + end + + it "returns the path if it exists" do + allow(File).to receive(:exists?).with(cred_file_path).and_return(true) + expect(Chef::TrainTransport.credentials_file_path).to eq(cred_file_path) + end + + it "raises an error if it does not exist" do + allow(File).to receive(:exists?).with(cred_file_path).and_return(false) + expect { Chef::TrainTransport.credentials_file_path }.to raise_error(ArgumentError, /does not exist/) + end + end + + it "returns the path to the default config file if it exists" do + cred_file_path = Chef::Config.platform_specific_path("/etc/chef/foo.example.org/credentials") + tm_config = double("Config Context", host: "foo.example.org", credentials_file: nil) + allow(Chef::Config).to receive(:target_mode).and_return(tm_config) + allow(File).to receive(:exists?).with(cred_file_path).and_return(true) + expect(Chef::TrainTransport.credentials_file_path).to eq(cred_file_path) + end + end +end |