summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/functional/dsl/reboot_pending_spec.rb7
-rw-r--r--spec/functional/provider/whyrun_safe_ruby_block_spec.rb51
-rw-r--r--spec/functional/rebooter_spec.rb105
-rw-r--r--spec/functional/resource/dsc_script_spec.rb337
-rw-r--r--spec/functional/resource/file_spec.rb29
-rw-r--r--spec/functional/resource/link_spec.rb32
-rw-r--r--spec/functional/resource/reboot_spec.rb103
-rw-r--r--spec/functional/resource/user/dscl_spec.rb3
-rw-r--r--spec/functional/util/path_helper_spec.rb37
-rw-r--r--spec/functional/util/powershell/cmdlet_spec.rb114
-rw-r--r--spec/integration/client/client_spec.rb2
-rw-r--r--spec/integration/client/ipv6_spec.rb2
-rw-r--r--spec/integration/knife/cookbook_api_ipv6_spec.rb2
-rw-r--r--spec/integration/recipes/lwrp_inline_resources_spec.rb2
-rw-r--r--spec/spec_helper.rb3
-rw-r--r--spec/support/platform_helpers.rb24
-rw-r--r--spec/support/shared/functional/file_resource.rb6
-rw-r--r--spec/support/shared/integration/integration_helper.rb5
-rw-r--r--spec/support/shared/matchers.rb17
-rw-r--r--spec/support/shared/matchers/exit_with_code.rb28
-rw-r--r--spec/support/shared/matchers/match_environment_variable.rb17
-rw-r--r--spec/unit/application/apply.rb12
-rw-r--r--spec/unit/config_spec.rb4
-rw-r--r--spec/unit/dsl/data_query_spec.rb118
-rw-r--r--spec/unit/dsl/reboot_pending_spec.rb8
-rw-r--r--spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb95
-rw-r--r--spec/unit/knife/bootstrap_spec.rb78
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb28
-rw-r--r--spec/unit/knife/data_bag_create_spec.rb134
-rw-r--r--spec/unit/knife/data_bag_edit_spec.rb132
-rw-r--r--spec/unit/knife/data_bag_from_file_spec.rb229
-rw-r--r--spec/unit/knife/data_bag_secret_options_spec.rb165
-rw-r--r--spec/unit/knife/data_bag_show_spec.rb146
-rw-r--r--spec/unit/knife/environment_from_file_spec.rb4
-rw-r--r--spec/unit/knife/ssl_check_spec.rb47
-rw-r--r--spec/unit/mixin/homebrew_owner_spec.rb65
-rw-r--r--spec/unit/platform/query_helpers_spec.rb23
-rw-r--r--spec/unit/provider/dsc_script_spec.rb145
-rw-r--r--spec/unit/provider/git_spec.rb4
-rw-r--r--spec/unit/provider/ifconfig/debian_spec.rb44
-rw-r--r--spec/unit/provider/ifconfig/redhat_spec.rb30
-rw-r--r--spec/unit/provider/ifconfig_spec.rb6
-rw-r--r--spec/unit/provider/link_spec.rb10
-rw-r--r--spec/unit/provider/package/homebrew_spec.rb251
-rw-r--r--spec/unit/provider/whyrun_safe_ruby_block_spec.rb4
-rw-r--r--spec/unit/resource/dsc_script_spec.rb127
-rw-r--r--spec/unit/resource/homebrew_package_spec.rb36
-rw-r--r--spec/unit/run_context_spec.rb15
-rw-r--r--spec/unit/util/dsc/configuration_generator_spec.rb171
-rw-r--r--spec/unit/util/dsc/lcm_output_parser_spec.rb169
-rw-r--r--spec/unit/util/dsc/local_configuration_manager_spec.rb134
-rw-r--r--spec/unit/util/path_helper_spec.rb24
-rw-r--r--spec/unit/util/powershell/cmdlet_spec.rb106
-rw-r--r--spec/unit/workstation_config_loader_spec.rb12
54 files changed, 2913 insertions, 589 deletions
diff --git a/spec/functional/dsl/reboot_pending_spec.rb b/spec/functional/dsl/reboot_pending_spec.rb
index 10d667f7bd..114754ccba 100644
--- a/spec/functional/dsl/reboot_pending_spec.rb
+++ b/spec/functional/dsl/reboot_pending_spec.rb
@@ -46,8 +46,11 @@ describe Chef::DSL::RebootPending, :windows_only do
describe "reboot_pending?" do
- context "when there is nothing to indicate a reboot is pending" do
- it { expect(recipe.reboot_pending?).to be_false }
+ describe "when there is nothing to indicate a reboot is pending" do
+ it "should return false" do
+ pending "Found existing registry keys" unless registry_safe?
+ expect(recipe.reboot_pending?).to be_false
+ end
end
describe 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations' do
diff --git a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
new file mode 100644
index 0000000000..150d46d384
--- /dev/null
+++ b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Serdar Sutay (<serdar@opscode.com>)
+# Copyright:: Copyright (c) 2014 Opscode, 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::WhyrunSafeRubyBlock do
+ let(:node) { Chef::Node.new }
+
+ let(:run_context) {
+ events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, events)
+ }
+
+ before do
+ $evil_global_evil_laugh = :wahwah
+ Chef::Config[:why_run] = true
+ end
+
+ after do
+ Chef::Config[:why_run] = false
+ end
+
+ describe "when testing the resource" do
+ let(:new_resource) do
+ r = Chef::Resource::WhyrunSafeRubyBlock.new("reload all", run_context)
+ r.block { $evil_global_evil_laugh = :mwahahaha }
+ r
+ end
+
+ it "updates the evil laugh, even in why-run mode" do
+ new_resource.run_action(new_resource.action)
+ $evil_global_evil_laugh.should == :mwahahaha
+ new_resource.should be_updated
+ end
+ end
+end
diff --git a/spec/functional/rebooter_spec.rb b/spec/functional/rebooter_spec.rb
new file mode 100644
index 0000000000..8006580d5c
--- /dev/null
+++ b/spec/functional/rebooter_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Chris Doherty <cdoherty@getchef.com>)
+# Copyright:: Copyright (c) 2014 Chef, 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::Platform::Rebooter do
+
+ let(:reboot_info) do
+ {
+ :delay_mins => 5,
+ :requested_by => "reboot resource functional test",
+ :reason => "rebooter spec test"
+ }
+ end
+
+ def create_resource
+ resource = Chef::Resource::Reboot.new(expected[:requested_by], run_context)
+ resource.delay_mins(expected[:delay_mins])
+ resource.reason(expected[:reason])
+ resource
+ end
+
+ let(:run_context) do
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, events)
+ end
+
+ let(:expected) do
+ {
+ :windows => 'shutdown /r /t 5 /c "rebooter spec test"',
+ :linux => 'shutdown -r +5 "rebooter spec test"'
+ }
+ end
+
+ let(:rebooter) { Chef::Platform::Rebooter }
+
+ describe '#reboot_if_needed!' do
+
+ it 'should not call #shell_out! when reboot has not been requested' do
+ expect(rebooter).to receive(:shell_out!).exactly(0).times
+ expect(rebooter).to receive(:reboot_if_needed!).once.and_call_original
+ rebooter.reboot_if_needed!(run_context.node)
+ end
+
+ describe 'calling #shell_out! to reboot' do
+
+ before(:each) do
+ run_context.request_reboot(reboot_info)
+ end
+
+ after(:each) do
+ run_context.cancel_reboot
+ end
+
+ shared_context 'test a reboot method' do
+ def test_rebooter_method(method_sym, is_windows, expected_reboot_str)
+ Chef::Platform.stub(:windows?).and_return(is_windows)
+ expect(rebooter).to receive(:shell_out!).once.with(expected_reboot_str)
+ expect(rebooter).to receive(method_sym).once.and_call_original
+ rebooter.send(method_sym, run_context.node)
+ end
+ end
+
+ describe 'when using #reboot_if_needed!' do
+ include_context 'test a reboot method'
+
+ it 'should produce the correct string on Windows' do
+ test_rebooter_method(:reboot_if_needed!, true, expected[:windows])
+ end
+
+ it 'should produce the correct (Linux-specific) string on non-Windows' do
+ test_rebooter_method(:reboot_if_needed!, false, expected[:linux])
+ end
+ end
+
+ describe 'when using #reboot!' do
+ include_context 'test a reboot method'
+
+ it 'should produce the correct string on Windows' do
+ test_rebooter_method(:reboot!, true, expected[:windows])
+ end
+
+ it 'should produce the correct (Linux-specific) string on non-Windows' do
+ test_rebooter_method(:reboot!, false, expected[:linux])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb
new file mode 100644
index 0000000000..fa13296c02
--- /dev/null
+++ b/spec/functional/resource/dsc_script_spec.rb
@@ -0,0 +1,337 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/windows_architecture_helper'
+
+describe Chef::Resource::DscScript, :windows_powershell_dsc_only do
+ include Chef::Mixin::WindowsArchitectureHelper
+ before(:all) do
+ @temp_dir = ::Dir.mktmpdir("dsc-functional-test")
+ end
+
+ after(:all) do
+ ::FileUtils.rm_rf(@temp_dir) if ::Dir.exist?(@temp_dir)
+ end
+
+ include Chef::Mixin::ShellOut
+
+ def create_config_script_from_code(code, configuration_name, data = false)
+ script_code = data ? code : "Configuration '#{configuration_name}'\n{\n\t#{code}\n}\n"
+ data_suffix = data ? '_config_data' : ''
+ extension = data ? 'psd1' : 'ps1'
+ script_path = "#{@temp_dir}/dsc_functional_test#{data_suffix}.#{extension}"
+ ::File.open(script_path, 'wt') do | script |
+ script.write(script_code)
+ end
+ script_path
+ end
+
+ def user_exists?(target_user)
+ result = false
+ begin
+ shell_out!("net user #{target_user}")
+ result = true
+ rescue Mixlib::ShellOut::ShellCommandFailed
+ end
+ result
+ end
+
+ def delete_user(target_user)
+ begin
+ shell_out!("net user #{target_user} /delete")
+ rescue Mixlib::ShellOut::ShellCommandFailed
+ end
+ end
+
+ let(:dsc_env_variable) { 'chefenvtest' }
+ let(:dsc_env_value1) { 'value1' }
+ let(:env_value2) { 'value2' }
+ let(:dsc_test_run_context) {
+ node = Chef::Node.new
+ node.automatic['platform'] = 'windows'
+ node.automatic['platform_version'] = '6.1'
+ node.automatic['kernel'][:machine] =
+ is_i386_process_on_x86_64_windows? ? :x86_64 : :i386
+ node.automatic[:languages][:powershell][:version] = '4.0'
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, empty_events)
+ }
+ let(:dsc_test_resource_name) { 'DSCTest' }
+ let(:dsc_test_resource_base) {
+ Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
+ }
+ 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' }
+ let(:test_registry_data2) { 'LL928' }
+ let(:dsc_code) { <<-EOH
+ Registry "ChefRegKey"
+ {
+ Key = '#{test_registry_key}'
+ ValueName = '#{test_registry_value}'
+ ValueData = '#{test_registry_data}'
+ Ensure = 'Present'
+ }
+EOH
+ }
+
+ let(:dsc_user_prefix) { 'dsc' }
+ let(:dsc_user_suffix) { 'chefx' }
+ let(:dsc_user) {"#{dsc_user_prefix}_usr_#{dsc_user_suffix}" }
+ let(:dsc_user_prefix_env_var_name) { 'dsc_user_env_prefix' }
+ let(:dsc_user_suffix_env_var_name) { 'dsc_user_env_suffix' }
+ let(:dsc_user_prefix_env_code) { "$env:#{dsc_user_prefix_env_var_name}"}
+ let(:dsc_user_suffix_env_code) { "$env:#{dsc_user_suffix_env_var_name}"}
+ let(:dsc_user_prefix_param_name) { 'dsc_user_prefix_param' }
+ let(:dsc_user_suffix_param_name) { 'dsc_user_suffix_param' }
+ let(:dsc_user_prefix_param_code) { "$#{dsc_user_prefix_param_name}"}
+ let(:dsc_user_suffix_param_code) { "$#{dsc_user_suffix_param_name}"}
+ let(:dsc_user_env_code) { "\"$(#{dsc_user_prefix_env_code})_usr_$(#{dsc_user_suffix_env_code})\""}
+ let(:dsc_user_param_code) { "\"$(#{dsc_user_prefix_param_code})_usr_$(#{dsc_user_suffix_param_code})\""}
+
+ let(:config_flags) { nil }
+ let(:config_params) { <<-EOH
+
+ [CmdletBinding()]
+ param
+ (
+ $#{dsc_user_prefix_param_name},
+ $#{dsc_user_suffix_param_name}
+ )
+EOH
+ }
+
+ 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
+ #{config_param_section}
+node localhost
+{
+$testuser = #{dsc_user_code}
+$testpassword = ConvertTo-SecureString -String "jf9a8m49jrajf4#" -AsPlainText -Force
+$testcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $testuser, $testpassword
+
+User dsctestusercreate
+{
+ UserName = $testuser
+ Password = $testcred
+ Description = "DSC test user"
+ Ensure = "Present"
+ Disabled = $false
+ PasswordNeverExpires = $true
+ PasswordChangeRequired = $false
+}
+}
+EOH
+ }
+
+ let(:dsc_user_config_data) {
+<<-EOH
+@{
+ AllNodes = @(
+ @{
+ NodeName = "localhost";
+ PSDscAllowPlainTextPassword = $true
+ }
+ )
+}
+
+EOH
+ }
+
+ 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
+if (($pwd.path -eq '#{dsc_environment_fail_etc_directory}') -and (test-path('#{dsc_environment_fail_etc_directory}')))
+{
+ throw 'Signature #{exception_message_signature}: Purposefully failing because cwd == #{dsc_environment_fail_etc_directory}'
+}
+environment "whatsmydir"
+{
+ Name = '#{dsc_environment_env_var_name}'
+ Value = $pwd.path
+ Ensure = 'Present'
+}
+EOH
+ }
+
+ let(:dsc_config_name) {
+ dsc_test_resource_base.name
+ }
+ let(:dsc_resource_from_code) {
+ dsc_test_resource_base.code(dsc_code)
+ dsc_test_resource_base
+ }
+ let(:config_name_value) { dsc_test_resource_base.name }
+
+ let(:dsc_resource_from_path) {
+ dsc_test_resource_base.command(create_config_script_from_code(dsc_code, config_name_value))
+ dsc_test_resource_base
+ }
+
+ before(:each) do
+ test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context)
+ test_key_resource.recursive(true)
+ test_key_resource.run_action(:delete_key)
+ end
+
+ after(:each) do
+ test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context)
+ test_key_resource.recursive(true)
+ test_key_resource.run_action(:delete_key)
+ end
+
+ shared_examples_for 'a dsc_script resource with specified PowerShell configuration code' do
+ let(:test_registry_data) { test_registry_data1 }
+ it 'should create a registry key with a specific registry value and data' do
+ expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(false)
+ dsc_test_resource.run_action(:run)
+ expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(true)
+ expect(dsc_test_resource.registry_value_exists?(test_registry_key, {:name => test_registry_value, :type => :string, :data => test_registry_data})).to eq(true)
+ end
+
+ it_should_behave_like 'a dsc_script resource with configuration affected by cwd'
+ end
+
+ shared_examples_for 'a dsc_script resource with configuration affected by cwd' do
+ after(:each) do
+ removal_resource = Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
+ removal_resource.code <<-EOH
+environment 'removethis'
+{
+ Name = '#{dsc_environment_env_var_name}'
+ Ensure = 'Absent'
+}
+EOH
+ removal_resource.run_action(:run)
+ end
+ let(:dsc_code) { dsc_environment_config }
+ it 'should not raise an exception if the cwd is not etc' do
+ dsc_test_resource.cwd(dsc_environment_no_fail_not_etc_directory)
+ expect {dsc_test_resource.run_action(:run)}.not_to raise_error
+ end
+
+ it 'should raise an exception if the cwd is etc' do
+ dsc_test_resource.cwd(dsc_environment_fail_etc_directory)
+ expect {dsc_test_resource.run_action(:run)}.to raise_error(Chef::Exceptions::PowershellCmdletException)
+ begin
+ dsc_test_resource.run_action(:run)
+ rescue Chef::Exceptions::PowershellCmdletException => e
+ expect(e.message).to match(exception_message_signature)
+ end
+ end
+ end
+
+ shared_examples_for 'a parameterized DSC configuration script' do
+ context 'when specifying environment variables in the environment attribute' do
+ let(:dsc_user_prefix_code) { dsc_user_prefix_env_code }
+ let(:dsc_user_suffix_code) { dsc_user_suffix_env_code }
+ it_behaves_like 'a dsc_script with configuration that uses environment variables'
+ end
+ end
+
+ shared_examples_for 'a dsc_script with configuration data' do
+ context 'when using the configuration_data attribute' do
+ let(:configuration_data_attribute) { 'configuration_data' }
+ it_behaves_like 'a dsc_script with configuration data set via an attribute'
+ end
+
+ context 'when using the configuration_data_script attribute' do
+ let(:configuration_data_attribute) { 'configuration_data_script' }
+ it_behaves_like 'a dsc_script with configuration data set via an attribute'
+ end
+ end
+
+ shared_examples_for 'a dsc_script with configuration data set via an attribute' do
+ it 'should run a configuration script that creates a user' do
+ config_data_value = dsc_user_config_data
+ dsc_test_resource.configuration_name(config_name_value)
+ if configuration_data_attribute == 'configuration_data_script'
+ config_data_value = create_config_script_from_code(dsc_user_config_data, '', true)
+ end
+ dsc_test_resource.environment({dsc_user_prefix_env_var_name => dsc_user_prefix,
+ dsc_user_suffix_env_var_name => dsc_user_suffix})
+ dsc_test_resource.send(configuration_data_attribute, config_data_value)
+ dsc_test_resource.flags(config_flags)
+ expect(user_exists?(dsc_user)).to eq(false)
+ expect {dsc_test_resource.run_action(:run)}.not_to raise_error
+ expect(user_exists?(dsc_user)).to eq(true)
+ end
+ end
+
+ shared_examples_for 'a dsc_script with configuration data that takes parameters' do
+ context 'when script code takes parameters for configuration' do
+ let(:dsc_user_code) { dsc_user_param_code }
+ let(:config_param_section) { config_params }
+ let(:config_flags) {{:"#{dsc_user_prefix_param_name}" => "#{dsc_user_prefix}", :"#{dsc_user_suffix_param_name}" => "#{dsc_user_suffix}"}}
+ it 'does not directly contain the user name' do
+ configuration_script_content = ::File.open(dsc_test_resource.command) do | file |
+ file.read
+ end
+ expect(configuration_script_content.include?(dsc_user)).to be(false)
+ end
+ it_behaves_like 'a dsc_script with configuration data'
+ end
+
+ end
+
+ shared_examples_for 'a dsc_script with configuration data that uses environment variables' do
+ context 'when script code uses environment variables' do
+ let(:dsc_user_code) { dsc_user_env_code }
+
+ it 'does not directly contain the user name' do
+ configuration_script_content = ::File.open(dsc_test_resource.command) do | file |
+ file.read
+ end
+ expect(configuration_script_content.include?(dsc_user)).to be(false)
+ end
+ it_behaves_like 'a dsc_script with configuration data'
+ end
+ end
+
+ context 'when supplying configuration through the configuration attribute' do
+ let(:dsc_test_resource) { dsc_resource_from_code }
+ it_behaves_like 'a dsc_script resource with specified PowerShell configuration code'
+ end
+
+ context 'when supplying configuration using the path attribute' do
+ let(:dsc_test_resource) { dsc_resource_from_path }
+ it_behaves_like 'a dsc_script resource with specified PowerShell configuration code'
+ end
+
+ context 'when running a configuration that manages users' do
+ before(:each) do
+ delete_user(dsc_user)
+ end
+
+ let(:dsc_code) { dsc_user_resources_code }
+ let(:config_name_value) { 'DSCTestConfig' }
+ let(:dsc_test_resource) { dsc_resource_from_path }
+
+ it_behaves_like 'a dsc_script with configuration data'
+ it_behaves_like 'a dsc_script with configuration data that uses environment variables'
+ it_behaves_like 'a dsc_script with configuration data that takes parameters'
+ end
+end
diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb
index d6f56db3e9..99966f85c8 100644
--- a/spec/functional/resource/file_spec.rb
+++ b/spec/functional/resource/file_spec.rb
@@ -24,12 +24,18 @@ describe Chef::Resource::File do
let(:file_base) { "file_spec" }
let(:expected_content) { "Don't fear the ruby." }
- def create_resource
+ def create_resource(opts={})
events = Chef::EventDispatch::Dispatcher.new
node = Chef::Node.new
run_context = Chef::RunContext.new(node, {}, events)
- resource = Chef::Resource::File.new(path, run_context)
- resource
+
+ use_path = if opts[:use_relative_path]
+ File.basename(path)
+ else
+ path
+ end
+
+ Chef::Resource::File.new(use_path, run_context)
end
let(:resource) do
@@ -42,6 +48,10 @@ describe Chef::Resource::File do
create_resource
end
+ let(:resource_with_relative_path) do
+ create_resource(:use_relative_path => true)
+ end
+
let(:unmanaged_content) do
"This is file content that is not managed by chef"
end
@@ -74,6 +84,19 @@ describe Chef::Resource::File do
end
end
+ # github issue 1842.
+ describe "when running action :create on a relative path" do
+ before do
+ resource_with_relative_path.run_action(:create)
+ end
+
+ context "and the file exists" do
+ it "should run without an exception" do
+ resource_with_relative_path.run_action(:create)
+ end
+ end
+ end
+
describe "when running action :touch" do
context "and the target file does not exist" do
before do
diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb
index 8e630d84f2..2220e973cf 100644
--- a/spec/functional/resource/link_spec.rb
+++ b/spec/functional/resource/link_spec.rb
@@ -72,8 +72,8 @@ describe Chef::Resource::Link do
end
end
- def canonicalize(path)
- windows? ? path.gsub('/', '\\') : path
+ def paths_eql?(path1, path2)
+ Chef::Util::PathHelper.paths_eql?(path1, path2)
end
def symlink(a, b)
@@ -180,7 +180,7 @@ describe Chef::Resource::Link do
it 'links to the target file' do
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(to)
+ paths_eql?(readlink(target_file), to).should be_true
end
it 'marks the resource updated' do
resource.should be_updated
@@ -201,7 +201,7 @@ describe Chef::Resource::Link do
it 'leaves the file linked' do
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(to)
+ paths_eql?(readlink(target_file), to).should be_true
end
it 'does not mark the resource updated' do
resource.should_not be_updated
@@ -279,7 +279,7 @@ describe Chef::Resource::Link do
before(:each) do
symlink(to, target_file)
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(to)
+ paths_eql?(readlink(target_file), to).should be_true
end
include_context 'create symbolic link is noop'
include_context 'delete succeeds'
@@ -294,7 +294,7 @@ describe Chef::Resource::Link do
File.open(@other_target, 'w') { |file| file.write('eek') }
symlink(@other_target, target_file)
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(@other_target)
+ paths_eql?(readlink(target_file), @other_target).should be_true
end
after(:each) do
File.delete(@other_target)
@@ -311,7 +311,7 @@ describe Chef::Resource::Link do
nonexistent = File.join(test_file_dir, make_tmpname('nonexistent_spec'))
symlink(nonexistent, target_file)
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(nonexistent)
+ paths_eql?(readlink(target_file), nonexistent).should be_true
end
include_context 'create symbolic link succeeds'
include_context 'delete succeeds'
@@ -393,7 +393,7 @@ describe Chef::Resource::Link do
File.open(@other_target, "w") { |file| file.write("eek") }
symlink(@other_target, to)
symlink?(to).should be_true
- readlink(to).should == canonicalize(@other_target)
+ paths_eql?(readlink(to), @other_target).should be_true
end
after(:each) do
File.delete(@other_target)
@@ -408,7 +408,7 @@ describe Chef::Resource::Link do
@other_target = File.join(test_file_dir, make_tmpname("other_spec"))
symlink(@other_target, to)
symlink?(to).should be_true
- readlink(to).should == canonicalize(@other_target)
+ paths_eql?(readlink(to), @other_target).should be_true
end
context 'and the link does not yet exist' do
include_context 'create symbolic link succeeds'
@@ -441,7 +441,7 @@ describe Chef::Resource::Link do
before(:each) do
symlink(to, target_file)
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(to)
+ paths_eql?(readlink(target_file), to).should be_true
end
include_context 'create symbolic link is noop'
include_context 'delete succeeds'
@@ -450,7 +450,7 @@ describe Chef::Resource::Link do
before(:each) do
symlink(absolute_to, target_file)
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(absolute_to)
+ paths_eql?(readlink(target_file), absolute_to).should be_true
end
include_context 'create symbolic link succeeds'
include_context 'delete succeeds'
@@ -478,7 +478,7 @@ describe Chef::Resource::Link do
before(:each) do
symlink(to, target_file)
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(to)
+ paths_eql?(readlink(target_file), to).should be_true
end
include_context 'create hard link succeeds'
it_behaves_like 'delete errors out'
@@ -552,7 +552,7 @@ describe Chef::Resource::Link do
File.open(@other_target, "w") { |file| file.write("eek") }
symlink(@other_target, to)
symlink?(to).should be_true
- readlink(to).should == canonicalize(@other_target)
+ paths_eql?(readlink(to), @other_target).should be_true
end
after(:each) do
File.delete(@other_target)
@@ -564,7 +564,7 @@ describe Chef::Resource::Link do
# OS X gets angry about this sort of link. Bug in OS X, IMO.
pending('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks', :if => (os_x? or freebsd? or aix?)) do
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(@other_target)
+ paths_eql?(readlink(target_file), @other_target).should be_true
end
end
include_context 'delete is noop'
@@ -575,7 +575,7 @@ describe Chef::Resource::Link do
@other_target = File.join(test_file_dir, make_tmpname("other_spec"))
symlink(@other_target, to)
symlink?(to).should be_true
- readlink(to).should == canonicalize(@other_target)
+ paths_eql?(readlink(to), @other_target).should be_true
end
context 'and the link does not yet exist' do
it 'links to the target file' do
@@ -588,7 +588,7 @@ describe Chef::Resource::Link do
File.exists?(target_file).should be_false
end
symlink?(target_file).should be_true
- readlink(target_file).should == canonicalize(@other_target)
+ paths_eql?(readlink(target_file), @other_target).should be_true
end
end
include_context 'delete is noop'
diff --git a/spec/functional/resource/reboot_spec.rb b/spec/functional/resource/reboot_spec.rb
new file mode 100644
index 0000000000..735ca994c8
--- /dev/null
+++ b/spec/functional/resource/reboot_spec.rb
@@ -0,0 +1,103 @@
+#
+# Author:: Chris Doherty <cdoherty@getchef.com>)
+# Copyright:: Copyright (c) 2014 Chef, 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::Reboot do
+
+ let(:expected) do
+ {
+ :delay_mins => 5,
+ :requested_by => "reboot resource functional test",
+ :reason => "reboot resource spec test"
+ }
+ end
+
+ def create_resource
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ resource = Chef::Resource::Reboot.new(expected[:requested_by], run_context)
+ resource.delay_mins(expected[:delay_mins])
+ resource.reason(expected[:reason])
+ resource
+ end
+
+ let(:resource) do
+ create_resource
+ end
+
+ shared_context 'testing run context modification' do
+ def test_reboot_action(resource)
+ reboot_info = resource.run_context.reboot_info
+ expect(reboot_info.keys.sort).to eq([:delay_mins, :reason, :requested_by, :timestamp])
+ expect(reboot_info[:delay_mins]).to eq(expected[:delay_mins])
+ expect(reboot_info[:reason]).to eq(expected[:reason])
+ expect(reboot_info[:requested_by]).to eq(expected[:requested_by])
+
+ expect(resource.run_context.reboot_requested?).to be_true
+ end
+ end
+
+ # the currently defined behavior for multiple calls to this resource is "last one wins."
+ describe 'the request_reboot_on_successful_run action' do
+ include_context 'testing run context modification'
+
+ before do
+ resource.run_action(:request_reboot)
+ end
+
+ after do
+ resource.run_context.cancel_reboot
+ end
+
+ it 'should have modified the run context correctly' do
+ test_reboot_action(resource)
+ end
+ end
+
+ describe 'the reboot_interrupt_run action' do
+ include_context 'testing run context modification'
+
+ after do
+ resource.run_context.cancel_reboot
+ end
+
+ it 'should have modified the run context correctly' do
+ # this doesn't actually test the flow of Chef::Client#do_run, unfortunately.
+ expect {
+ resource.run_action(:reboot_now)
+ }.to throw_symbol(:end_client_run_early)
+
+ test_reboot_action(resource)
+ end
+ end
+
+ describe "the cancel action" do
+ before do
+ resource.run_context.request_reboot(expected)
+ resource.run_action(:cancel)
+ end
+
+ it 'should have cleared the reboot request' do
+ # arguably we shouldn't be querying RunContext's internal data directly.
+ expect(resource.run_context.reboot_info).to eq({})
+ expect(resource.run_context.reboot_requested?).to be_false
+ end
+ end
+end
diff --git a/spec/functional/resource/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb
index 5f13bfcb0b..ba508e3258 100644
--- a/spec/functional/resource/user/dscl_spec.rb
+++ b/spec/functional/resource/user/dscl_spec.rb
@@ -21,7 +21,8 @@ require 'chef/mixin/shell_out'
metadata = {
:unix_only => true,
:requires_root => true,
- :provider => {:user => Chef::Provider::User::Dscl}
+ :provider => {:user => Chef::Provider::User::Dscl},
+ :not_supported_on_mac_osx_106 => true,
}
describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metadata do
diff --git a/spec/functional/util/path_helper_spec.rb b/spec/functional/util/path_helper_spec.rb
new file mode 100644
index 0000000000..ccdf383c22
--- /dev/null
+++ b/spec/functional/util/path_helper_spec.rb
@@ -0,0 +1,37 @@
+#
+# Copyright:: Copyright (c) 2014 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 'tmpdir'
+require 'chef/util/path_helper'
+require 'spec_helper'
+
+describe Chef::Util::PathHelper, "escape_glob" do
+ PathHelper = Chef::Util::PathHelper
+
+ it "escapes the glob metacharacters so globbing succeeds" do
+ # make a dir
+ Dir.mktmpdir("\\silly[dir]") do |dir|
+ # add some files
+ files = ["some.rb", "file.txt", "names.csv"]
+ files.each do |file|
+ File.new(File.join(dir, file), 'w').close
+ end
+
+ pattern = File.join(PathHelper.escape_glob(dir), "*")
+ Dir.glob(pattern).map { |x| File.basename(x) }.should match_array(files)
+ end
+ end
+end
diff --git a/spec/functional/util/powershell/cmdlet_spec.rb b/spec/functional/util/powershell/cmdlet_spec.rb
new file mode 100644
index 0000000000..63d1ac09b5
--- /dev/null
+++ b/spec/functional/util/powershell/cmdlet_spec.rb
@@ -0,0 +1,114 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+#
+# Copyright:: 2014, Chef Software, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'json'
+require File.expand_path('../../../../spec_helper', __FILE__)
+
+describe Chef::Util::Powershell::Cmdlet, :windows_only do
+ before(:all) do
+ ohai = Ohai::System.new
+ ohai.load_plugins
+ ohai.run_plugins(true, ['platform', 'kernel'])
+ @node = Chef::Node.new
+ @node.consume_external_attrs(ohai.data, {})
+ end
+ let(:cmd_output_format) { :text }
+ let(:simple_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, 'get-childitem', cmd_output_format, {:depth => 2}) }
+ let(:invalid_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, 'get-idontexist', cmd_output_format) }
+ let(:cmdlet_get_item_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, 'get-item', cmd_output_format, {:depth => 2}) }
+ let(:cmdlet_alias_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, 'alias', cmd_output_format, {:depth => 2}) }
+ let(:etc_directory) { "#{ENV['systemroot']}\\system32\\drivers\\etc" }
+ let(:architecture_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "$env:PROCESSOR_ARCHITECTURE")}
+
+ it "executes a simple process" do
+ result = simple_cmdlet.run
+ expect(result.succeeded?).to eq(true)
+ end
+
+ it "#run does not raise a PowershellCmdletException exception if the command cannot be executed" do
+ expect {invalid_cmdlet.run}.not_to raise_error
+ end
+
+ it "#run! raises a PowershellCmdletException exception if the command cannot be executed" do
+ expect {invalid_cmdlet.run!}.to raise_error(Chef::Exceptions::PowershellCmdletException)
+ end
+
+ it "executes a 64-bit command on a 64-bit OS, 32-bit otherwise" do
+ os_arch = ENV['PROCESSOR_ARCHITEW6432']
+ if os_arch.nil?
+ os_arch = ENV['PROCESSOR_ARCHITECTURE']
+ end
+
+ result = architecture_cmdlet.run
+ execution_arch = result.return_value
+ execution_arch.strip!
+ expect(execution_arch).to eq(os_arch)
+ end
+
+ it "passes command line switches to the command" do
+ result = cmdlet_alias_requires_switch_or_argument.run({:name => 'ls'})
+ expect(result.succeeded?).to eq(true)
+ end
+
+ it "passes command line arguments to the command" do
+ result = cmdlet_alias_requires_switch_or_argument.run({},{},'ls')
+ expect(result.succeeded?).to eq(true)
+ end
+
+ it "passes command line arguments and switches to the command" do
+ result = cmdlet_get_item_requires_switch_or_argument.run({:path => etc_directory},{},' | select-object -property fullname | format-table -hidetableheaders')
+ expect(result.succeeded?).to eq(true)
+ returned_directory = result.return_value
+ returned_directory.strip!
+ expect(returned_directory).to eq(etc_directory)
+ end
+
+ it "passes execution options to the command" do
+ result = cmdlet_get_item_requires_switch_or_argument.run({},{:cwd => etc_directory},'. | select-object -property fullname | format-table -hidetableheaders')
+ expect(result.succeeded?).to eq(true)
+ returned_directory = result.return_value
+ returned_directory.strip!
+ expect(returned_directory).to eq(etc_directory)
+ end
+
+ context "when returning json" do
+ let(:cmd_output_format) { :json }
+ it "returns json format data", :windows_powershell_dsc_only do
+ result = cmdlet_alias_requires_switch_or_argument.run({},{},'ls')
+ expect(result.succeeded?).to eq(true)
+ expect(lambda{JSON.parse(result.return_value)}).not_to raise_error
+ end
+ end
+
+ context "when returning Ruby objects" do
+ let(:cmd_output_format) { :object }
+ it "returns object format data", :windows_powershell_dsc_only do
+ result = simple_cmdlet.run({},{:cwd => etc_directory}, 'hosts')
+ expect(result.succeeded?).to eq(true)
+ data = result.return_value
+ expect(data['Name']).to eq('hosts')
+ end
+ end
+
+ context "when constructor is given invalid arguments" do
+ let(:cmd_output_format) { :invalid }
+ it "throws an exception if an invalid format is passed to the constructor" do
+ expect(lambda{simple_cmdlet}).to raise_error
+ end
+ end
+end
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index 8a1a65249b..0144ae0ce3 100644
--- a/spec/integration/client/client_spec.rb
+++ b/spec/integration/client/client_spec.rb
@@ -16,7 +16,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" }
+ let(:chef_client) { "ruby '#{chef_dir}/chef-client'" }
when_the_repository "has a cookbook with a no-op recipe" do
before { file 'cookbooks/x/recipes/default.rb', '' }
diff --git a/spec/integration/client/ipv6_spec.rb b/spec/integration/client/ipv6_spec.rb
index f49b7b7711..76dd1938f7 100644
--- a/spec/integration/client/ipv6_spec.rb
+++ b/spec/integration/client/ipv6_spec.rb
@@ -76,7 +76,7 @@ END_CLIENT_RB
let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
- let(:chef_client_cmd) { %Q[ruby #{chef_dir}/chef-client -c "#{path_to('config/client.rb')}" -lwarn] }
+ let(:chef_client_cmd) { %Q[ruby '#{chef_dir}/chef-client' -c "#{path_to('config/client.rb')}" -lwarn] }
after do
FileUtils.rm_rf(cache_path)
diff --git a/spec/integration/knife/cookbook_api_ipv6_spec.rb b/spec/integration/knife/cookbook_api_ipv6_spec.rb
index 4191bb1731..c5b5b81abe 100644
--- a/spec/integration/knife/cookbook_api_ipv6_spec.rb
+++ b/spec/integration/knife/cookbook_api_ipv6_spec.rb
@@ -62,7 +62,7 @@ END_VALIDATION_PEM
end
let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
- let(:knife) { "ruby #{chef_dir}/knife" }
+ let(:knife) { "ruby '#{chef_dir}/knife'" }
let(:knife_config_flag) { "-c '#{path_to("config/knife.rb")}'" }
diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb
index 9e2cf3fc8d..a0c13da6f7 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" }
+ let(:chef_client) { "ruby '#{chef_dir}/chef-client'" }
when_the_repository "has a cookbook with a nested LWRP" do
before do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 7c11957997..ed0a8f89f6 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -107,8 +107,11 @@ RSpec.configure do |config|
config.filter_run_excluding :not_supported_on_win2k3 => true if windows_win2k3?
config.filter_run_excluding :not_supported_on_solaris => true if solaris?
config.filter_run_excluding :win2k3_only => true unless windows_win2k3?
+ config.filter_run_excluding :windows_2008r2_or_later => true unless windows_2008r2_or_later?
config.filter_run_excluding :windows64_only => true unless windows64?
config.filter_run_excluding :windows32_only => true unless windows32?
+ config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc?
+ config.filter_run_excluding :windows_powershell_no_dsc_only => true unless ! windows_powershell_dsc?
config.filter_run_excluding :windows_domain_joined_only => true unless windows_domain_joined?
config.filter_run_excluding :solaris_only => true unless solaris?
config.filter_run_excluding :system_windows_service_gem_only => true unless system_windows_service_gem?
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
index a7c616d7a7..f8cad6de7f 100644
--- a/spec/support/platform_helpers.rb
+++ b/spec/support/platform_helpers.rb
@@ -52,6 +52,30 @@ def windows_win2k3?
(host['version'] && host['version'].start_with?("5.2"))
end
+def windows_2008r2_or_later?
+ return false unless windows?
+ wmi = WmiLite::Wmi.new
+ host = wmi.first_of('Win32_OperatingSystem')
+ version = host['version']
+ return false unless version
+ components = version.split('.').map do | component |
+ component.to_i
+ end
+ components.length >=2 && components[0] >= 6 && components[1] >= 1
+end
+
+def windows_powershell_dsc?
+ return false unless windows?
+ supports_dsc = false
+ begin
+ wmi = WmiLite::Wmi.new('root/microsoft/windows/desiredstateconfiguration')
+ lcm = wmi.query("SELECT * FROM meta_class WHERE __this ISA 'MSFT_DSCLocalConfigurationManager'")
+ supports_dsc = !! lcm
+ rescue WmiLite::WmiException
+ end
+ supports_dsc
+end
+
def mac_osx_106?
if File.exists? "/usr/bin/sw_vers"
result = shell_out("/usr/bin/sw_vers")
diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb
index 804830fcdc..72b72912bd 100644
--- a/spec/support/shared/functional/file_resource.rb
+++ b/spec/support/shared/functional/file_resource.rb
@@ -284,6 +284,7 @@ shared_examples_for "a file resource" do
before do
Chef::Config[:why_run] = true
+ Chef::Config[:ssl_verify_mode] = :verify_none
end
after do
@@ -333,6 +334,10 @@ shared_examples_for "file resource not pointing to a real file" do
!symlink?(file_path) && File.file?(file_path)
end
+ before do
+ Chef::Config[:ssl_verify_mode] = :verify_none
+ end
+
describe "when force_unlink is set to true" do
it ":create unlinks the target" do
real_file?(path).should be_false
@@ -363,6 +368,7 @@ shared_examples_for "a configured file resource" do
before do
Chef::Log.level = :info
+ Chef::Config[:ssl_verify_mode] = :verify_none
end
# note the stripping of the drive letter from the tmpdir on windows
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
index 465633b9e0..b42f7f69d9 100644
--- a/spec/support/shared/integration/integration_helper.rb
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -118,7 +118,10 @@ module IntegrationSupport
Chef::Config.delete("#{object_name}_path".to_sym)
end
Chef::Config.delete(:chef_repo_path)
- FileUtils.remove_entry_secure(@repository_dir)
+ # TODO: "force" actually means "silence all exceptions". this
+ # silences a weird permissions error on Windows that we should track
+ # down, but for now there's no reason for it to blow up our CI.
+ FileUtils.remove_entry_secure(@repository_dir, force=Chef::Platform.windows?)
ensure
@repository_dir = nil
end
diff --git a/spec/support/shared/matchers.rb b/spec/support/shared/matchers.rb
deleted file mode 100644
index 2e1c660c19..0000000000
--- a/spec/support/shared/matchers.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-
-require 'rspec/expectations'
-require 'spec/support/platform_helpers'
-
-RSpec::Matchers.define :match_environment_variable do |varname|
- match do |actual|
- expected = if windows? && ENV[varname].nil?
- # On Windows, if an environment variable is not set, the command
- # `echo %VARNAME%` outputs %VARNAME%
- "%#{varname}%"
- else
- ENV[varname].to_s
- end
-
- actual == expected
- end
-end
diff --git a/spec/support/shared/matchers/exit_with_code.rb b/spec/support/shared/matchers/exit_with_code.rb
new file mode 100644
index 0000000000..957586c85d
--- /dev/null
+++ b/spec/support/shared/matchers/exit_with_code.rb
@@ -0,0 +1,28 @@
+require 'rspec/expectations'
+
+# Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec
+RSpec::Matchers.define :exit_with_code do |exp_code|
+ actual = nil
+ match do |block|
+ begin
+ block.call
+ rescue SystemExit => e
+ actual = e.status
+ end
+ actual and actual == exp_code
+ end
+
+ failure_message_for_should do |block|
+ "expected block to call exit(#{exp_code}) but exit" +
+ (actual.nil? ? " not called" : "(#{actual}) was called")
+ end
+
+ failure_message_for_should_not do |block|
+ "expected block not to call exit(#{exp_code})"
+ end
+
+ description do
+ "expect block to call exit(#{exp_code})"
+ end
+
+end
diff --git a/spec/support/shared/matchers/match_environment_variable.rb b/spec/support/shared/matchers/match_environment_variable.rb
new file mode 100644
index 0000000000..c8c905f44a
--- /dev/null
+++ b/spec/support/shared/matchers/match_environment_variable.rb
@@ -0,0 +1,17 @@
+
+require 'rspec/expectations'
+require 'spec/support/platform_helpers'
+
+RSpec::Matchers.define :match_environment_variable do |varname|
+ match do |actual|
+ expected = if windows? && ENV[varname].nil?
+ # On Windows, if an environment variable is not set, the command
+ # `echo %VARNAME%` outputs %VARNAME%
+ "%#{varname}%"
+ else
+ ENV[varname].to_s
+ end
+
+ actual == expected
+ end
+end
diff --git a/spec/unit/application/apply.rb b/spec/unit/application/apply.rb
index 32c98c6ed6..62a53c2a31 100644
--- a/spec/unit/application/apply.rb
+++ b/spec/unit/application/apply.rb
@@ -20,7 +20,7 @@ require 'spec_helper'
describe Chef::Application::Apply do
before do
- @app = Chef::Application::Recipe.new
+ @app = Chef::Application::Apply.new
@app.stub(:configure_logging).and_return(true)
@recipe_text = "package 'nyancat'"
Chef::Config[:solo] = true
@@ -73,4 +73,14 @@ describe Chef::Application::Apply do
@recipe_fh.path.should == @app.instance_variable_get(:@recipe_filename)
end
end
+ describe "recipe_file_arg" do
+ before do
+ ARGV.clear
+ end
+ it "should exit and log message" do
+ Chef::Log.should_receive(:debug).with(/^No recipe file provided/)
+ lambda { @app.run }.should raise_error(SystemExit) { |e| e.status.should == 1 }
+ end
+
+ end
end
diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb
index af71c43b77..41411669e6 100644
--- a/spec/unit/config_spec.rb
+++ b/spec/unit/config_spec.rb
@@ -242,8 +242,8 @@ describe Chef::Config do
Chef::Config[:file_backup_path].should == backup_path
end
- it "Chef::Config[:ssl_verify_mode] defaults to :verify_none" do
- Chef::Config[:ssl_verify_mode].should == :verify_none
+ it "Chef::Config[:ssl_verify_mode] defaults to :verify_peer" do
+ Chef::Config[:ssl_verify_mode].should == :verify_peer
end
it "Chef::Config[:ssl_ca_path] defaults to nil" do
diff --git a/spec/unit/dsl/data_query_spec.rb b/spec/unit/dsl/data_query_spec.rb
index 8a985437b7..78cd5569e8 100644
--- a/spec/unit/dsl/data_query_spec.rb
+++ b/spec/unit/dsl/data_query_spec.rb
@@ -86,123 +86,21 @@ describe Chef::DSL::DataQuery do
end
context "when the item is encrypted" do
- let(:default_secret) { "abc123SECRET" }
-
- let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret) }
-
- let(:item) do
- item = Chef::DataBagItem.new
- item.data_bag(bag_name)
- item.raw_data = encoded_data
- item
- end
+ let(:secret) { "abc123SECRET" }
+ let(:enc_data_bag) { double("Chef::EncryptedDataBagItem") }
before do
allow( Chef::DataBagItem ).to receive(:load).with(bag_name, item_name).and_return(item)
+ expect(language).to receive(:encrypted?).and_return(true)
+ expect( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_return(secret)
end
- shared_examples_for "encryption detected" do
- let(:encoded_data) do
- Chef::Config[:data_bag_encrypt_version] = version
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret)
- end
-
- before do
- allow( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_return(default_secret)
- end
-
- it "detects encrypted data bag" do
- expect( encryptor ).to receive(:encryptor_keys).at_least(:once).and_call_original
- expect( Chef::Log ).to receive(:debug).with(/Data bag item looks encrypted/)
- language.data_bag_item(bag_name, item_name)
- end
- end
-
- context "when encryption version is 1" do
- include_examples "encryption detected" do
- let(:version) { 1 }
- let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor }
- end
- end
-
- context "when encryption version is 2" do
- include_examples "encryption detected" do
- let(:version) { 2 }
- let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor }
- end
+ it "detects encrypted data bag" do
+ expect( Chef::EncryptedDataBagItem ).to receive(:new).with(raw_data, secret).and_return(enc_data_bag)
+ expect( Chef::Log ).to receive(:debug).with(/Data bag item looks encrypted/)
+ expect(language.data_bag_item(bag_name, item_name)).to eq(enc_data_bag)
end
- context "when encryption version is 3", :ruby_20_only do
- include_examples "encryption detected" do
- let(:version) { 3 }
- let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor }
- end
- end
-
- shared_examples_for "an encrypted data bag item" do
- it "returns an encrypted data bag item" do
- expect( language.data_bag_item(bag_name, item_name, secret) ).to be_a_kind_of(Chef::EncryptedDataBagItem)
- end
-
- it "decrypts the contents of the data bag item" do
- expect( language.data_bag_item(bag_name, item_name, secret).to_hash ).to eql raw_data
- end
- end
-
- context "when a secret is supplied" do
- include_examples "an encrypted data bag item" do
- let(:secret) { default_secret }
- end
- end
-
- context "when a secret is not supplied" do
- before do
- allow( Chef::Config ).to receive(:[]).and_call_original
- expect( Chef::Config ).to receive(:[]).with(:encrypted_data_bag_secret).and_return(path)
- expect( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_call_original
- end
-
- context "when a secret is located at Chef::Config[:encrypted_data_bag_secret]" do
- let(:path) { "/tmp/my_secret" }
-
- before do
- expect( File ).to receive(:exist?).with(path).and_return(true)
- expect( IO ).to receive(:read).with(path).and_return(default_secret)
- end
-
- include_examples "an encrypted data bag item" do
- let(:secret) { nil }
- end
- end
-
- shared_examples_for "no secret file" do
- it "should fail to load the data bag item" do
- expect( Chef::Log ).to receive(:error).with(/Failed to load secret for encrypted data bag item/)
- expect( Chef::Log ).to receive(:error).with(/Failed to load data bag item/)
- expect{ language.data_bag_item(bag_name, item_name) }.to raise_error(error_type, error_message)
- end
- end
-
- context "when Chef::Config[:encrypted_data_bag_secret] is not configured" do
- include_examples "no secret file" do
- let(:path) { nil }
- let(:error_type) { ArgumentError }
- let(:error_message) { /No secret specified and no secret found/ }
- end
- end
-
- context "when Chef::Config[:encrypted_data_bag_secret] does not exist" do
- include_examples "no secret file" do
- before do
- expect( File ).to receive(:exist?).with(path).and_return(false)
- end
-
- let(:path) { "/tmp/my_secret" }
- let(:error_type) { Errno::ENOENT }
- let(:error_message) { /file not found/ }
- end
- end
- end
end
end
end
diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb
index 8576ae168a..0d643514e0 100644
--- a/spec/unit/dsl/reboot_pending_spec.rb
+++ b/spec/unit/dsl/reboot_pending_spec.rb
@@ -21,7 +21,7 @@ require "spec_helper"
describe Chef::DSL::RebootPending do
describe "reboot_pending?" do
- describe "in isoloation" do
+ describe "in isolation" do
let(:recipe) { Object.new.extend(Chef::DSL::RebootPending) }
before do
@@ -74,12 +74,6 @@ describe Chef::DSL::RebootPending do
end
end
- context "platform is not supported" do
- it 'should raise an exception' do
- recipe.stub_chain(:node, :[]).with(:platform).and_return('msdos')
- expect { recipe.reboot_pending? }.to raise_error(Chef::Exceptions::UnsupportedPlatform)
- end
- end
end # describe in isolation
describe "in a recipe" do
diff --git a/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb
new file mode 100644
index 0000000000..1da5efb36e
--- /dev/null
+++ b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb
@@ -0,0 +1,95 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2010-2014 Opscode, 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/encrypted_data_bag_item/check_encrypted'
+
+class CheckEncryptedTester
+ include Chef::EncryptedDataBagItem::CheckEncrypted
+end
+
+describe Chef::EncryptedDataBagItem::CheckEncrypted do
+
+ let(:tester) { CheckEncryptedTester.new }
+
+ it "detects the item is not encrypted when the data is empty" do
+ expect(tester.encrypted?({})).to eq(false)
+ end
+
+ it "detects the item is not encrypted when the data only contains an id" do
+ expect(tester.encrypted?({id: "foo"})).to eq(false)
+ end
+
+ context "when the item is encrypted" do
+
+ let(:default_secret) { "abc123SECRET" }
+ let(:item_name) { "item_name" }
+ let(:raw_data) {{
+ "id" => item_name,
+ "greeting" => "hello",
+ "nested" => {
+ "a1" => [1, 2, 3],
+ "a2" => { "b1" => true }
+ }
+ }}
+
+ let(:version) { 1 }
+ let(:encoded_data) do
+ Chef::Config[:data_bag_encrypt_version] = version
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret)
+ end
+
+ it "does not detect encryption when the item version is unknown" do
+ # It shouldn't be possible for someone to normally encrypt an item with an unknown version - they would have to
+ # do something funky like encrypting it and then manually changing the version
+ modified_encoded_data = encoded_data
+ modified_encoded_data["greeting"]["version"] = 4
+ expect(tester.encrypted?(modified_encoded_data)).to eq(false)
+ end
+
+ shared_examples_for "encryption detected" do
+ it "detects encrypted data bag" do
+ expect( encryptor ).to receive(:encryptor_keys).at_least(:once).and_call_original
+ expect(tester.encrypted?(encoded_data)).to eq(true)
+ end
+ end
+
+ context "when encryption version is 1" do
+ include_examples "encryption detected" do
+ let(:version) { 1 }
+ let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor }
+ end
+ end
+
+ context "when encryption version is 2" do
+ include_examples "encryption detected" do
+ let(:version) { 2 }
+ let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor }
+ end
+ end
+
+ context "when encryption version is 3", :ruby_20_only do
+ include_examples "encryption detected" do
+ let(:version) { 3 }
+ let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor }
+ end
+ end
+
+ end
+
+end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index 78be9632f6..d5c668753e 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -30,6 +30,7 @@ describe Chef::Knife::Bootstrap do
k.merge_configs
k.ui.stub(:stderr).and_return(stderr)
+ allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
k
end
@@ -221,10 +222,6 @@ describe Chef::Knife::Bootstrap do
k
end
- # Include a data bag secret in the options to prevent Bootstrap from
- # attempting to access /etc/chef/encrypted_data_bag_secret, which
- # can fail when the file exists but can't be accessed by the user
- # running the tests.
let(:options){ ["--bootstrap-no-proxy", setting, "-s", "foo"] }
let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
let(:rendered_template) do
@@ -290,7 +287,6 @@ describe Chef::Knife::Bootstrap do
describe "specifying the encrypted data bag secret key" do
let(:secret) { "supersekret" }
- let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') }
let(:options) { [] }
let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) }
let(:rendered_template) do
@@ -299,59 +295,55 @@ describe Chef::Knife::Bootstrap do
knife.render_template
end
- context "via --secret" do
- let(:options){ ["--secret", secret] }
-
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
- end
+ it "creates a secret file" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ rendered_template.should match(%r{#{secret}})
+ end
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
+ it "renders the client.rb with an encrypted_data_bag_secret entry" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
end
- context "via --secret-file" do
- let(:options) { ["--secret-file", secret_file] }
- let(:secret) { IO.read(secret_file) }
+ end
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
- end
+ describe "when transferring trusted certificates" do
+ let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, 'trusted_certs') }
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
end
- context "secret via config" do
- before do
- Chef::Config[:knife][:secret] = secret
- end
-
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
- end
+ before do
+ Chef::Config[:trusted_certs_dir] = trusted_certs_dir
+ IO.stub(:read).and_call_original
+ IO.stub(:read).with(File.expand_path(Chef::Config[:validation_key])).and_return("")
+ end
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
+ def certificates
+ Dir[File.join(trusted_certs_dir, "*.{crt,pem}")]
end
- context "secret-file via config" do
- let(:secret) { IO.read(secret_file) }
+ it "creates /etc/chef/trusted_certs" do
+ rendered_template.should match(%r{mkdir -p /etc/chef/trusted_certs})
+ end
- before do
- Chef::Config[:knife][:secret_file] = secret_file
+ it "copies the certificates in the directory" do
+ certificates.each do |cert|
+ IO.should_receive(:read).with(File.expand_path(cert))
end
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
+ certificates.each do |cert|
+ rendered_template.should match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'})
end
+ end
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
+ it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do
+ Dir.should_receive(:glob).with(File.join(trusted_certs_dir, "*.{crt,pem}")).and_return([])
+ rendered_template.should_not match(%r{mkdir -p /etc/chef/trusted_certs})
end
end
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
index 064f8c5621..266991a7dd 100644
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -29,9 +29,10 @@ describe Chef::Knife::Core::BootstrapContext do
:validation_client_name => 'chef-validator-testing'
}
end
- let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') }
- subject(:bootstrap_context) { described_class.new(config, run_list, chef_config) }
+ let(:secret) { nil }
+
+ subject(:bootstrap_context) { described_class.new(config, run_list, chef_config, secret) }
it "runs chef with the first-boot.json in the _default environment" do
bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default"
@@ -105,26 +106,9 @@ EXPECTED
end
describe "when an encrypted_data_bag_secret is provided" do
- context "via config[:secret]" do
- let(:chef_config) do
- {
- :knife => {:secret => "supersekret" }
- }
- end
- it "reads the encrypted_data_bag_secret" do
- bootstrap_context.encrypted_data_bag_secret.should eq "supersekret"
- end
- end
-
- context "via config[:secret_file]" do
- let(:chef_config) do
- {
- :knife => {:secret_file => secret_file}
- }
- end
- it "reads the encrypted_data_bag_secret" do
- bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file)
- end
+ let(:secret) { "supersekret" }
+ it "reads the encrypted_data_bag_secret" do
+ bootstrap_context.encrypted_data_bag_secret.should eq "supersekret"
end
end
diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb
index 984be8e58a..c31c88577d 100644
--- a/spec/unit/knife/data_bag_create_spec.rb
+++ b/spec/unit/knife/data_bag_create_spec.rb
@@ -20,97 +20,89 @@
require 'spec_helper'
require 'tempfile'
-module ChefSpecs
- class ChefRest
- attr_reader :args_received
- def initialize
- @args_received = []
- end
-
- def post_rest(*args)
- @args_received << args
- end
+describe Chef::Knife::DataBagCreate do
+ let(:knife) do
+ k = Chef::Knife::DataBagCreate.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
end
-end
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
-describe Chef::Knife::DataBagCreate do
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::DataBagCreate.new
- @rest = ChefSpecs::ChefRest.new
- @knife.stub(:rest).and_return(@rest)
- @stdout = StringIO.new
- @knife.ui.stub(:stdout).and_return(@stdout)
- end
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
+
+ let(:secret) { "abc123SECRET" }
+ let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }}
- it "creates a data bag when given one argument" do
- @knife.name_args = ['sudoing_admins']
- @rest.should_receive(:post_rest).with("data", {"name" => "sudoing_admins"})
- @knife.ui.should_receive(:info).with("Created data_bag[sudoing_admins]")
+ let(:config) { {} }
- @knife.run
+ before do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
end
it "tries to create a data bag with an invalid name when given one argument" do
- @knife.name_args = ['invalid&char']
- @knife.should_receive(:exit).with(1)
-
- @knife.run
+ knife.name_args = ['invalid&char']
+ expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName)
+ expect {knife.run}.to exit_with_code(1)
end
- it "creates a data bag item when given two arguments" do
- @knife.name_args = ['sudoing_admins', 'ME']
- user_supplied_hash = {"login_name" => "alphaomega", "id" => "ME"}
- data_bag_item = Chef::DataBagItem.from_hash(user_supplied_hash)
- data_bag_item.data_bag("sudoing_admins")
- @knife.should_receive(:create_object).and_yield(user_supplied_hash)
- @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered
- @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered
+ context "when given one argument" do
+ before do
+ knife.name_args = [bag_name]
+ end
+
+ it "creates a data bag" do
+ expect(rest).to receive(:post_rest).with("data", {"name" => bag_name})
+ expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]")
- @knife.run
+ knife.run
+ end
end
- describe "encrypted data bag items" do
- before(:each) do
- @secret = "abc123SECRET"
- @plain_data = {"login_name" => "alphaomega", "id" => "ME"}
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
- @knife.name_args = ['sudoing_admins', 'ME']
- @knife.should_receive(:create_object).and_yield(@plain_data)
- data_bag_item = Chef::DataBagItem.from_hash(@enc_data)
- data_bag_item.data_bag("sudoing_admins")
-
- # Random IV is used each time the data bag item is encrypted, so values
- # will not be equal if we re-encrypt.
- Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data)
-
- @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered
- @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
+ context "no secret is specified for encryption" do
+ let(:item) do
+ item = Chef::DataBagItem.from_hash(raw_hash)
+ item.data_bag(bag_name)
+ item
end
- after do
- @secret_file.close
- @secret_file.unlink
+ it "creates a data bag item" do
+ expect(knife).to receive(:create_object).and_yield(raw_hash)
+ expect(knife).to receive(:encryption_secret_provided?).and_return(false)
+ expect(rest).to receive(:post_rest).with("data", {'name' => bag_name}).ordered
+ expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered
+
+ knife.run
end
+ end
+
+ context "a secret is specified for encryption" do
+ let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
- it "creates an encrypted data bag item via --secret" do
- @knife.stub(:config).and_return({:secret => @secret})
- @knife.run
+ let(:item) do
+ item = Chef::DataBagItem.from_hash(encoded_data)
+ item.data_bag(bag_name)
+ item
end
- it "creates an encrypted data bag item via --secret_file" do
- secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- secret_file.puts(@secret)
- secret_file.flush
- @knife.stub(:config).and_return({:secret_file => secret_file.path})
- @knife.run
+ it "creates an encrypted data bag item" do
+ expect(knife).to receive(:create_object).and_yield(raw_hash)
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::EncryptedDataBagItem)
+ .to receive(:encrypt_data_bag_item)
+ .with(raw_hash, secret)
+ .and_return(encoded_data)
+ expect(rest).to receive(:post_rest).with("data", {"name" => bag_name}).ordered
+ expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered
+
+ knife.run
end
end
diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb
index 866ca99174..6f19b5e63e 100644
--- a/spec/unit/knife/data_bag_edit_spec.rb
+++ b/spec/unit/knife/data_bag_edit_spec.rb
@@ -21,73 +21,107 @@ require 'tempfile'
describe Chef::Knife::DataBagEdit do
before do
- @plain_data = {"login_name" => "alphaomega", "id" => "item_name"}
- @edited_data = {
- "login_name" => "rho", "id" => "item_name",
- "new_key" => "new_value" }
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
+ end
+
+ let(:knife) do
+ k = Chef::Knife::DataBagEdit.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:raw_hash) { {"login_name" => "alphaomega", "id" => "item_name"} }
+ let(:db) { Chef::DataBagItem.from_hash(raw_hash)}
+ let(:raw_edited_hash) { {"login_name" => "rho", "id" => "item_name", "new_key" => "new_value"} }
+
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
- Chef::Config[:node_name] = "webmonkey.example.com"
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
- @knife = Chef::Knife::DataBagEdit.new
- @rest = double('chef-rest-mock')
- @knife.stub(:rest).and_return(@rest)
+ let(:secret) { "abc123SECRET" }
- @stdout = StringIO.new
- @knife.stub(:stdout).and_return(@stdout)
- @log = Chef::Log
- @knife.name_args = ['bag_name', 'item_name']
+ let(:config) { {} }
+
+ let(:is_encrypted?) { false }
+ let(:transmitted_hash) { raw_edited_hash }
+ let(:data_to_edit) { db }
+
+ shared_examples_for "editing a data bag" do
+ it "correctly edits then uploads the data bag" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
+ expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(is_encrypted?)
+ expect(knife).to receive(:edit_data).with(data_to_edit).and_return(raw_edited_hash)
+ expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", transmitted_hash).ordered
+
+ knife.run
+ end
end
it "requires data bag and item arguments" do
- @knife.name_args = []
- lambda { @knife.run }.should raise_error(SystemExit)
- @stdout.string.should match(/^You must supply the data bag and an item to edit/)
+ knife.name_args = []
+ expect(stdout).to receive(:puts).twice.with(anything)
+ expect {knife.run}.to exit_with_code(1)
+ expect(stdout.string).to eq("")
end
- it "saves edits on a data bag item" do
- Chef::DataBagItem.stub(:load).with('bag_name', 'item_name').and_return(@plain_data)
- @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
- @rest.should_receive(:put_rest).with("data/bag_name/item_name", @edited_data).ordered
- @knife.run
+ context "when no secret is provided" do
+ include_examples "editing a data bag"
end
- describe "encrypted data bag items" do
- before(:each) do
- @secret = "abc123SECRET"
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
- @enc_edited_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@edited_data,
- @secret)
- Chef::DataBagItem.stub(:load).with('bag_name', 'item_name').and_return(@enc_data)
-
- # Random IV is used each time the data bag item is encrypted, so values
- # will not be equal if we encrypt same value twice.
- Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_edited_data)
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
+ context "when config[:print_after] is set" do
+ let(:config) { {:print_after => true} }
+ before do
+ expect(knife.ui).to receive(:output).with(raw_edited_hash)
end
- after do
- @secret_file.close
- @secret_file.unlink
+ include_examples "editing a data bag"
+ end
+
+ context "when a secret is provided" do
+ let!(:enc_raw_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
+ let!(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_edited_hash, secret) }
+ let(:transmitted_hash) { enc_edited_hash }
+
+ before(:each) do
+ expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret)
+ expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash)
end
- it "decrypts and encrypts via --secret" do
- @knife.stub(:config).and_return({:secret => @secret})
- @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
- @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered
+ context "the data bag starts encrypted" do
+ let(:is_encrypted?) { true }
+ let(:db) { Chef::DataBagItem.from_hash(enc_raw_hash) }
+ # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag
+ let (:data_to_edit) { raw_hash }
+
+ before(:each) do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ end
- @knife.run
+ include_examples "editing a data bag"
end
- it "decrypts and encrypts via --secret_file" do
- @knife.stub(:config).and_return({:secret_file => @secret_file.path})
- @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
- @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered
+ context "the data bag starts unencrypted" do
+ before(:each) do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).exactly(0).times
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ end
- @knife.run
+ include_examples "editing a data bag"
end
end
+
+ it "fails to edit an encrypted data bag if the secret is missing" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
+ expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true)
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+
+ expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.")
+ expect {knife.run}.to exit_with_code(1)
+ end
+
end
diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb
index 1ad6b4712c..662af3f0d6 100644
--- a/spec/unit/knife/data_bag_from_file_spec.rb
+++ b/spec/unit/knife/data_bag_from_file_spec.rb
@@ -26,168 +26,145 @@ Chef::Knife::DataBagFromFile.load_deps
describe Chef::Knife::DataBagFromFile do
before :each do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::DataBagFromFile.new
- @rest = double("Chef::REST")
- @knife.stub(:rest).and_return(@rest)
- @stdout = StringIO.new
- @knife.ui.stub(:stdout).and_return(@stdout)
- @tmp_dir = Dir.mktmpdir
- @db_folder = File.join(@tmp_dir, 'data_bags', 'bag_name')
- FileUtils.mkdir_p(@db_folder)
- @db_file = Tempfile.new(["data_bag_from_file_test", ".json"], @db_folder)
- @db_file2 = Tempfile.new(["data_bag_from_file_test2", ".json"], @db_folder)
- @db_folder2 = File.join(@tmp_dir, 'data_bags', 'bag_name2')
- FileUtils.mkdir_p(@db_folder2)
- @db_file3 = Tempfile.new(["data_bag_from_file_test3", ".json"], @db_folder2)
- @plain_data = {
- "id" => "item_name",
- "greeting" => "hello",
- "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
- }
- @db_file.write(@plain_data.to_json)
- @db_file.flush
- @knife.instance_variable_set(:@name_args, ['bag_name', @db_file.path])
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ FileUtils.mkdir_p([db_folder, db_folder2])
+ db_file.write(plain_data.to_json)
+ db_file.flush
+ allow(knife).to receive(:config).and_return(config)
+ allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader)
end
# We have to explicitly clean up Tempfile on Windows because it said so.
after :each do
- @db_file.close
- @db_file2.close
- @db_file3.close
- FileUtils.rm_rf(@db_folder)
- FileUtils.rm_rf(@db_folder2)
- FileUtils.remove_entry_secure @tmp_dir
+ db_file.close
+ db_file2.close
+ db_file3.close
+ FileUtils.rm_rf(db_folder)
+ FileUtils.rm_rf(db_folder2)
+ FileUtils.remove_entry_secure tmp_dir
end
+ let(:knife) do
+ k = Chef::Knife::DataBagFromFile.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:tmp_dir) { Dir.mktmpdir }
+ let(:db_folder) { File.join(tmp_dir, data_bags_path, bag_name) }
+ let(:db_file) { Tempfile.new(["data_bag_from_file_test", ".json"], db_folder) }
+ let(:db_file2) { Tempfile.new(["data_bag_from_file_test2", ".json"], db_folder) }
+ let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) }
+ let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) }
+
+ def new_bag_expects(b = bag_name, d = plain_data)
+ data_bag = double
+ expect(data_bag).to receive(:data_bag).with(b)
+ expect(data_bag).to receive(:raw_data=).with(d)
+ expect(data_bag).to receive(:save)
+ expect(data_bag).to receive(:data_bag)
+ expect(data_bag).to receive(:id)
+ data_bag
+ end
+
+ let(:loader) { double("Knife::Core::ObjectLoader") }
+
+ let(:data_bags_path) { "data_bags" }
+ let(:plain_data) { {
+ "id" => "item_name",
+ "greeting" => "hello",
+ "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
+ } }
+ let(:enc_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_data, secret) }
+
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
+
+ let(:bag_name) { "sudoing_admins" }
+ let(:bag_name2) { "sudoing_admins2" }
+ let(:item_name) { "ME" }
+
+ let(:secret) { "abc123SECRET" }
+
+ let(:config) { {} }
+
it "loads from a file and saves" do
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
-
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @plain_data
+ knife.name_args = [bag_name, db_file.path]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects)
+
+ knife.run
end
- it "loads all from a mutiple files and saves" do
- @knife.name_args = [ 'bag_name', @db_file.path, @db_file2.path ]
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save).twice
- @knife.run
-
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @plain_data
+ it "loads all from multiple files and saves" do
+ knife.name_args = [ bag_name, db_file.path, db_file2.path ]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
+
+ knife.run
end
it "loads all from a folder and saves" do
- @knife.name_args = [ 'bag_name', @db_folder ]
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save).twice
- @knife.run
+ knife.name_args = [ bag_name, db_folder ]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
+
+ knife.run
end
describe "loading all data bags" do
- before do
- @pwd = Dir.pwd
- Dir.chdir(@tmp_dir)
- end
-
- after do
- Dir.chdir(@pwd)
- end
-
it "loads all data bags when -a or --all options is provided" do
- @knife.name_args = []
- @knife.stub(:config).and_return({:all => true})
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file.path)).
- and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file2.path)).
- and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)).
- and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save).exactly(3).times
- @knife.run
+ knife.name_args = []
+ config[:all] = true
+ expect(loader).to receive(:find_all_object_dirs).with("./#{data_bags_path}").and_return([bag_name, bag_name2])
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name}").and_return([File.basename(db_file.path), File.basename(db_file2.path)])
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2))
+
+ knife.run
end
it "loads all data bags items when -a or --all options is provided" do
- @knife.name_args = ["bag_name2"]
- @knife.stub(:config).and_return({:all => true})
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)).
- and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
- dbag.data_bag.should == 'bag_name2'
- dbag.raw_data.should == @plain_data
+ knife.name_args = [bag_name2]
+ config[:all] = true
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2))
+
+ knife.run
end
end
describe "encrypted data bag items" do
before(:each) do
- @secret = "abc123SECRET"
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
-
- # Random IV is used each time the data bag item is encrypted, so values
- # will not be equal if we re-encrypt.
- Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data)
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
- end
-
- after do
- @secret_file.close
- @secret_file.unlink
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(enc_data)
end
it "encrypts values when given --secret" do
- @knife.stub(:config).and_return({:secret => @secret})
-
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", @db_file.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @enc_data
- end
-
- it "encrypts values when given --secret_file" do
- @knife.stub(:config).and_return({:secret_file => @secret_file.path})
+ knife.name_args = [bag_name, db_file.path]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data))
- @knife.loader.stub(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @enc_data
+ knife.run
end
end
describe "command line parsing" do
it "prints help if given no arguments" do
- @knife.instance_variable_set(:@name_args, [])
- lambda { @knife.run }.should raise_error(SystemExit)
- help_text = "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
- help_text_regex = Regexp.new("^#{Regexp.escape(help_text)}")
- @stdout.string.should match(help_text_regex)
+ knife.name_args = [bag_name]
+ expect {knife.run}.to exit_with_code(1)
+ expect(stdout.string).to start_with("knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)")
end
end
diff --git a/spec/unit/knife/data_bag_secret_options_spec.rb b/spec/unit/knife/data_bag_secret_options_spec.rb
new file mode 100644
index 0000000000..0a2d8ca4bf
--- /dev/null
+++ b/spec/unit/knife/data_bag_secret_options_spec.rb
@@ -0,0 +1,165 @@
+#
+# Author:: Tyler Ball (<tball@opscode.com>)
+# Copyright:: Copyright (c) 2009-2014 Opscode, 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/knife'
+require 'chef/config'
+require 'tempfile'
+
+class ExampleDataBagCommand < Chef::Knife
+ include Chef::Knife::DataBagSecretOptions
+end
+
+describe Chef::Knife::DataBagSecretOptions do
+ let(:example_db) do
+ k = ExampleDataBagCommand.new
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:stdout) { StringIO.new }
+
+ let(:secret) { "abc123SECRET" }
+ let(:secret_file) do
+ sfile = Tempfile.new("encrypted_data_bag_secret")
+ sfile.puts(secret)
+ sfile.flush
+ sfile
+ end
+
+ after do
+ secret_file.close
+ secret_file.unlink
+ end
+
+ describe "#validate_secrets" do
+
+ it "throws an error when provided with both --secret and --secret-file on the CL" do
+ Chef::Config[:knife][:cl_secret_file] = secret_file.path
+ Chef::Config[:knife][:cl_secret] = secret
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file")
+
+ example_db.validate_secrets
+ end
+
+ it "throws an error when provided with `secret` and `secret_file` in knife.rb" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config file")
+
+ example_db.validate_secrets
+ end
+
+ end
+
+ describe "#read_secret" do
+
+ it "returns the secret first" do
+ Chef::Config[:knife][:cl_secret] = secret
+ expect(example_db).to receive(:config).and_return({ :secret => secret })
+ expect(example_db.read_secret).to eq(secret)
+ end
+
+ it "returns the secret_file only if secret does not exist" do
+ Chef::Config[:knife][:cl_secret_file] = secret_file.path
+ expect(example_db).to receive(:config).and_return({ :secret_file => secret_file.path })
+ expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
+ expect(example_db.read_secret).to eq("secret file contents")
+ end
+
+ it "returns the secret from the knife.rb config" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.read_secret).to eq(secret)
+ end
+
+ it "returns the secret_file from the knife.rb config only if the secret does not exist" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
+ expect(example_db.read_secret).to eq("secret file contents")
+ end
+
+ end
+
+ describe "#encryption_secret_provided?" do
+
+ it "returns true if the secret is passed on the CL" do
+ Chef::Config[:knife][:cl_secret] = secret
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if the secret_file is passed on the CL" do
+ Chef::Config[:knife][:cl_secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if --encrypt is passed on the CL and :secret is in config" do
+ expect(example_db).to receive(:config).and_return({ :encrypt => true })
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if --encrypt is passed on the CL and :secret_file is in config" do
+ expect(example_db).to receive(:config).and_return({ :encrypt => true })
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the config" do
+ expect(example_db).to receive(:config).and_return({ :encrypt => true })
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.")
+ example_db.encryption_secret_provided?
+ end
+
+ it "returns false if no secret is passed" do
+ expect(example_db).to receive(:config).and_return({})
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns false if --encrypt is not provided and :secret is in the config" do
+ expect(example_db).to receive(:config).and_return({})
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns false if --encrypt is not provided and :secret_file is in the config" do
+ expect(example_db).to receive(:config).and_return({})
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
+ end
+
+ it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
+ end
+
+ it "returns false if --encrypt is not provided and need_encrypt_flag is false" do
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(false)
+ end
+
+ end
+
+end
diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb
index 4aa642fc4b..1125d99c2a 100644
--- a/spec/unit/knife/data_bag_show_spec.rb
+++ b/spec/unit/knife/data_bag_show_spec.rb
@@ -25,97 +25,99 @@ require 'chef/json_compat'
require 'tempfile'
describe Chef::Knife::DataBagShow do
+
before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::DataBagShow.new
- @knife.config[:format] = 'json'
- @rest = double("Chef::REST")
- allow(@knife).to receive(:rest).and_return(@rest)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
end
-
- it "prints the ids of the data bag items when given a bag name" do
- @knife.instance_variable_set(:@name_args, ['bag_o_data'])
- data_bag_contents = { "baz"=>"http://localhost:4000/data/bag_o_data/baz",
- "qux"=>"http://localhost:4000/data/bag_o_data/qux"}
- expect(Chef::DataBag).to receive(:load).and_return(data_bag_contents)
- expected = %q|[
- "baz",
- "qux"
-]|
- @knife.run
- expect(@stdout.string.strip).to eq(expected)
+ let(:knife) do
+ k = Chef::Knife::DataBagShow.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
end
- it "prints the contents of the data bag item when given a bag and item name" do
- @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item'])
- data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}}
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
- expect(Chef::DataBagItem).to receive(:load).with('bag_o_data', 'an_item').and_return(data_item)
-
- @knife.run
- expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(data_item.raw_data)
- end
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
- it "should pretty print the data bag contents" do
- @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item'])
- data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}}
+ let(:data_bag_contents) { { "id" => "id", "baz"=>"http://localhost:4000/data/bag_o_data/baz",
+ "qux"=>"http://localhost:4000/data/bag_o_data/qux"} }
+ let(:enc_hash) {Chef::EncryptedDataBagItem.encrypt_data_bag_item(data_bag_contents, secret)}
+ let(:data_bag) {Chef::DataBagItem.from_hash(data_bag_contents)}
+ let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_hash) }
+ let(:enc_data_bag) { Chef::EncryptedDataBagItem.new(enc_hash, secret) }
- expect(Chef::DataBagItem).to receive(:load).with('bag_o_data', 'an_item').and_return(data_item)
+ let(:secret) { "abc123SECRET" }
+ #
+ # let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }}
+ #
+ let(:config) { {format: "json"} }
- @knife.run
- expect(@stdout.string).to eql("{\n \"id\": \"an_item\",\n \"zsh\": \"victory_through_tabbing\"\n}\n")
- end
+ context "Data bag to show is encrypted" do
+ before do
+ allow(knife).to receive(:encrypted?).and_return(true)
+ end
- describe "encrypted data bag items" do
- before(:each) do
- @secret = "abc123SECRET"
- @plain_data = {
- "id" => "item_name",
- "greeting" => "hello",
- "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
- }
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
- @knife.instance_variable_set(:@name_args, ['bag_name', 'item_name'])
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
+ it "decrypts and displays the encrypted data bag when the secret is provided" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
+ expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.")
+ expect(Chef::EncryptedDataBagItem).to receive(:load).with(bag_name, item_name, secret).and_return(enc_data_bag)
+
+ expected = %q|baz: http://localhost:4000/data/bag_o_data/baz
+id: id
+qux: http://localhost:4000/data/bag_o_data/qux|
+ knife.run
+ expect(stdout.string.strip).to eq(expected)
end
- after do
- @secret_file.close
- @secret_file.unlink
+ it "displays the encrypted data bag when the secret is not provided" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
+ expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
+
+ knife.run
+ expect(stdout.string.strip).to include("baz", "qux", "cipher")
end
+ end
- it "prints the decrypted contents of an item when given --secret" do
- allow(@knife).to receive(:config).and_return({:secret => @secret})
- expect(Chef::EncryptedDataBagItem).to receive(:load).
- with('bag_name', 'item_name', @secret).
- and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret))
- @knife.run
- expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(@plain_data)
+ context "Data bag to show is not encrypted" do
+ before do
+ allow(knife).to receive(:encrypted?).and_return(false)
+ expect(knife).to receive(:read_secret).exactly(0).times
end
- it "prints the decrypted contents of an item when given --secret_file" do
- allow(@knife).to receive(:config).and_return({:secret_file => @secret_file.path})
- expect(Chef::EncryptedDataBagItem).to receive(:load).
- with('bag_name', 'item_name', @secret).
- and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret))
- @knife.run
- expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(@plain_data)
+ it "displays the data bag" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag)
+ expect(knife.ui).to receive(:info).with("Unencrypted data bag detected, ignoring any provided secret options.")
+
+ expected = %q|baz: http://localhost:4000/data/bag_o_data/baz
+id: id
+qux: http://localhost:4000/data/bag_o_data/qux|
+ knife.run
+ expect(stdout.string.strip).to eq(expected)
end
end
- describe "command line parsing" do
- it "prints help if given no arguments" do
- @knife.instance_variable_set(:@name_args, [])
- expect { @knife.run }.to raise_error(SystemExit)
- expect(@stdout.string).to match(/^knife data bag show BAG \[ITEM\] \(options\)/)
- end
+ it "displays the list of items in the data bag when only one @name_arg is provided" do
+ knife.name_args = [bag_name]
+ expect(Chef::DataBag).to receive(:load).with(bag_name).and_return({})
+
+ knife.run
+ expect(stdout.string.strip).to eq("")
+ end
+
+ it "raises an error when no @name_args are provided" do
+ knife.name_args = []
+
+ expect {knife.run}.to exit_with_code(1)
+ expect(stdout.string).to start_with("knife data bag show BAG [ITEM] (options)")
end
end
diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb
index f208320c5d..ed3631fcf5 100644
--- a/spec/unit/knife/environment_from_file_spec.rb
+++ b/spec/unit/knife/environment_from_file_spec.rb
@@ -57,8 +57,8 @@ describe Chef::Knife::EnvironmentFromFile do
end
it "loads all environments with -a" do
- File.stub(:expand_path).with("./environments/*.{json,rb}").and_return("/tmp/environments")
- Dir.stub(:glob).with("/tmp/environments").and_return(["spec.rb", "apple.rb"])
+ File.stub(:expand_path).with("./environments/").and_return("/tmp/environments")
+ Dir.stub(:glob).with("/tmp/environments/*.{json,rb}").and_return(["spec.rb", "apple.rb"])
@knife.name_args = []
@knife.stub(:config).and_return({:all => true})
@environment.should_receive(:save).twice
diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb
index 32405a5977..bb803ce2ca 100644
--- a/spec/unit/knife/ssl_check_spec.rb
+++ b/spec/unit/knife/ssl_check_spec.rb
@@ -95,6 +95,49 @@ E
end
end
+ describe "verifying trusted certificate X509 properties" do
+ let(:name_args) { %w{https://foo.example.com:8443} }
+
+ let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, "trusted_certs") }
+ let(:trusted_cert_file) { File.join(trusted_certs_dir, "example.crt") }
+
+ let(:store) { OpenSSL::X509::Store.new }
+ let(:certificate) { OpenSSL::X509::Certificate.new(IO.read(trusted_cert_file)) }
+
+ before do
+ Chef::Config[:trusted_certs_dir] = trusted_certs_dir
+ ssl_check.stub(:trusted_certificates).and_return([trusted_cert_file])
+ store.stub(:add_cert).with(certificate)
+ OpenSSL::X509::Store.stub(:new).and_return(store)
+ OpenSSL::X509::Certificate.stub(:new).with(IO.read(trusted_cert_file)).and_return(certificate)
+ ssl_check.stub(:verify_cert).and_return(true)
+ ssl_check.stub(:verify_cert_host).and_return(true)
+ end
+
+ context "when the trusted certificates have valid X509 properties" do
+ before do
+ store.stub(:verify).with(certificate).and_return(true)
+ end
+
+ it "does not generate any X509 warnings" do
+ expect(ssl_check.ui).not_to receive(:warn).with(/There are invalid certificates in your trusted_certs_dir/)
+ ssl_check.run
+ end
+ end
+
+ context "when the trusted certificates have invalid X509 properties" do
+ before do
+ store.stub(:verify).with(certificate).and_return(false)
+ store.stub(:error_string).and_return("unable to get local issuer certificate")
+ end
+
+ it "generates a warning message with invalid certificate file names" do
+ expect(ssl_check.ui).to receive(:warn).with(/#{trusted_cert_file}: unable to get local issuer certificate/)
+ ssl_check.run
+ end
+ end
+ end
+
describe "verifying the remote certificate" do
let(:name_args) { %w{https://foo.example.com:8443} }
@@ -117,6 +160,7 @@ E
context "when the remote host's certificate is valid" do
before do
+ ssl_check.should_receive(:verify_X509).and_return(true) # X509 valid certs (no warn)
ssl_socket.should_receive(:connect) # no error
ssl_socket.should_receive(:post_connection_check).with("foo.example.com") # no error
end
@@ -148,6 +192,7 @@ E
context "when the certificate's CN does not match the hostname" do
before do
+ ssl_check.should_receive(:verify_X509).and_return(true) # X509 valid certs
ssl_socket.should_receive(:connect) # no error
ssl_socket.should_receive(:post_connection_check).
with("foo.example.com").
@@ -167,6 +212,7 @@ E
context "when the cert is not signed by any trusted authority" do
before do
+ ssl_check.should_receive(:verify_X509).and_return(true) # X509 valid certs
ssl_socket.should_receive(:connect).
and_raise(OpenSSL::SSL::SSLError)
ssl_socket_for_debug.should_receive(:connect)
@@ -184,4 +230,3 @@ E
end
end
-
diff --git a/spec/unit/mixin/homebrew_owner_spec.rb b/spec/unit/mixin/homebrew_owner_spec.rb
new file mode 100644
index 0000000000..428cd827d9
--- /dev/null
+++ b/spec/unit/mixin/homebrew_owner_spec.rb
@@ -0,0 +1,65 @@
+#
+# Author:: Joshua Timberman (<joshua@getchef.com>)
+#
+# Copyright 2014, Chef Software, Inc <legal@getchef.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+require 'chef/mixin/homebrew_owner'
+
+class ExampleHomebrewOwner
+ include Chef::Mixin::HomebrewOwner
+end
+
+describe Chef::Mixin::HomebrewOwner do
+ before(:each) do
+ node.default['homebrew']['owner'] = nil
+ end
+
+ let(:homebrew_owner) { ExampleHomebrewOwner.new }
+ let(:node) { Chef::Node.new }
+
+ describe 'when the homebrew owner node attribute is set' do
+ it 'raises an exception if the owner is root' do
+ node.default['homebrew']['owner'] = 'root'
+ expect { homebrew_owner.homebrew_owner(node) }.to raise_exception(Chef::Exceptions::CannotDetermineHomebrewOwner)
+ end
+
+ it 'returns the owner set by attribute' do
+ node.default['homebrew']['owner'] = 'siouxsie'
+ expect(homebrew_owner.homebrew_owner(node)).to eql('siouxsie')
+ end
+ end
+
+ describe 'when the owner attribute is not set and we use sudo' do
+ before(:each) do
+ ENV.stub(:[]).with('SUDO_USER').and_return('john_lydon')
+ end
+
+ it 'uses the SUDO_USER environment variable' do
+ expect(homebrew_owner.homebrew_owner(node)).to eql('john_lydon')
+ end
+ end
+
+ describe 'when the owner attribute is not set and we arent using sudo' do
+ before(:each) do
+ ENV.stub(:[]).with('USER').and_return('sid_vicious')
+ ENV.stub(:[]).with('SUDO_USER').and_return(nil)
+ end
+
+ it 'uses the current user' do
+ expect(homebrew_owner.homebrew_owner(node)).to eql('sid_vicious')
+ end
+ end
+end
diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb
index 2414bdf552..6adea5eecf 100644
--- a/spec/unit/platform/query_helpers_spec.rb
+++ b/spec/unit/platform/query_helpers_spec.rb
@@ -30,3 +30,26 @@ describe "Chef::Platform#windows_server_2003?" do
expect { Thread.fork { Chef::Platform.windows_server_2003? }.join }.not_to raise_error
end
end
+
+describe 'Chef::Platform#supports_dsc?' do
+ it 'returns false if powershell is not present' do
+ node = Chef::Node.new
+ Chef::Platform.supports_dsc?(node).should be_false
+ end
+
+ ['1.0', '2.0', '3.0'].each do |version|
+ it "returns false for Powershell #{version}" do
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = version
+ Chef::Platform.supports_dsc?(node).should be_false
+ end
+ end
+
+ ['4.0', '5.0'].each do |version|
+ it "returns true for Powershell #{version}" do
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = version
+ Chef::Platform.supports_dsc?(node).should be_true
+ end
+ end
+end
diff --git a/spec/unit/provider/dsc_script_spec.rb b/spec/unit/provider/dsc_script_spec.rb
new file mode 100644
index 0000000000..8a7a7b5c6a
--- /dev/null
+++ b/spec/unit/provider/dsc_script_spec.rb
@@ -0,0 +1,145 @@
+#
+# Author:: Jay Mundrawala (<jdm@getchef.com>)
+#
+# Copyright:: Copyright (c) 2014 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 'chef'
+require 'chef/util/dsc/resource_info'
+require 'spec_helper'
+
+describe Chef::Provider::DscScript do
+ let (:node) {
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = '4.0'
+ node
+ }
+ let (:events) { Chef::EventDispatch::Dispatcher.new }
+ let (:run_context) { Chef::RunContext.new(node, {}, events) }
+ let (:resource) { Chef::Resource::DscScript.new("script", run_context) }
+ let (:provider) do
+ Chef::Provider::DscScript.new(resource, run_context)
+ end
+
+ describe '#load_current_resource' do
+ it "describes the resource as converged if there were 0 DSC resources" do
+ allow(provider).to receive(:run_configuration).with(:test).and_return([])
+ provider.load_current_resource
+ provider.instance_variable_get('@resource_converged').should be_true
+ end
+
+ it "describes the resource as not converged if there is 1 DSC resources that is converged" do
+ dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something'])
+ allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info])
+ provider.load_current_resource
+ provider.instance_variable_get('@resource_converged').should be_true
+ end
+
+ it "describes the resource as not converged if there is 1 DSC resources that is not converged" do
+ dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something'])
+ allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info])
+ provider.load_current_resource
+ provider.instance_variable_get('@resource_converged').should be_false
+ end
+
+ it "describes the resource as not converged if there are any DSC resources that are not converged" do
+ dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something'])
+ dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something'])
+
+ allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2])
+ provider.load_current_resource
+ provider.instance_variable_get('@resource_converged').should be_false
+ end
+
+ it "describes the resource as converged if all DSC resources that are converged" do
+ dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something'])
+ dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new('resource', false, ['nothing will change something'])
+
+ allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2])
+ provider.load_current_resource
+ provider.instance_variable_get('@resource_converged').should be_true
+ end
+ end
+
+ describe '#generate_configuration_document' do
+ # I think integration tests should cover these cases
+
+ it 'uses configuration_document_from_script_path when a dsc script file is given' do
+ allow(provider).to receive(:load_current_resource)
+ resource.command("path_to_script")
+ generator = double('Chef::Util::DSC::ConfigurationGenerator')
+ generator.should_receive(:configuration_document_from_script_path)
+ allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator)
+ provider.send(:generate_configuration_document, 'tmp', nil)
+ end
+
+ it 'uses configuration_document_from_script_code when a the dsc resource is given' do
+ allow(provider).to receive(:load_current_resource)
+ resource.code("ImADSCResource{}")
+ generator = double('Chef::Util::DSC::ConfigurationGenerator')
+ generator.should_receive(:configuration_document_from_script_code)
+ allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator)
+ provider.send(:generate_configuration_document, 'tmp', nil)
+ end
+
+ it 'should noop if neither code or command are provided' do
+ allow(provider).to receive(:load_current_resource)
+ generator = double('Chef::Util::DSC::ConfigurationGenerator')
+ generator.should_receive(:configuration_document_from_script_code).with('', anything(), anything())
+ allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator)
+ provider.send(:generate_configuration_document, 'tmp', nil)
+ end
+ end
+
+ describe 'action_run' do
+ it 'should converge the script if it is not converged' do
+ dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resource', true, ['will change something'])
+ allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info])
+ allow(provider).to receive(:run_configuration).with(:set)
+
+ provider.run_action(:run)
+ resource.should be_updated
+ end
+
+ it 'should not converge if the script is already converged' do
+ allow(provider).to receive(:run_configuration).with(:test).and_return([])
+
+ provider.run_action(:run)
+ resource.should_not be_updated
+ end
+ end
+
+ describe '#generate_description' do
+ it 'removes the resource name from the beginning of any log line from the LCM' do
+ dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline'])
+ provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info])
+ provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing something/)
+ end
+
+ it 'ignores the last line' do
+ dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', true, ['resourcename doing something', 'lastline'])
+ provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info])
+ provider.send(:generate_description)[1].should_not match(/lastline/)
+ end
+
+ it 'reports a dsc resource has not been changed if the LCM reported no change was required' do
+ dsc_resource_info = Chef::Util::DSC::ResourceInfo.new('resourcename', false, ['resourcename does nothing', 'lastline'])
+ provider.instance_variable_set('@dsc_resources_info', [dsc_resource_info])
+ provider.send(:generate_description)[1].should match(/converge DSC resource resourcename by doing nothing/)
+ end
+ end
+end
+
diff --git a/spec/unit/provider/git_spec.rb b/spec/unit/provider/git_spec.rb
index 143c22da6e..69a6443c67 100644
--- a/spec/unit/provider/git_spec.rb
+++ b/spec/unit/provider/git_spec.rb
@@ -244,8 +244,8 @@ SHAS
@provider.clone
end
- it "runs a checkout command with default options and uses -B to reset branches if necessary" do
- expected_cmd = 'git checkout -B deploy d35af14d41ae22b19da05d7d03a0bafc321b244c'
+ it "runs a checkout command with default options" do
+ expected_cmd = 'git branch -f deploy d35af14d41ae22b19da05d7d03a0bafc321b244c; git checkout deploy'
@provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir",
:log_tag => "git[web2.0 app]")
@provider.checkout
diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb
index c6a37fdd5b..ebb16e22af 100644
--- a/spec/unit/provider/ifconfig/debian_spec.rb
+++ b/spec/unit/provider/ifconfig/debian_spec.rb
@@ -56,8 +56,6 @@ describe Chef::Provider::Ifconfig::Debian do
describe "generate_config" do
context "when writing a file" do
- let(:config_file_ifcfg) { StringIO.new }
-
let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
@@ -67,14 +65,13 @@ describe Chef::Provider::Ifconfig::Debian do
before do
stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
- expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
end
it "should write a network-script" do
provider.run_action(:add)
- expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/)
- expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/)
- expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
+ expect(File.read(config_filename_ifcfg)).to match(/^iface eth0 inet static\s*$/)
+ expect(File.read(config_filename_ifcfg)).to match(/^\s+address 10\.0\.0\.1\s*$/)
+ expect(File.read(config_filename_ifcfg)).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
end
context "when the interface_dot_d directory does not exist" do
@@ -123,7 +120,6 @@ iface eth0 inet static
netmask 255.255.254.0
EOF
)
- expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist
end
@@ -139,6 +135,8 @@ EOF
before do
tempfile.write(expected_string)
tempfile.close
+
+ expect(provider).not_to receive(:converge_by).with(/modifying #{tempfile.path} to source #{tempdir_path}/)
end
it "should preserve all the contents" do
@@ -165,6 +163,9 @@ EOF
before do
tempfile.write("a line\nanother line\n")
tempfile.close
+
+ allow(provider).to receive(:converge_by).and_call_original
+ expect(provider).to receive(:converge_by).with(/modifying #{tempfile.path} to source #{tempdir_path}/).and_call_original
end
it "should preserve the original contents and add the source line" do
@@ -316,12 +317,37 @@ source #{tempdir_path}/*
describe "delete_config for action_delete" do
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
+
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
+
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
+
+ before do
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
+ File.open(config_filename_ifcfg, "w") do |fh|
+ fh.write "arbitrary text\n"
+ fh.close
+ end
+ end
+
+ after do
+ Dir.rmdir(tempdir_path)
+ end
+
it "should delete network-script if it exists" do
current_resource.device new_resource.device
- expect(File).to receive(:exist?).with(config_filename_ifcfg).and_return(true)
- expect(FileUtils).to receive(:rm_f).with(config_filename_ifcfg, :verbose => false)
+ # belt and suspenders testing?
+ Chef::Util::Backup.any_instance.should_receive(:do_backup).and_call_original
+
+ # internal implementation detail of Ifconfig.
+ Chef::Provider::File.any_instance.should_receive(:action_delete).and_call_original
+
+ expect(File.exist?(config_filename_ifcfg)).to be_true
provider.run_action(:delete)
+ expect(File.exist?(config_filename_ifcfg)).to be_false
end
end
diff --git a/spec/unit/provider/ifconfig/redhat_spec.rb b/spec/unit/provider/ifconfig/redhat_spec.rb
index f4b98dfc32..138c2a389d 100644
--- a/spec/unit/provider/ifconfig/redhat_spec.rb
+++ b/spec/unit/provider/ifconfig/redhat_spec.rb
@@ -37,22 +37,26 @@ describe Chef::Provider::Ifconfig::Redhat do
status = double("Status", :exitstatus => 0)
@provider.instance_variable_set("@status", status)
@provider.current_resource = @current_resource
- end
+
+ config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+ @config = double("chef-resource-file")
+ @provider.should_receive(:resource_for_config).with(config_filename).and_return(@config)
+ end
describe "generate_config for action_add" do
- it "should write network-script for centos" do
+ it "should write network-script for centos" do
@provider.stub(:load_current_resource)
@provider.stub(:run_command)
- config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
- config_file = StringIO.new
- File.should_receive(:new).with(config_filename, "w").and_return(config_file)
-
+ @config.should_receive(:content) do |arg|
+ arg.should match(/^\s*DEVICE=eth0\s*$/)
+ arg.should match(/^\s*IPADDR=10\.0\.0\.1\s*$/)
+ arg.should match(/^\s*NETMASK=255\.255\.254\.0\s*$/)
+ end
+ @config.should_receive(:run_action).with(:create)
+ @config.should_receive(:updated?).and_return(true)
@provider.run_action(:add)
- config_file.string.should match(/^\s*DEVICE=eth0\s*$/)
- config_file.string.should match(/^\s*IPADDR=10\.0\.0\.1\s*$/)
- config_file.string.should match(/^\s*NETMASK=255\.255\.254\.0\s*$/)
- end
+ end
end
describe "delete_config for action_delete" do
@@ -61,10 +65,8 @@ describe Chef::Provider::Ifconfig::Redhat do
@current_resource.device @new_resource.device
@provider.stub(:load_current_resource)
@provider.stub(:run_command)
- config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
- File.should_receive(:exist?).with(config_filename).and_return(true)
- FileUtils.should_receive(:rm_f).with(config_filename, :verbose => false)
-
+ @config.should_receive(:run_action).with(:delete)
+ @config.should_receive(:updated?).and_return(true)
@provider.run_action(:delete)
end
end
diff --git a/spec/unit/provider/ifconfig_spec.rb b/spec/unit/provider/ifconfig_spec.rb
index fb8d0956d7..b09e365c65 100644
--- a/spec/unit/provider/ifconfig_spec.rb
+++ b/spec/unit/provider/ifconfig_spec.rb
@@ -72,7 +72,7 @@ describe Chef::Provider::Ifconfig do
@provider.stub(:load_current_resource)
@provider.should_not_receive(:run_command)
@current_resource.inet_addr "10.0.0.1"
- @provider.should_not_receive(:generate_config)
+ @provider.should_receive(:generate_config)
@provider.run_action(:add)
@new_resource.should_not be_updated
@@ -123,7 +123,7 @@ describe Chef::Provider::Ifconfig do
it "should not delete interface if it does not exist" do
@provider.stub(:load_current_resource)
@provider.should_not_receive(:run_command)
- @provider.should_not_receive(:delete_config)
+ @provider.should_receive(:delete_config)
@provider.run_action(:delete)
@new_resource.should_not be_updated
@@ -171,7 +171,7 @@ describe Chef::Provider::Ifconfig do
@provider.stub(:load_current_resource)
# This is so that nothing actually runs
@provider.should_not_receive(:run_command)
- @provider.should_not_receive(:delete_config)
+ @provider.should_receive(:delete_config)
@provider.run_action(:delete)
@new_resource.should_not be_updated
diff --git a/spec/unit/provider/link_spec.rb b/spec/unit/provider/link_spec.rb
index 6052f5dd3b..2f0a5f2020 100644
--- a/spec/unit/provider/link_spec.rb
+++ b/spec/unit/provider/link_spec.rb
@@ -38,8 +38,8 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do
result
end
- def canonicalize(path)
- Chef::Platform.windows? ? path.gsub('/', '\\') : path
+ def paths_eql?(path1, path2)
+ Chef::Util::PathHelper.paths_eql?(path1, path2)
end
describe "when the target is a symlink" do
@@ -68,7 +68,7 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do
provider.current_resource.link_type.should == :symbolic
end
it "should update the source of the existing link with the links target" do
- provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+ paths_eql?(provider.current_resource.to, "#{CHEF_SPEC_DATA}/fofile").should be_true
end
it "should set the owner" do
provider.current_resource.owner.should == 501
@@ -110,7 +110,7 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do
provider.current_resource.link_type.should == :symbolic
end
it "should update the source of the existing link to the link's target" do
- provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+ paths_eql?(provider.current_resource.to, "#{CHEF_SPEC_DATA}/fofile").should be_true
end
it "should not set the owner" do
provider.current_resource.owner.should be_nil
@@ -221,7 +221,7 @@ describe Chef::Resource::Link, :not_supported_on_win2k3 do
provider.current_resource.link_type.should == :hard
end
it "should update the source of the existing link to the link's target" do
- provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+ paths_eql?(provider.current_resource.to, "#{CHEF_SPEC_DATA}/fofile").should be_true
end
it "should not set the owner" do
provider.current_resource.owner.should == nil
diff --git a/spec/unit/provider/package/homebrew_spec.rb b/spec/unit/provider/package/homebrew_spec.rb
new file mode 100644
index 0000000000..9f105c13b8
--- /dev/null
+++ b/spec/unit/provider/package/homebrew_spec.rb
@@ -0,0 +1,251 @@
+#
+# Author:: Joshua Timberman (<joshua@getchef.com>)
+# Copyright (c) 2014, Chef Software, Inc. <legal@getchef.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Package::Homebrew do
+ let(:node) { Chef::Node.new }
+ let(:events) { double('Chef::Events').as_null_object }
+ let(:run_context) { double('Chef::RunContext', node: node, events: events) }
+ let(:new_resource) { Chef::Resource::HomebrewPackage.new('emacs') }
+ let(:current_resource) { Chef::Resource::HomebrewPackage.new('emacs')}
+
+ let(:provider) do
+ Chef::Provider::Package::Homebrew.new(new_resource, run_context)
+ end
+
+ let(:uninstalled_brew_info) do
+ {
+ 'name' => 'emacs',
+ 'homepage' => 'http://www.gnu.org/software/emacs',
+ 'versions' => {
+ 'stable' => '24.3',
+ 'bottle' => false,
+ 'devel' => nil,
+ 'head' => nil
+ },
+ 'revision' => 0,
+ 'installed' => [],
+ 'linked_keg' => nil,
+ 'keg_only' => nil,
+ 'dependencies' => [],
+ 'conflicts_with' => [],
+ 'caveats' => nil,
+ 'options' => []
+ }
+ end
+
+ let(:installed_brew_info) do
+ {
+ 'name' => 'emacs',
+ 'homepage' => 'http://www.gnu.org/software/emacs/',
+ 'versions' => {
+ 'stable' => '24.3',
+ 'bottle' => false,
+ 'devel' => nil,
+ 'head' => 'HEAD'
+ },
+ 'revision' => 0,
+ 'installed' => [{ 'version' => '24.3' }],
+ 'linked_keg' => '24.3',
+ 'keg_only' => nil,
+ 'dependencies' => [],
+ 'conflicts_with' => [],
+ 'caveats' => '',
+ 'options' => []
+ }
+ end
+
+ let(:keg_only_brew_info) do
+ {
+ 'name' => 'emacs-kegger',
+ 'homepage' => 'http://www.gnu.org/software/emacs/',
+ 'versions' => {
+ 'stable' => '24.3-keggy',
+ 'bottle' => false,
+ 'devel' => nil,
+ 'head' => 'HEAD'
+ },
+ 'revision' => 0,
+ 'installed' => [{ 'version' => '24.3-keggy' }],
+ 'linked_keg' => nil,
+ 'keg_only' => true,
+ 'dependencies' => [],
+ 'conflicts_with' => [],
+ 'caveats' => '',
+ 'options' => []
+ }
+ end
+
+ before(:each) do
+ node.default['homebrew']['owner'] = 'sid_vicious'
+ allow(Etc).to receive(:getpwnam).with('sid_vicious').and_return('/Users/sid_vicious')
+ end
+
+ describe 'load_current_resource' do
+ before(:each) do
+ allow(provider).to receive(:current_installed_version).and_return(nil)
+ allow(provider).to receive(:candidate_version).and_return('24.3')
+ end
+
+ it 'creates a current resource with the name of the new resource' do
+ provider.load_current_resource
+ expect(provider.current_resource).to be_a(Chef::Resource::Package)
+ expect(provider.current_resource.name).to eql('emacs')
+ end
+
+ it 'creates a current resource with the version if the package is installed' do
+ expect(provider).to receive(:current_installed_version).and_return('24.3')
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql('24.3')
+ end
+
+ it 'creates a current resource with a nil version if the package is not installed' do
+ provider.load_current_resource
+ expect(provider.current_resource.version).to be_nil
+ end
+
+ it 'sets a candidate version if one exists' do
+ provider.load_current_resource
+ expect(provider.candidate_version).to eql('24.3')
+ end
+ end
+
+ describe 'current_installed_version' do
+ it 'returns the latest version from brew info if the package is keg only' do
+ allow(provider).to receive(:brew_info).and_return(keg_only_brew_info)
+ expect(provider.current_installed_version).to eql('24.3-keggy')
+ end
+
+ it 'returns the linked keg version if the package is not keg only' do
+ allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ expect(provider.current_installed_version).to eql('24.3')
+ end
+
+ it 'returns nil if the package is not installed' do
+ allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
+ expect(provider.current_installed_version).to be_nil
+ end
+ end
+
+ describe 'brew' do
+ it 'passes a single to the brew command and return stdout' do
+ allow(provider).to receive(:get_response_from_command).and_return('zombo')
+ expect(provider.brew).to eql('zombo')
+ end
+
+ it 'takes multiple arguments as an array' do
+ allow(provider).to receive(:get_response_from_command).and_return('homestarrunner')
+ expect(provider.brew('info', 'opts', 'bananas')).to eql('homestarrunner')
+ end
+ end
+
+ context 'when testing actions' do
+ before(:each) do
+ provider.current_resource = current_resource
+ end
+
+ describe 'install_package' do
+ before(:each) do
+ allow(provider).to receive(:candidate_version).and_return('24.3')
+ end
+
+ it 'installs the named package with brew install' do
+ allow(provider.new_resource).to receive(:version).and_return('24.3')
+ allow(provider.current_resource).to receive(:version).and_return(nil)
+ allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
+ expect(provider).to receive(:get_response_from_command).with('brew install emacs')
+ provider.install_package('emacs', '24.3')
+ end
+
+ it 'does not do anything if the package is installed' do
+ allow(provider.current_resource).to receive(:version).and_return('24.3')
+ allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ expect(provider).not_to receive(:get_response_from_command)
+ provider.install_package('emacs', '24.3')
+ end
+
+ it 'uses options to the brew command if specified' do
+ allow(provider.new_resource).to receive(:options).and_return('--cocoa')
+ allow(provider.current_resource).to receive(:version).and_return('24.3')
+ allow(provider).to receive(:get_response_from_command).with('brew install --cocoa emacs')
+ provider.install_package('emacs', '24.3')
+ end
+ end
+
+ describe 'upgrade_package' do
+ it 'uses brew upgrade to upgrade the package if it is installed' do
+ allow(provider.current_resource).to receive(:version).and_return('24')
+ allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ expect(provider).to receive(:get_response_from_command).with('brew upgrade emacs')
+ provider.upgrade_package('emacs', '24.3')
+ end
+
+ it 'does not do anything if the package version is already installed' do
+ allow(provider.current_resource).to receive(:version).and_return('24.3')
+ allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ expect(provider).not_to receive(:get_response_from_command)
+ provider.install_package('emacs', '24.3')
+ end
+
+ it 'uses brew install to install the package if it is not installed' do
+ allow(provider.current_resource).to receive(:version).and_return(nil)
+ allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
+ expect(provider).to receive(:get_response_from_command).with('brew install emacs')
+ provider.upgrade_package('emacs', '24.3')
+ end
+
+ it 'uses options to the brew command if specified' do
+ allow(provider.current_resource).to receive(:version).and_return('24')
+ allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ allow(provider.new_resource).to receive(:options).and_return('--cocoa')
+ expect(provider).to receive(:get_response_from_command).with('brew upgrade --cocoa emacs')
+ provider.upgrade_package('emacs', '24.3')
+ end
+ end
+
+ describe 'remove_package' do
+ it 'uninstalls the package with brew uninstall' do
+ allow(provider.current_resource).to receive(:version).and_return('24.3')
+ allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ expect(provider).to receive(:get_response_from_command).with('brew uninstall emacs')
+ provider.remove_package('emacs', '24.3')
+ end
+
+ it 'does not do anything if the package is not installed' do
+ allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
+ expect(provider).not_to receive(:get_response_from_command)
+ provider.remove_package('emacs', '24.3')
+ end
+ end
+
+ describe 'purge_package' do
+ it 'uninstalls the package with brew uninstall --force' do
+ allow(provider.current_resource).to receive(:version).and_return('24.3')
+ allow(provider).to receive(:brew_info).and_return(installed_brew_info)
+ expect(provider).to receive(:get_response_from_command).with('brew uninstall --force emacs')
+ provider.purge_package('emacs', '24.3')
+ end
+
+ it 'does not do anything if the package is not installed' do
+ allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info)
+ expect(provider).not_to receive(:get_response_from_command)
+ provider.purge_package('emacs', '24.3')
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/whyrun_safe_ruby_block_spec.rb b/spec/unit/provider/whyrun_safe_ruby_block_spec.rb
index 5a17aacbd9..d5209248b3 100644
--- a/spec/unit/provider/whyrun_safe_ruby_block_spec.rb
+++ b/spec/unit/provider/whyrun_safe_ruby_block_spec.rb
@@ -30,14 +30,14 @@ describe Chef::Provider::WhyrunSafeRubyBlock, "initialize" do
end
it "should call the block and flag the resource as updated" do
- @provider.run_action(:create)
+ @provider.run_action(:run)
$evil_global_evil_laugh.should == :mwahahaha
@new_resource.should be_updated
end
it "should call the block and flat the resource as updated - even in whyrun" do
Chef::Config[:why_run] = true
- @provider.run_action(:create)
+ @provider.run_action(:run)
$evil_global_evil_laugh.should == :mwahahaha
@new_resource.should be_updated
Chef::Config[:why_run] = false
diff --git a/spec/unit/resource/dsc_script_spec.rb b/spec/unit/resource/dsc_script_spec.rb
new file mode 100644
index 0000000000..cbd502a61c
--- /dev/null
+++ b/spec/unit/resource/dsc_script_spec.rb
@@ -0,0 +1,127 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+# Copyright:: Copyright (c) 2014 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::DscScript do
+ let(:dsc_test_resource_name) { 'DSCTest' }
+
+ context 'when Powershell supports Dsc' do
+ let(:dsc_test_run_context) {
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = '4.0'
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, empty_events)
+ }
+ let(:dsc_test_resource) {
+ Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
+ }
+ let(:configuration_code) {'echo "This is supposed to create a configuration document."'}
+ let(:configuration_path) {'c:/myconfigs/formatc.ps1'}
+ let(:configuration_name) { 'formatme' }
+ let(:configuration_data) { '@{AllNodes = @( @{ NodeName = "localhost"; PSDscAllowPlainTextPassword = $true })}' }
+ let(:configuration_data_script) { 'c:/myconfigs/data/safedata.psd1' }
+
+ it "has a default action of `:run`" do
+ expect(dsc_test_resource.action).to eq(:run)
+ end
+
+ it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do
+ expect(dsc_test_resource.allowed_actions.to_set).to eq([:run,:nothing].to_set)
+ end
+
+ it "allows the code attribute to be set" do
+ dsc_test_resource.code(configuration_code)
+ expect(dsc_test_resource.code).to eq(configuration_code)
+ end
+
+ it "allows the command attribute to be set" do
+ dsc_test_resource.command(configuration_path)
+ expect(dsc_test_resource.command).to eq(configuration_path)
+ end
+
+ it "allows the configuration_name attribute to be set" do
+ dsc_test_resource.configuration_name(configuration_name)
+ expect(dsc_test_resource.configuration_name).to eq(configuration_name)
+ end
+
+ it "allows the configuration_data attribute to be set" do
+ dsc_test_resource.configuration_data(configuration_data)
+ expect(dsc_test_resource.configuration_data).to eq(configuration_data)
+ end
+
+ it "allows the configuration_data_script attribute to be set" do
+ dsc_test_resource.configuration_data_script(configuration_data_script)
+ expect(dsc_test_resource.configuration_data_script).to eq(configuration_data_script)
+ end
+
+ it "raises an ArgumentError exception if an attempt is made to set the code attribute when the command attribute is already set" do
+ dsc_test_resource.command(configuration_path)
+ expect { dsc_test_resource.code(configuration_code) }.to raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError exception if an attempt is made to set the command attribute when the code attribute is already set" do
+ dsc_test_resource.code(configuration_code)
+ expect { dsc_test_resource.command(configuration_path) }.to raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError exception if an attempt is made to set the configuration_name attribute when the code attribute is already set" do
+ dsc_test_resource.code(configuration_code)
+ expect { dsc_test_resource.configuration_name(configuration_name) }.to raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError exception if an attempt is made to set the configuration_data attribute when the configuration_data_script attribute is already set" do
+ dsc_test_resource.configuration_data_script(configuration_data_script)
+ expect { dsc_test_resource.configuration_data(configuration_data) }.to raise_error(ArgumentError)
+ end
+
+ it "raises an ArgumentError exception if an attempt is made to set the configuration_data_script attribute when the configuration_data attribute is already set" do
+ dsc_test_resource.configuration_data(configuration_data)
+ expect { dsc_test_resource.configuration_data_script(configuration_data_script) }.to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when Powershell does not supported Dsc' do
+ ['1.0', '2.0', '3.0'].each do |version|
+ it "raises an exception for powershell version '#{version}'" do
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = version
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ dsc_test_run_context = Chef::RunContext.new(node, {}, empty_events)
+
+ expect {
+ Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
+ }.to raise_error(Chef::Exceptions::NoProviderAvailable)
+ end
+ end
+ end
+
+ context 'when Powershell is not present' do
+ let (:dsc_test_run_context) {
+ node = Chef::Node.new
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ dsc_test_run_context = Chef::RunContext.new(node, {}, empty_events)
+ }
+
+ it 'raises an exception if powershell is not present' do
+ expect {
+ Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
+ }.to raise_error(Chef::Exceptions::NoProviderAvailable)
+ end
+ end
+end
diff --git a/spec/unit/resource/homebrew_package_spec.rb b/spec/unit/resource/homebrew_package_spec.rb
new file mode 100644
index 0000000000..4b4f9afe5e
--- /dev/null
+++ b/spec/unit/resource/homebrew_package_spec.rb
@@ -0,0 +1,36 @@
+#
+# Author:: Joshua Timberman (<joshua@getchef.com>)
+# Copyright (c) 2014, Chef Software, Inc. <legal@getchef.com>
+#
+# 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::HomebrewPackage, 'initialize' do
+
+ let(:resource) { Chef::Resource::HomebrewPackage.new('emacs') }
+
+ it 'returns a Chef::Resource::HomebrewPackage' do
+ expect(resource).to be_a_kind_of(Chef::Resource::HomebrewPackage)
+ end
+
+ it 'sets the resource_name to :homebrew_package' do
+ expect(resource.resource_name).to eql(:homebrew_package)
+ end
+
+ it 'sets the provider to Chef::Provider::Package::Homebrew' do
+ expect(resource.provider).to eql(Chef::Provider::Package::Homebrew)
+ end
+
+end
diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb
index 1def10faf5..21ece2abaa 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -134,4 +134,19 @@ describe Chef::RunContext do
end
end
+ describe "handling reboot requests" do
+ let(:expected) do
+ { :reason => "spec tests require a reboot" }
+ end
+
+ it "stores and deletes the reboot request" do
+ @run_context.request_reboot(expected)
+ expect(@run_context.reboot_info).to eq(expected)
+ expect(@run_context.reboot_requested?).to be_true
+
+ @run_context.cancel_reboot
+ expect(@run_context.reboot_info).to eq({})
+ expect(@run_context.reboot_requested?).to be_false
+ end
+ end
end
diff --git a/spec/unit/util/dsc/configuration_generator_spec.rb b/spec/unit/util/dsc/configuration_generator_spec.rb
new file mode 100644
index 0000000000..03f3ffe25c
--- /dev/null
+++ b/spec/unit/util/dsc/configuration_generator_spec.rb
@@ -0,0 +1,171 @@
+#
+# Author:: Jay Mundrawala <jmundrawala@getchef.com>
+# Copyright:: Copyright (c) 2014 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 'chef'
+require 'chef/util/dsc/configuration_generator'
+
+describe Chef::Util::DSC::ConfigurationGenerator do
+ let(:conf_man) do
+ node = Chef::Node.new
+ Chef::Util::DSC::ConfigurationGenerator.new(node, 'tmp')
+ end
+
+ describe '#validate_configuration_name!' do
+ it 'should not raise an error if a name contains all upper case letters' do
+ conf_man.send(:validate_configuration_name!, "HELLO")
+ end
+
+ it 'should not raise an error if the name contains all lower case letters' do
+ conf_man.send(:validate_configuration_name!, "hello")
+ end
+
+ it 'should not raise an error if no special characters are used except _' do
+ conf_man.send(:validate_configuration_name!, "hello_world")
+ end
+
+ %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym|
+ it "raises an Argument error if it configuration name contains #{sym}" do
+ expect {
+ conf_man.send(:validate_configuration_name!, "Hello#{sym}")
+ }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe "#get_merged_configuration_flags" do
+ context 'when strings are used as switches' do
+ it 'should merge the hash if there are no restricted switches' do
+ merged = conf_man.send(:get_merged_configuration_flags!, {'flag' => 'a'}, 'hello')
+ merged.should include(:flag)
+ merged[:flag].should eql('a')
+ merged.should include(:outputpath)
+ end
+
+ it 'should raise an ArgumentError if you try to override outputpath' do
+ expect {
+ conf_man.send(:get_merged_configuration_flags!, {'outputpath' => 'a'}, 'hello')
+ }.to raise_error(ArgumentError)
+ end
+
+ it 'should be case insensitive for switches that are not allowed' do
+ expect {
+ conf_man.send(:get_merged_configuration_flags!, {'OutputPath' => 'a'}, 'hello')
+ }.to raise_error(ArgumentError)
+ end
+
+ it 'should be case insensitive to switches that are allowed' do
+ merged = conf_man.send(:get_merged_configuration_flags!, {'FLAG' => 'a'}, 'hello')
+ merged.should include(:flag)
+ end
+ end
+
+ context 'when symbols are used as switches' do
+ it 'should merge the hash if there are no restricted switches' do
+ merged = conf_man.send(:get_merged_configuration_flags!, {:flag => 'a'}, 'hello')
+ merged.should include(:flag)
+ merged[:flag].should eql('a')
+ merged.should include(:outputpath)
+ end
+
+ it 'should raise an ArgumentError if you try to override outputpath' do
+ expect {
+ conf_man.send(:get_merged_configuration_flags!, {:outputpath => 'a'}, 'hello')
+ }.to raise_error(ArgumentError)
+ end
+
+ it 'should be case insensitive for switches that are not allowed' do
+ expect {
+ conf_man.send(:get_merged_configuration_flags!, {:OutputPath => 'a'}, 'hello')
+ }.to raise_error(ArgumentError)
+ end
+
+ it 'should be case insensitive to switches that are allowed' do
+ merged = conf_man.send(:get_merged_configuration_flags!, {:FLAG => 'a'}, 'hello')
+ merged.should include(:flag)
+ end
+ end
+
+ context 'when there are no flags' do
+ it 'should supply an output path if configuration_flags is an empty hash' do
+ merged = conf_man.send(:get_merged_configuration_flags!, {}, 'hello')
+ merged.should include(:outputpath)
+ merged.length.should eql(1)
+ end
+
+ it 'should supply an output path if configuration_flags is an empty hash' do
+ merged = conf_man.send(:get_merged_configuration_flags!, nil, 'hello')
+ merged.should include(:outputpath)
+ merged.length.should eql(1)
+ end
+ end
+
+ # What should happen if configuration flags contains duplicates?
+ # flagA => 'a', flaga => 'a'
+ # or
+ # flagA => 'a', flaga => 'b'
+ #
+ end
+
+ describe '#write_document_generation_script' do
+ let(:file_like_object) { double("file like object") }
+
+ it "should write the input to a file" do
+ allow(File).to receive(:open).and_yield(file_like_object)
+ allow(File).to receive(:join) do |a, b|
+ [a,b].join("++")
+ end
+ allow(file_like_object).to receive(:write)
+ conf_man.send(:write_document_generation_script, 'file', 'hello')
+ expect(file_like_object).to have_received(:write)
+ end
+ end
+
+ describe "#find_configuration_document" do
+ it "should find the mof file" do
+ # These tests seem way too implementation specific. Unfortunatly, File and Dir
+ # need to be mocked because they are OS specific
+ allow(File).to receive(:join) do |a, b|
+ [a,b].join("++")
+ end
+
+ allow(Dir).to receive(:entries).with("tmp++hello") {['f1', 'f2', 'hello.mof', 'f3']}
+ expect(conf_man.send(:find_configuration_document, 'hello')).to eql('tmp++hello++hello.mof')
+ end
+
+ it "should return nil if the mof file is not found" do
+ allow(File).to receive(:join) do |a, b|
+ [a,b].join("++")
+ end
+ allow(Dir).to receive(:entries).with("tmp++hello") {['f1', 'f2', 'f3']}
+ expect(conf_man.send(:find_configuration_document, 'hello')).to be_nil
+ end
+ end
+
+ describe "#configuration_code" do
+ it "should build dsc" do
+ dsc = conf_man.send(:configuration_code, 'archive{}', 'hello')
+ found_configuration = false
+ dsc.split(';').each do |command|
+ if command.downcase =~ /\s*configuration\s+'hello'\s*\{\s*node\s+'localhost'\s*\{\s*archive\s*\{\s*\}\s*\}\s*\}\s*/
+ found_configuration = true
+ end
+ end
+ expect(found_configuration).to be_true
+ end
+ end
+end
diff --git a/spec/unit/util/dsc/lcm_output_parser_spec.rb b/spec/unit/util/dsc/lcm_output_parser_spec.rb
new file mode 100644
index 0000000000..23a3dbd3ec
--- /dev/null
+++ b/spec/unit/util/dsc/lcm_output_parser_spec.rb
@@ -0,0 +1,169 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 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 'chef/util/dsc/lcm_output_parser'
+
+describe Chef::Util::DSC::LocalConfigurationManager::Parser do
+ context 'empty input parameter' do
+ it 'returns an empty array for a 0 length string' do
+ Chef::Util::DSC::LocalConfigurationManager::Parser::parse('').should be_empty
+ end
+
+ it 'returns an empty array for a nil input' do
+ Chef::Util::DSC::LocalConfigurationManager::Parser::parse('').should be_empty
+ end
+ end
+
+ context 'correctly formatted output from lcm' do
+ it 'returns an empty array for a log with no resources' do
+ str = <<EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+ Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str).should be_empty
+ end
+
+ it 'returns a single resource when only 1 logged with the correct name' do
+ str = <<EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ End Resource ] [name]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources.length.should eq(1)
+ resources[0].name.should eq('[name]')
+ end
+
+ it 'identifies when a resource changes the state of the system' do
+ str = <<EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ Start Set ] [name]
+logtype: [machinename]: LCM: [ End Set ] [name]
+logtype: [machinename]: LCM: [ End Resource ] [name]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources[0].changes_state?.should be_true
+ end
+
+ it 'preserves the log provided for how the system changed the state' do
+ str = <<EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ Start Set ] [name]
+logtype: [machinename]: [message]
+logtype: [machinename]: LCM: [ End Set ] [name]
+logtype: [machinename]: LCM: [ End Resource ] [name]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources[0].change_log.should match_array(["[name]","[message]","[name]"])
+ end
+
+ it 'should return false for changes_state?' do
+ str = <<EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ Skip Set ] [name]
+logtype: [machinename]: LCM: [ End Resource ] [name]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources[0].changes_state?.should be_false
+ end
+
+ it 'should return an empty array for change_log if changes_state? is false' do
+ str = <<EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ Skip Set ] [name]
+logtype: [machinename]: LCM: [ End Resource ] [name]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources[0].change_log.should be_empty
+ end
+ end
+
+ context 'Incorrectly formatted output from LCM' do
+ it 'should allow missing a [End Resource] when its the last one and still find all the resource' do
+ str = <<-EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ Start Test ]
+logtype: [machinename]: LCM: [ End Test ]
+logtype: [machinename]: LCM: [ Skip Set ]
+logtype: [machinename]: LCM: [ End Resource ]
+logtype: [machinename]: LCM: [ Start Resource ] [name2]
+logtype: [machinename]: LCM: [ Start Test ]
+logtype: [machinename]: LCM: [ End Test ]
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ End Set ]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources[0].changes_state?.should be_false
+ resources[1].changes_state?.should be_true
+ end
+
+ it 'should allow missing a [End Resource] when its the first one and still find all the resource' do
+ str = <<-EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ Start Test ]
+logtype: [machinename]: LCM: [ End Test ]
+logtype: [machinename]: LCM: [ Skip Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name2]
+logtype: [machinename]: LCM: [ Start Test ]
+logtype: [machinename]: LCM: [ End Test ]
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ End Set ]
+logtype: [machinename]: LCM: [ End Resource ]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources[0].changes_state?.should be_false
+ resources[1].changes_state?.should be_true
+ end
+
+ it 'should allow missing set and end resource and assume an unconverged resource in this case' do
+ str = <<-EOF
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ Start Test ]
+logtype: [machinename]: LCM: [ End Test ]
+logtype: [machinename]: LCM: [ Start Resource ] [name2]
+logtype: [machinename]: LCM: [ Start Test ]
+logtype: [machinename]: LCM: [ End Test ]
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ End Set ]
+logtype: [machinename]: LCM: [ End Resource ]
+logtype: [machinename]: LCM: [ End Set ]
+EOF
+ resources = Chef::Util::DSC::LocalConfigurationManager::Parser::parse(str)
+ resources[0].changes_state?.should be_true
+ resources[0].name.should eql('[name]')
+ resources[1].changes_state?.should be_true
+ resources[1].name.should eql('[name2]')
+ end
+ end
+end
diff --git a/spec/unit/util/dsc/local_configuration_manager_spec.rb b/spec/unit/util/dsc/local_configuration_manager_spec.rb
new file mode 100644
index 0000000000..fb6664bd40
--- /dev/null
+++ b/spec/unit/util/dsc/local_configuration_manager_spec.rb
@@ -0,0 +1,134 @@
+#
+# Author:: Adam Edwards <adamed@getchef.com>
+# Copyright:: Copyright (c) 2014 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 'chef'
+require 'chef/util/dsc/local_configuration_manager'
+
+describe Chef::Util::DSC::LocalConfigurationManager do
+
+ let(:lcm) { Chef::Util::DSC::LocalConfigurationManager.new(nil, 'tmp') }
+
+ let(:normal_lcm_output) { <<-EOH
+logtype: [machinename]: LCM: [ Start Set ]
+logtype: [machinename]: LCM: [ Start Resource ] [name]
+logtype: [machinename]: LCM: [ End Resource ] [name]
+logtype: [machinename]: LCM: [ End Set ]
+EOH
+ }
+
+ let(:no_whatif_lcm_output) { <<-EOH
+Start-DscConfiguration : A parameter cannot be found that matches parameter name 'whatif'.
+At line:1 char:123
++ run-somecommand -whatif
++ ~~~~~~~~
+ + CategoryInfo : InvalidArgument: (:) [Start-DscConfiguration], ParameterBindingException
+ + FullyQualifiedErrorId : NamedParameterNotFound,SomeCompany.SomeAssembly.Commands.RunSomeCommand
+EOH
+ }
+
+ let(:dsc_resource_import_failure_output) { <<-EOH
+PowerShell provider MSFT_xWebsite failed to execute Test-TargetResource functionality with error message: Please ensure that WebAdministration module is installed. + CategoryInfo : InvalidOperation: (:) [], CimException + FullyQualifiedErrorId : ProviderOperationExecutionFailure + PSComputerName : . PowerShell provider MSFT_xWebsite failed to execute Test-TargetResource functionality with error message: Please ensure that WebAdministration module is installed. + CategoryInfo : InvalidOperation: (:) [], CimException + FullyQualifiedErrorId : ProviderOperationExecutionFailure + PSComputerName : . The SendConfigurationApply function did not succeed. + CategoryInfo : NotSpecified: (root/Microsoft/...gurationManager:String) [], CimException + FullyQualifiedErrorId : MI RESULT 1 + PSComputerName : .
+EOH
+ }
+
+ let(:lcm_status) {
+ double("LCM cmdlet status", :stderr => lcm_standard_error, :return_value => lcm_standard_output, :succeeded? => lcm_cmdlet_success)
+ }
+
+ describe 'test_configuration method invocation' do
+ context 'when interacting with the LCM using a PowerShell cmdlet' do
+ before(:each) do
+ allow(lcm).to receive(:run_configuration_cmdlet).and_return(lcm_status)
+ end
+ context 'that returns successfully' do
+ before(:each) do
+ allow(lcm).to receive(:run_configuration_cmdlet).and_return(lcm_status)
+ end
+
+ let(:lcm_standard_output) { normal_lcm_output }
+ let(:lcm_standard_error) { nil }
+ let(:lcm_cmdlet_success) { true }
+
+ it 'should successfully return resource information for normally formatted output when cmdlet the cmdlet succeeds' do
+ test_configuration_result = lcm.test_configuration('config')
+ expect(test_configuration_result.class).to be(Array)
+ expect(test_configuration_result.length).to be > 0
+ expect(Chef::Log).not_to receive(:warn)
+ end
+ end
+
+ context 'that fails due to missing what-if switch in DSC resource cmdlet implementation' do
+ let(:lcm_standard_output) { '' }
+ let(:lcm_standard_error) { no_whatif_lcm_output }
+ let(:lcm_cmdlet_success) { false }
+
+ it 'should should return a (possibly empty) array of ResourceInfo instances' do
+ expect(Chef::Log).to receive(:warn)
+ test_configuration_result = nil
+ expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error
+ expect(test_configuration_result.class).to be(Array)
+ end
+ end
+
+ context 'that fails due to a DSC resource not being imported before StartDSCConfiguration -whatif is executed' do
+ let(:lcm_standard_output) { '' }
+ let(:lcm_standard_error) { dsc_resource_import_failure_output }
+ let(:lcm_cmdlet_success) { false }
+
+ it 'should log a warning if the message is formatted as expected when a resource import failure occurs' do
+ expect(Chef::Log).to receive(:warn)
+ expect(lcm).to receive(:output_has_dsc_module_failure?).and_call_original
+ test_configuration_result = nil
+ expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error
+ end
+
+ it 'should return a (possibly empty) array of ResourceInfo instances' do
+ expect(Chef::Log).to receive(:warn)
+ test_configuration_result = nil
+ expect {test_configuration_result = lcm.test_configuration('config')}.not_to raise_error
+ expect(test_configuration_result.class).to be(Array)
+ end
+ end
+
+ context 'that fails due to an PowerShell cmdlet error that cannot be handled' do
+ let(:lcm_standard_output) { 'some output' }
+ let(:lcm_standard_error) { 'Abort, Retry, Fail?' }
+ let(:lcm_cmdlet_success) { false }
+
+ it 'should raise a Chef::Exceptions::PowershellCmdletException' do
+ expect(Chef::Log).not_to receive(:warn)
+ expect(lcm).to receive(:output_has_dsc_module_failure?).and_call_original
+ expect {lcm.test_configuration('config')}.to raise_error(Chef::Exceptions::PowershellCmdletException)
+ end
+ end
+ end
+
+ it 'should identify a correctly formatted error message as a resource import failure' do
+ expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output)).to be(true)
+ end
+
+ it 'should not identify an incorrectly formatted error message as a resource import failure' do
+ expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output.gsub('module', 'gibberish'))).to be(false)
+ end
+
+ it 'should not identify a message without a CimException reference as a resource import failure' do
+ expect(lcm.send(:output_has_dsc_module_failure?, dsc_resource_import_failure_output.gsub('CimException', 'ArgumentException'))).to be(false)
+ end
+ end
+end
+
diff --git a/spec/unit/util/path_helper_spec.rb b/spec/unit/util/path_helper_spec.rb
index 66ad323c52..1d97efc607 100644
--- a/spec/unit/util/path_helper_spec.rb
+++ b/spec/unit/util/path_helper_spec.rb
@@ -214,6 +214,28 @@ describe Chef::Util::PathHelper do
PathHelper.stub(:canonical_path).with("bandit").and_return("c:/Bo/Bandit")
PathHelper.stub(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit")
expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_false
- end
+ end
+ end
+
+ describe "escape_glob" do
+ it "escapes characters reserved by glob" do
+ path = "C:\\this\\*path\\[needs]\\escaping?"
+ escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
+ expect(PathHelper.escape_glob(path)).to eq(escaped_path)
+ end
+
+ context "when given more than one argument" do
+ it "joins, cleanpaths, and escapes characters reserved by glob" do
+ args = ["this/*path", "[needs]", "escaping?"]
+ escaped_path = if windows?
+ "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
+ else
+ "this/\\*path/\\[needs\\]/escaping\\?"
+ end
+ expect(PathHelper).to receive(:join).with(*args).and_call_original
+ expect(PathHelper).to receive(:cleanpath).and_call_original
+ expect(PathHelper.escape_glob(*args)).to eq(escaped_path)
+ end
+ end
end
end
diff --git a/spec/unit/util/powershell/cmdlet_spec.rb b/spec/unit/util/powershell/cmdlet_spec.rb
new file mode 100644
index 0000000000..a964f607c8
--- /dev/null
+++ b/spec/unit/util/powershell/cmdlet_spec.rb
@@ -0,0 +1,106 @@
+#
+# Author:: Jay Mundrawala <jdm@getchef.com>
+# Copyright:: Copyright (c) 2014 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 'chef'
+require 'chef/util/powershell/cmdlet'
+
+describe Chef::Util::Powershell::Cmdlet do
+ before (:all) do
+ @node = Chef::Node.new
+ @cmdlet = Chef::Util::Powershell::Cmdlet.new(@node, 'Some-Commandlet')
+ end
+
+ describe '#validate_switch_name!' do
+ it 'should not raise an error if a name contains all upper case letters' do
+ @cmdlet.send(:validate_switch_name!, "HELLO")
+ end
+
+ it 'should not raise an error if the name contains all lower case letters' do
+ @cmdlet.send(:validate_switch_name!, "hello")
+ end
+
+ it 'should not raise an error if no special characters are used except _' do
+ @cmdlet.send(:validate_switch_name!, "hello_world")
+ end
+
+ %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym|
+ it "raises an Argument error if it configuration name contains #{sym}" do
+ expect {
+ @cmdlet.send(:validate_switch_name!, "Hello#{sym}")
+ }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ describe '#escape_parameter_value' do
+ # Is this list really complete?
+ %w{` " # '}.each do |c|
+ it "escapse #{c}" do
+ @cmdlet.send(:escape_parameter_value, "stuff #{c}").should eql("stuff `#{c}")
+ end
+ end
+
+ it 'does not do anything to a string without special characters' do
+ @cmdlet.send(:escape_parameter_value, 'stuff').should eql('stuff')
+ end
+ end
+
+ describe '#escape_string_parameter_value' do
+ it "surrounds a string with ''" do
+ @cmdlet.send(:escape_string_parameter_value, 'stuff').should eql("'stuff'")
+ end
+ end
+
+ describe '#command_switches_string' do
+ it 'raises an ArgumentError if the key is not a symbol' do
+ expect {
+ @cmdlet.send(:command_switches_string, {'foo' => 'bar'})
+ }.to raise_error(ArgumentError)
+ end
+
+ it 'does not allow invalid switch names' do
+ expect {
+ @cmdlet.send(:command_switches_string, {:foo! => 'bar'})
+ }.to raise_error(ArgumentError)
+ end
+
+ it 'ignores switches with a false value' do
+ @cmdlet.send(:command_switches_string, {foo: false}).should eql('')
+ end
+
+ it 'should correctly handle a value type of string' do
+ @cmdlet.send(:command_switches_string, {foo: 'bar'}).should eql("-foo 'bar'")
+ end
+
+ it 'should correctly handle a value type of string even when it is 0 length' do
+ @cmdlet.send(:command_switches_string, {foo: ''}).should eql("-foo ''")
+ end
+
+ it 'should not quote integers' do
+ @cmdlet.send(:command_switches_string, {foo: 1}).should eql("-foo 1")
+ end
+
+ it 'should not quote floats' do
+ @cmdlet.send(:command_switches_string, {foo: 1.0}).should eql("-foo 1.0")
+ end
+
+ it 'has just the switch when the value is true' do
+ @cmdlet.send(:command_switches_string, {foo: true}).should eql("-foo")
+ end
+ end
+end
diff --git a/spec/unit/workstation_config_loader_spec.rb b/spec/unit/workstation_config_loader_spec.rb
index 78313aec37..de108ff6d7 100644
--- a/spec/unit/workstation_config_loader_spec.rb
+++ b/spec/unit/workstation_config_loader_spec.rb
@@ -88,7 +88,12 @@ describe Chef::WorkstationConfigLoader do
let(:env_pwd) { "/path/to/cwd" }
before do
- env["PWD"] = env_pwd
+ if Chef::Platform.windows?
+ env["CD"] = env_pwd
+ else
+ env["PWD"] = env_pwd
+ end
+
allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/knife.rb").and_return(true)
allow(File).to receive(:exist?).with("#{env_pwd}/.chef").and_return(true)
allow(File).to receive(:directory?).with("#{env_pwd}/.chef").and_return(true)
@@ -229,13 +234,14 @@ describe Chef::WorkstationConfigLoader do
let(:config_content) { "" }
let(:explicit_config_location) do
- t = Tempfile.new("#{described_class}-rspec-test")
+ # could use described_class, but remove all ':' from the path if so.
+ t = Tempfile.new("Chef-WorkstationConfigLoader-rspec-test")
t.print(config_content)
t.close
t.path
end
- after { File.unlink(explicit_config_location) }
+ after { File.unlink(explicit_config_location) if File.exists?(explicit_config_location) }
context "and is valid" do