From f246fe546c965720eadadc01c7318569d2795aba Mon Sep 17 00:00:00 2001 From: James FitzGibbon Date: Wed, 4 Mar 2015 15:47:25 -0800 Subject: Add warnings to 'knife node run list remove ...' when the item to be removed doesn't exist --- lib/chef/knife/node_run_list_remove.rb | 13 ++++++++++++- spec/unit/knife/node_run_list_remove_spec.rb | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/chef/knife/node_run_list_remove.rb b/lib/chef/knife/node_run_list_remove.rb index 4b8953a264..ef03c176b8 100644 --- a/lib/chef/knife/node_run_list_remove.rb +++ b/lib/chef/knife/node_run_list_remove.rb @@ -42,7 +42,18 @@ class Chef entries = @name_args[1].split(',').map { |e| e.strip } end - entries.each { |e| node.run_list.remove(e) } + # iterate over the list of things to remove, + # warning if one of them was not found + entries.each do |e| + if node.run_list.find { |rli| e == rli.to_s } + node.run_list.remove(e) + else + ui.warn "#{e} is not in the run list" + unless e =~ /^(recipe|role)\[/ + ui.warn '(did you forget recipe[] or role[] around it?)' + end + end + end node.save diff --git a/spec/unit/knife/node_run_list_remove_spec.rb b/spec/unit/knife/node_run_list_remove_spec.rb index ceceef7178..a279a59635 100644 --- a/spec/unit/knife/node_run_list_remove_spec.rb +++ b/spec/unit/knife/node_run_list_remove_spec.rb @@ -84,6 +84,23 @@ describe Chef::Knife::NodeRunListRemove do expect(@node.run_list).not_to include('role[monkey]') expect(@node.run_list).not_to include('recipe[duck::type]') end + + it "should warn when the thing to remove is not in the runlist" do + @node.run_list << 'role[blah]' + @node.run_list << 'recipe[duck::type]' + @knife.name_args = [ 'adam', 'role[blork]' ] + expect(@knife.ui).to receive(:warn).with("role[blork] is not in the run list") + @knife.run + end + + it "should warn even more when the thing to remove is not in the runlist and unqualified" do + @node.run_list << 'role[blah]' + @node.run_list << 'recipe[duck::type]' + @knife.name_args = [ 'adam', 'blork' ] + expect(@knife.ui).to receive(:warn).with("blork is not in the run list") + expect(@knife.ui).to receive(:warn).with(/did you forget recipe\[\] or role\[\]/) + @knife.run + end end end end -- cgit v1.2.1 From 4cdce72a39b6f2e9ad0eb3029d861c99494738f0 Mon Sep 17 00:00:00 2001 From: Sean Walberg Date: Sun, 5 Apr 2015 00:10:57 -0500 Subject: Allow tags to be set on a node during bootstrap I can set attributes, environment, and run_list, but why not tags? Some of our recipes are driven off of tags, such as for sudo access or membership in a load balancing pool. If this patch is accepted then I will add the corresponding feature to knife-vsphere. This patch only works with the new vault method, not the validator key method. If someone has some pointers on where to add it in the validator method I'd be happy to amend. --- lib/chef/knife/bootstrap.rb | 6 ++++++ lib/chef/knife/bootstrap/client_builder.rb | 3 +++ spec/unit/knife/bootstrap/client_builder_spec.rb | 16 ++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index a4095e8402..157c815a98 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -143,6 +143,12 @@ class Chef :proc => lambda { |o| o.split(/[\s,]+/) }, :default => [] + option :tags, + :long => "--tags TAGS", + :description => "Comma separated list of tags to apply to the node", + :proc => lambda { |o| o.split(/[\s,]+/) }, + :default => [] + option :first_boot_attributes, :short => "-j JSON_ATTRIBS", :long => "--json-attributes", diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb index b9c1d98bec..32258f6fa5 100644 --- a/lib/chef/knife/bootstrap/client_builder.rb +++ b/lib/chef/knife/bootstrap/client_builder.rb @@ -140,6 +140,9 @@ class Chef node.run_list(normalized_run_list) node.normal_attrs = first_boot_attributes if first_boot_attributes node.environment(environment) if environment + (knife_config[:tags] || []).each do |tag| + node.tags << tag + end node end end diff --git a/spec/unit/knife/bootstrap/client_builder_spec.rb b/spec/unit/knife/bootstrap/client_builder_spec.rb index e6aa307c7e..930ae8c9d3 100644 --- a/spec/unit/knife/bootstrap/client_builder_spec.rb +++ b/spec/unit/knife/bootstrap/client_builder_spec.rb @@ -149,6 +149,22 @@ describe Chef::Knife::Bootstrap::ClientBuilder do client_builder.run end + it "does not add tags by default" do + allow(node).to receive(:run_list).with([]) + expect(node).to_not receive(:tags) + client_builder.run + end + + it "adds tags to the node when given" do + tag_receiver = [] + + knife_config[:tags] = %w[foo bar] + allow(node).to receive(:run_list).with([]) + allow(node).to receive(:tags).and_return(tag_receiver) + client_builder.run + expect(tag_receiver).to eq %w[foo bar] + end + it "builds a node when the run_list is a string" do knife_config[:run_list] = "role[base],role[app]" expect(node).to receive(:run_list).with(["role[base]", "role[app]"]) -- cgit v1.2.1 From f6835a4f5af4a380d7e35e32ddd8c3a0fe7f2ac3 Mon Sep 17 00:00:00 2001 From: Sean Walberg Date: Thu, 9 Apr 2015 09:54:49 -0500 Subject: If tags are present, add to the first-boot.json Thanks to @coderanger from IRC for pointing me in the right direction. I also snuck in a whitespace fix --- lib/chef/knife/core/bootstrap_context.rb | 7 +++++-- spec/unit/knife/core/bootstrap_context_spec.rb | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 7197653489..867b6fe366 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -163,11 +163,14 @@ CONFIG end def first_boot - (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list) + (@config[:first_boot_attributes] || {}).tap do |attributes| + attributes.merge!(:run_list => @run_list) + attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty? + end end private - + # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped # This string should contain both the commands necessary to both create the files, as well as their content def trusted_certs_content diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 3718cb228c..268b61c756 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -97,6 +97,13 @@ EXPECTED end end + describe "when tags are given" do + let(:config) { {:tags => [ "unicorn" ] } } + it "adds the attributes to first_boot" do + expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({:run_list => run_list, :tags => ["unicorn"]})) + end + end + describe "when JSON attributes are given" do let(:config) { {:first_boot_attributes => {:baz => :quux}} } it "adds the attributes to first_boot" do -- cgit v1.2.1 From 841b973d59382192058d05f7a5d46c8e1ea1a875 Mon Sep 17 00:00:00 2001 From: cmluciano Date: Fri, 20 Mar 2015 16:19:04 -0400 Subject: Fix condition of removing a group before user error. This should fix [issue 1586](https://github.com/chef/chef/issues/1586). The issue is that if a group is removed before the user, the GID remained and causes an error. This should remove the check for remove actions. --- lib/chef/provider/user.rb | 2 +- spec/unit/http_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb index f6ac72448e..de261105b3 100644 --- a/lib/chef/provider/user.rb +++ b/lib/chef/provider/user.rb @@ -90,7 +90,7 @@ class Chef end def define_resource_requirements - requirements.assert(:all_actions) do |a| + requirements.assert(:create, :modify, :manage, :lock, :unlock) do |a| a.assertion { @group_name_resolved } a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}" a.whyrun "group name #{@new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously." diff --git a/spec/unit/http_spec.rb b/spec/unit/http_spec.rb index 4d851df951..a654d14aa2 100644 --- a/spec/unit/http_spec.rb +++ b/spec/unit/http_spec.rb @@ -89,4 +89,4 @@ describe Chef::HTTP do end # head -end +end \ No newline at end of file -- cgit v1.2.1 From 1162606a8d5f7be3a7af9526732fe8cb61f9c96b Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Mon, 1 Jun 2015 16:40:47 -0700 Subject: Allow creation of secure strings over remoting/services The issue here is the user profile does not get loaded when running over winrm / as a service. This user profile is needed to use the DPAPI to encrypt the data for the current user. Should fix #3246 --- lib/chef/win32/crypto.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb index 79cf51b002..a047071b79 100644 --- a/lib/chef/win32/crypto.rb +++ b/lib/chef/win32/crypto.rb @@ -29,7 +29,7 @@ class Chef def self.encrypt(str, &block) data_blob = CRYPT_INTEGER_BLOB.new - unless CryptProtectData(CRYPT_INTEGER_BLOB.new(str.to_wstring), nil, nil, nil, nil, 0, data_blob) + unless CryptProtectData(CRYPT_INTEGER_BLOB.new(str.to_wstring), nil, nil, nil, nil, CRYPTPROTECT_LOCAL_MACHINE, data_blob) Chef::ReservedNames::Win32::Error.raise! end bytes = data_blob[:pbData].get_bytes(0, data_blob[:cbData]) -- cgit v1.2.1 From d9820c430b686a1dedd4ab2cadd20b0cadbbb0d7 Mon Sep 17 00:00:00 2001 From: Kaushik C Date: Tue, 9 Jun 2015 16:00:06 -0400 Subject: Fixing Issue #2513 - the broken render of nested partial templates with variables Authors: Kaushik C , Sam Dunne --- lib/chef/mixin/template.rb | 1 + .../openldap/templates/default/nested_openldap_partials.erb | 1 + spec/data/cookbooks/openldap/templates/default/nested_partial.erb | 1 + spec/unit/cookbook/syntax_check_spec.rb | 2 ++ spec/unit/mixin/template_spec.rb | 6 +++++- 5 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb create mode 100644 spec/data/cookbooks/openldap/templates/default/nested_partial.erb diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb index d705a9e7be..9b35bbcc33 100644 --- a/lib/chef/mixin/template.rb +++ b/lib/chef/mixin/template.rb @@ -89,6 +89,7 @@ class Chef raise "You cannot render partials in this context" unless @template_finder partial_variables = options.delete(:variables) || _public_instance_variables + partial_variables[:template_finder] = @template_finder partial_context = self.class.new(partial_variables) partial_context._extend_modules(@_extension_modules) diff --git a/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb b/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb new file mode 100644 index 0000000000..2d356ec21d --- /dev/null +++ b/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb @@ -0,0 +1 @@ +before <%= render 'nested_partial.erb', :variables => { :hello => @test } %> after diff --git a/spec/data/cookbooks/openldap/templates/default/nested_partial.erb b/spec/data/cookbooks/openldap/templates/default/nested_partial.erb new file mode 100644 index 0000000000..415646ca31 --- /dev/null +++ b/spec/data/cookbooks/openldap/templates/default/nested_partial.erb @@ -0,0 +1 @@ +{<%= @hello %>} diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb index ee4e0bed02..318c2a7e85 100644 --- a/spec/unit/cookbook/syntax_check_spec.rb +++ b/spec/unit/cookbook/syntax_check_spec.rb @@ -54,6 +54,8 @@ describe Chef::Cookbook::SyntaxCheck do basenames = %w{ helpers_via_partial_test.erb helper_test.erb openldap_stuff.conf.erb + nested_openldap_partials.erb + nested_partial.erb openldap_variable_stuff.conf.erb test.erb some_windows_line_endings.erb diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb index 6a867b5f9a..95d0eb6711 100644 --- a/spec/unit/mixin/template_spec.rb +++ b/spec/unit/mixin/template_spec.rb @@ -150,6 +150,11 @@ describe Chef::Mixin::Template, "render_template" do output == "before {super secret is candy} after" end + it "should pass the template finder to the partials" do + output = @template_context.render_template_from_string("before {<%= render 'nested_openldap_partials.erb', :variables => {:hello => 'Hello World!' } %>} after") + output == "before {Hello World!} after" + end + it "should pass variables to partials" do output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'whatever' } %>} after") expect(output).to eq("before {super secret is whatever} after") @@ -266,4 +271,3 @@ describe Chef::Mixin::Template, "render_template" do end end end - -- cgit v1.2.1 From c93f286bb7c187522a1294ea8e52a5aa04273162 Mon Sep 17 00:00:00 2001 From: Nitz Date: Tue, 31 Mar 2015 18:12:35 +0300 Subject: Migrated deploy resource to use shell_out instead of run_command Should help with troubleshooting failed migrations --- lib/chef/provider/deploy.rb | 4 ++-- spec/functional/resource/deploy_revision_spec.rb | 2 +- spec/unit/provider/deploy_spec.rb | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb index 6d9b7f4397..77a0410593 100644 --- a/lib/chef/provider/deploy.rb +++ b/lib/chef/provider/deploy.rb @@ -201,7 +201,7 @@ class Chef converge_by("execute migration command #{@new_resource.migration_command}") do Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}" - run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info)) + shell_out!(@new_resource.migration_command,run_options(:cwd=>release_path, :log_level => :info)) end end end @@ -221,7 +221,7 @@ class Chef else converge_by("restart app using command #{@new_resource.restart_command}") do Chef::Log.info("#{@new_resource} restarting app") - run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path)) + shell_out!(@new_resource.restart_command,run_options(:cwd=>@new_resource.current_path)) end end end diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb index e5f5341fcd..4bce309a51 100644 --- a/spec/functional/resource/deploy_revision_spec.rb +++ b/spec/functional/resource/deploy_revision_spec.rb @@ -819,7 +819,7 @@ describe Chef::Resource::DeployRevision, :unix_only => true do end before do - expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Chef::Exceptions::Exec) + expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) deploy_to_latest_with_callback_tracking.run_action(:deploy) end diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb index f6bb78823f..e6a7125e32 100644 --- a/spec/unit/provider/deploy_spec.rb +++ b/spec/unit/provider/deploy_spec.rb @@ -362,7 +362,7 @@ describe Chef::Provider::Deploy do it "skips the migration when resource.migrate => false but runs symlinks before migration" do @resource.migrate false - expect(@provider).not_to receive :run_command + expect(@provider).not_to receive :shell_out! expect(@provider).to receive :run_symlinks_before_migrate @provider.migrate end @@ -378,7 +378,7 @@ describe Chef::Provider::Deploy do allow(STDOUT).to receive(:tty?).and_return(true) allow(Chef::Log).to receive(:info?).and_return(true) - expect(@provider).to receive(:run_command).with(:command => "migration_foo", :cwd => @expected_release_dir, + expect(@provider).to receive(:shell_out!).with("migration_foo",:cwd => @expected_release_dir, :user => "deployNinja", :group => "deployNinjas", :log_level => :info, :live_stream => STDOUT, :log_tag => "deploy[/my/deploy/dir]", @@ -445,13 +445,13 @@ describe Chef::Provider::Deploy do end it "does nothing for restart if restart_command is empty" do - expect(@provider).not_to receive(:run_command) + expect(@provider).not_to receive(:shell_out!) @provider.restart end it "runs the restart command in the current application dir when the resource has a restart_command" do @resource.restart_command "restartcmd" - expect(@provider).to receive(:run_command).with(:command => "restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug) + expect(@provider).to receive(:shell_out!).with("restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug) @provider.restart end @@ -509,7 +509,7 @@ describe Chef::Provider::Deploy do it "shouldn't give a no method error on migrate if the environment is nil" do allow(@provider).to receive(:enforce_ownership) allow(@provider).to receive(:run_symlinks_before_migrate) - allow(@provider).to receive(:run_command) + allow(@provider).to receive(:shell_out!) @provider.migrate end -- cgit v1.2.1 From 9022f929ea74930b4b581edd2a08cd67025c6b68 Mon Sep 17 00:00:00 2001 From: Martin Smith Date: Fri, 21 Aug 2015 11:37:45 -0500 Subject: Further revision for compile errors due to frozen Per the discussion at the bottom of https://github.com/chef/chef/pull/3757, doing some further refinement of compile_error_inspector's message regarding frozen objects. Helps further address the original issue https://github.com/chef/chef/issues/3734. --- .../error_inspectors/compile_error_inspector.rb | 30 ++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb index fe418ed485..3c22d2e763 100644 --- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb +++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb @@ -47,11 +47,31 @@ class Chef if exception_message_modifying_frozen? msg = <<-MESSAGE - Chef calls the freeze method on certain ruby objects to prevent - pollution across multiple instances. Specifically, resource - properties have frozen default values to avoid modifying the - property for all instances of a resource. Try modifying the - particular instance variable or using an instance accessor instead. + Ruby objects are often frozen to prevent further modifications + when they would negatively impact the process (e.g. values inside + Ruby's ENV class) or to prevent polluting other objects when default + values are passed by reference to many instances of an object (e.g. + the empty Array as a Chef resource default, passed by reference + to every instance of the resource). + + Chef uses Object#freeze to ensure the default values of properties + inside Chef resources are not modified, so that when a new instance + of a Chef resource is created, and Object#dup copies values by + reference, the new resource is not receiving a default value that + has been by a previous instance of that resource. + + Instead of modifying an object that contains a default value for all + instances of a Chef resource, create a new object and assign it to + the resource's parameter, e.g.: + + fruit_basket = resource(:fruit_basket, 'default') + + # BAD: modifies 'contents' object for all new fruit_basket instances + fruit_basket.contents << 'apple' + + # GOOD: allocates new array only owned by this fruit_basket instance + fruit_basket.contents %w(apple) + MESSAGE error_description.section("Additional information:", msg.gsub(/^ {6}/, '')) -- cgit v1.2.1 From 01419e6c445b7e92912d6f501f7e22926472e4d8 Mon Sep 17 00:00:00 2001 From: Simon Detheridge Date: Thu, 20 Aug 2015 17:27:15 +0100 Subject: Replace output_of_command with shell_out! in subversion provider output_of_command doesn't work properly on Windows (see chef/chef#1533) --- lib/chef/provider/subversion.rb | 20 ++++++--- lib/chef/resource/subversion.rb | 5 +++ spec/unit/provider/subversion_spec.rb | 81 +++++++++++++++++++++-------------- spec/unit/resource/subversion_spec.rb | 4 ++ 4 files changed, 72 insertions(+), 38 deletions(-) diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb index 5f36483c32..e3e3d5158a 100644 --- a/lib/chef/provider/subversion.rb +++ b/lib/chef/provider/subversion.rb @@ -130,8 +130,8 @@ class Chef @new_resource.revision else command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}") - status, svn_info, error_message = output_of_command(command, run_options) - handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}") + svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0,1])).stdout + extract_revision_info(svn_info) end end @@ -142,11 +142,8 @@ class Chef def find_current_revision return nil unless ::File.exist?(::File.join(@new_resource.destination, ".svn")) command = scm(:info) - status, svn_info, error_message = output_of_command(command, run_options(:cwd => cwd)) + svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0,1])).stdout - unless [0,1].include?(status.exitstatus) - handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}") - end extract_revision_info(svn_info) end @@ -180,6 +177,7 @@ class Chef attrs end rev = (repo_attrs['Last Changed Rev'] || repo_attrs['Revision']) + rev.strip! if rev raise "Could not parse `svn info` data: #{svn_info}" if repo_attrs.empty? Chef::Log.debug "#{@new_resource} resolved revision #{@new_resource.revision} to #{rev}" rev @@ -197,12 +195,20 @@ class Chef end def scm(*args) - ['svn', *args].compact.join(" ") + binary = svn_binary + binary = "\"#{binary}\"" if binary =~ /\s/ + [binary, *args].compact.join(" ") end def target_dir_non_existent_or_empty? !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..'] end + + def svn_binary + @new_resource.svn_binary || + (Chef::Platform.windows? ? 'svn.exe' : 'svn') + end + def assert_target_directory_valid! target_parent_directory = ::File.dirname(@new_resource.destination) unless ::File.directory?(target_parent_directory) diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb index ae6a37caa2..a6f4cb4897 100644 --- a/lib/chef/resource/subversion.rb +++ b/lib/chef/resource/subversion.rb @@ -28,12 +28,17 @@ class Chef super @svn_arguments = '--no-auth-cache' @svn_info_args = '--no-auth-cache' + @svn_binary = nil end # Override exception to strip password if any, so it won't appear in logs and different Chef notifications def custom_exception_message(e) "#{self} (#{defined_at}) had an error: #{e.class.name}: #{svn_password ? e.message.gsub(svn_password, "[hidden_password]") : e.message}" end + + def svn_binary(arg=nil) + set_or_return(:svn_binary, arg, :kind_of => [String]) + end end end end diff --git a/spec/unit/provider/subversion_spec.rb b/spec/unit/provider/subversion_spec.rb index 9ca11b8d82..9d4a8bd218 100644 --- a/spec/unit/provider/subversion_spec.rb +++ b/spec/unit/provider/subversion_spec.rb @@ -27,6 +27,7 @@ describe Chef::Provider::Subversion do @resource.revision "12345" @resource.svn_arguments(false) @resource.svn_info_args(false) + @resource.svn_binary "svn" @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @@ -63,28 +64,18 @@ describe Chef::Provider::Subversion do "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n" expect(::File).to receive(:exist?).at_least(1).times.with("/my/deploy/dir/.svn").and_return(true) - expect(::File).to receive(:directory?).with("/my/deploy/dir").and_return(true) - expect(::Dir).to receive(:chdir).with("/my/deploy/dir").and_yield - allow(@stdout).to receive(:string).and_return(example_svn_info) - allow(@stderr).to receive(:string).and_return("") - allow(@exitstatus).to receive(:exitstatus).and_return(0) - expected_command = ["svn info", {:cwd=>"/my/deploy/dir"}] - expect(@provider).to receive(:popen4).with(*expected_command). - and_yield("no-pid", "no-stdin", @stdout,@stderr). - and_return(@exitstatus) + expected_command = ["svn info", {:cwd => '/my/deploy/dir', :returns => [0,1]}] + expect(@provider).to receive(:shell_out!).with(*expected_command). + and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) expect(@provider.find_current_revision).to eql("11410") end it "gives nil as the current revision if the deploy dir isn't a SVN working copy" do example_svn_info = "svn: '/tmp/deploydir' is not a working copy\n" expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").and_return(true) - expect(::File).to receive(:directory?).with("/my/deploy/dir").and_return(true) - expect(::Dir).to receive(:chdir).with("/my/deploy/dir").and_yield - allow(@stdout).to receive(:string).and_return(example_svn_info) - allow(@stderr).to receive(:string).and_return("") - allow(@exitstatus).to receive(:exitstatus).and_return(1) - expect(@provider).to receive(:popen4).and_yield("no-pid", "no-stdin", @stdout,@stderr). - and_return(@exitstatus) + expected_command = ["svn info", {:cwd => '/my/deploy/dir', :returns => [0,1]}] + expect(@provider).to receive(:shell_out!).with(*expected_command). + and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) expect(@provider.find_current_revision).to be_nil end @@ -127,28 +118,20 @@ describe Chef::Provider::Subversion do "Last Changed Author: codeninja\n" + "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n" - exitstatus = double("exitstatus") - allow(exitstatus).to receive(:exitstatus).and_return(0) @resource.revision "HEAD" - allow(@stdout).to receive(:string).and_return(example_svn_info) - allow(@stderr).to receive(:string).and_return("") - expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache -rHEAD", {:cwd=>Dir.tmpdir}] - expect(@provider).to receive(:popen4).with(*expected_command). - and_yield("no-pid","no-stdin",@stdout,@stderr). - and_return(exitstatus) + expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache -rHEAD", {:cwd => '/my/deploy/dir', :returns => [0,1]}] + expect(@provider).to receive(:shell_out!).with(*expected_command). + and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) expect(@provider.revision_int).to eql("11410") end it "returns a helpful message if data from `svn info` can't be parsed" do example_svn_info = "some random text from an error message\n" - exitstatus = double("exitstatus") - allow(exitstatus).to receive(:exitstatus).and_return(0) @resource.revision "HEAD" - allow(@stdout).to receive(:string).and_return(example_svn_info) - allow(@stderr).to receive(:string).and_return("") - expect(@provider).to receive(:popen4).and_yield("no-pid","no-stdin",@stdout,@stderr). - and_return(exitstatus) - expect {@provider.revision_int}.to raise_error(RuntimeError, "Could not parse `svn info` data: some random text from an error message") + expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache -rHEAD", {:cwd => '/my/deploy/dir', :returns => [0,1]}] + expect(@provider).to receive(:shell_out!).with(*expected_command). + and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) + expect {@provider.revision_int}.to raise_error(RuntimeError, "Could not parse `svn info` data: some random text from an error message\n") end @@ -277,4 +260,40 @@ describe Chef::Provider::Subversion do expect(@resource).to be_updated end + context "selects the correct svn binary" do + before do + end + + it "selects 'svn' as the binary by default" do + @resource.svn_binary nil + allow(ChefConfig).to receive(:windows?) { false } + expect(@provider).to receive(:svn_binary).and_return('svn') + expect(@provider.export_command).to eql( + 'svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir') + end + + it "selects an svn binary with an exe extension on windows" do + @resource.svn_binary nil + allow(ChefConfig).to receive(:windows?) { true } + expect(@provider).to receive(:svn_binary).and_return('svn.exe') + expect(@provider.export_command).to eql( + 'svn.exe export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir') + end + + it "uses a custom svn binary as part of the svn command" do + @resource.svn_binary 'teapot' + expect(@provider).to receive(:svn_binary).and_return('teapot') + expect(@provider.export_command).to eql( + 'teapot export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir') + end + + it "wraps custom svn binary with quotes if it contains whitespace" do + @resource.svn_binary 'c:/program files (x86)/subversion/svn.exe' + expect(@provider).to receive(:svn_binary).and_return('c:/program files (x86)/subversion/svn.exe') + expect(@provider.export_command).to eql( + '"c:/program files (x86)/subversion/svn.exe" export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir') + end + + end + end diff --git a/spec/unit/resource/subversion_spec.rb b/spec/unit/resource/subversion_spec.rb index 5cd5d0de80..aa4d1ed708 100644 --- a/spec/unit/resource/subversion_spec.rb +++ b/spec/unit/resource/subversion_spec.rb @@ -54,6 +54,10 @@ describe Chef::Resource::Subversion do expect(@svn.svn_arguments).to eq('--no-auth-cache') end + it "sets svn binary to nil by default" do + expect(@svn.svn_binary).to be_nil + end + it "resets svn arguments to nil when given false in the setter" do @svn.svn_arguments(false) expect(@svn.svn_arguments).to be_nil -- cgit v1.2.1 From 1b85c82cd8a61e41e675685aa61a0b97857ed84f Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Fri, 28 Aug 2015 16:40:44 -0700 Subject: Make win32/api/net.rb look nicer --- lib/chef/win32/api/net.rb | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb index 082cf4bb9a..0519e93a96 100644 --- a/lib/chef/win32/api/net.rb +++ b/lib/chef/win32/api/net.rb @@ -152,13 +152,15 @@ class Chef #_In_ LPBYTE buf, #_Out_ LPDWORD parm_err #); - safe_attach_function :NetLocalGroupAdd, [ :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD + safe_attach_function :NetLocalGroupAdd, [ + :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD + ], :DWORD #NET_API_STATUS NetLocalGroupDel( #_In_ LPCWSTR servername, #_In_ LPCWSTR groupname #); - safe_attach_function :NetLocalGroupDel, [ :LPCWSTR, :LPCWSTR], :DWORD + safe_attach_function :NetLocalGroupDel, [:LPCWSTR, :LPCWSTR], :DWORD #NET_API_STATUS NetLocalGroupGetMembers( #_In_ LPCWSTR servername, @@ -170,7 +172,7 @@ class Chef #_Out_ LPDWORD totalentries, #_Inout_ PDWORD_PTR resumehandle #); - safe_attach_function :NetLocalGroupGetMembers, [ + safe_attach_function :NetLocalGroupGetMembers, [ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :PDWORD_PTR ], :DWORD @@ -185,12 +187,15 @@ class Chef # _Out_ LPDWORD totalentries, # _Inout_ LPDWORD resume_handle # ); - safe_attach_function :NetUserEnum, [ :LPCWSTR, :DWORD, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :LPDWORD ], :DWORD + safe_attach_function :NetUserEnum, [ + :LPCWSTR, :DWORD, :DWORD, :LPBYTE, + :DWORD, :LPDWORD, :LPDWORD, :LPDWORD + ], :DWORD # NET_API_STATUS NetApiBufferFree( # _In_ LPVOID Buffer # ); - safe_attach_function :NetApiBufferFree, [ :LPVOID ], :DWORD + safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD #NET_API_STATUS NetUserAdd( #_In_ LMSTR servername, @@ -198,7 +203,9 @@ class Chef #_In_ LPBYTE buf, #_Out_ LPDWORD parm_err #); - safe_attach_function :NetUserAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD ], :DWORD + safe_attach_function :NetUserAdd, [ + :LMSTR, :DWORD, :LPBYTE, :LPDWORD + ], :DWORD #NET_API_STATUS NetLocalGroupAddMembers( # _In_ LPCWSTR servername, @@ -207,7 +214,9 @@ class Chef # _In_ LPBYTE buf, # _In_ DWORD totalentries #); - safe_attach_function :NetLocalGroupAddMembers, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD + safe_attach_function :NetLocalGroupAddMembers, [ + :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD + ], :DWORD #NET_API_STATUS NetLocalGroupSetMembers( # _In_ LPCWSTR servername, @@ -216,7 +225,9 @@ class Chef # _In_ LPBYTE buf, # _In_ DWORD totalentries #); - safe_attach_function :NetLocalGroupSetMembers, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD + safe_attach_function :NetLocalGroupSetMembers, [ + :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD + ], :DWORD #NET_API_STATUS NetLocalGroupDelMembers( # _In_ LPCWSTR servername, @@ -225,7 +236,9 @@ class Chef # _In_ LPBYTE buf, # _In_ DWORD totalentries #); - safe_attach_function :NetLocalGroupDelMembers, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD + safe_attach_function :NetLocalGroupDelMembers, [ + :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD + ], :DWORD #NET_API_STATUS NetUserGetInfo( # _In_ LPCWSTR servername, @@ -233,7 +246,9 @@ class Chef # _In_ DWORD level, # _Out_ LPBYTE *bufptr #); - safe_attach_function :NetUserGetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE], :DWORD + safe_attach_function :NetUserGetInfo, [ + :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE + ], :DWORD #NET_API_STATUS NetApiBufferFree( # _In_ LPVOID Buffer @@ -247,7 +262,9 @@ class Chef # _In_ LPBYTE buf, # _Out_ LPDWORD parm_err #); - safe_attach_function :NetUserSetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD + safe_attach_function :NetUserSetInfo, [ + :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD + ], :DWORD #NET_API_STATUS NetUserDel( # _In_ LPCWSTR servername, -- cgit v1.2.1 From a9524b0e799c13e90aef846e96c8d32909b5ee39 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 27 Aug 2015 13:33:13 -0700 Subject: Rename NetUser -> Net --- lib/chef/win32/net.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb index 0de310daf1..babe5f829e 100644 --- a/lib/chef/win32/net.rb +++ b/lib/chef/win32/net.rb @@ -22,7 +22,7 @@ require 'chef/mixin/wstring' class Chef module ReservedNames::Win32 - class NetUser + class Net include Chef::ReservedNames::Win32::API::Error extend Chef::ReservedNames::Win32::API::Error @@ -287,5 +287,6 @@ END end end end + NetUser = Net # For backwards compatibility end end -- cgit v1.2.1 From 306423bac260655a9d4b4b152782401f03325519 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 27 Aug 2015 14:02:54 -0700 Subject: Use FFI for NetUseDel --- lib/chef/util/windows/net_use.rb | 15 +++++++++++---- lib/chef/win32/api/net.rb | 11 +++++++++++ lib/chef/win32/net.rb | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb index 62d7e169dc..34835b6d3d 100644 --- a/lib/chef/util/windows/net_use.rb +++ b/lib/chef/util/windows/net_use.rb @@ -21,6 +21,7 @@ #see also cmd.exe: net use /? require 'chef/util/windows' +require 'chef/win32/net' class Chef::Util::Windows::NetUse < Chef::Util::Windows @@ -76,6 +77,7 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows def initialize(localname) @localname = localname @name = multi_to_wide(localname) + @use_name = localname end def add(args) @@ -111,11 +113,16 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows def device get_info()[:remote] end - #XXX should we use some FORCE here? + def delete - rc = NetUseDel.call(nil, @name, USE_NOFORCE) - if rc != NERR_Success - raise ArgumentError, get_last_error(rc) + begin + Chef::ReservedNames::Win32::Net.net_use_del(nil, use_name, :use_noforce) + rescue Chef::Exceptions::Win32APIError => e + raise ArgumentError, e end end + + def use_name + @use_name + end end diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb index 0519e93a96..a7e20d0aeb 100644 --- a/lib/chef/win32/api/net.rb +++ b/lib/chef/win32/api/net.rb @@ -40,6 +40,10 @@ class Chef UF_NORMAL_ACCOUNT = 0x000200 UF_DONT_EXPIRE_PASSWD = 0x010000 + USE_NOFORCE = 0 + USE_FORCE = 1 + USE_LOTS_OF_FORCE = 2 #every windows API should support this flag + NERR_Success = 0 NERR_InvalidComputer = 2351 NERR_NotPrimary = 2226 @@ -272,6 +276,13 @@ class Chef #); safe_attach_function :NetUserDel, [:LPCWSTR, :LPCWSTR], :DWORD +#NET_API_STATUS NetUseDel( + #_In_ LMSTR UncServerName, + #_In_ LMSTR UseName, + #_In_ DWORD ForceCond +#); + safe_attach_function :NetUseDel, [:LMSTR, :LMSTR, :DWORD], :DWORD + end end end diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb index babe5f829e..e481be7ddb 100644 --- a/lib/chef/win32/net.rb +++ b/lib/chef/win32/net.rb @@ -286,6 +286,26 @@ END net_api_error!(rc) end end + + def self.net_use_del(server_name, use_name, force=:use_noforce) + server_name = wstring(server_name) + use_name = wstring(use_name) + force_const = case force + when :use_noforce + USE_NOFORCE + when :use_force + USE_FORCE + when :use_lots_of_force + USE_LOTS_OF_FORCE + else + raise ArgumentError, "force must be one of [:use_noforce, :use_force, or :use_lots_of_force]" + end + + rc = NetUseDel(server_name, use_name, force_const) + if rc != NERR_Success + net_api_error!(rc) + end + end end NetUser = Net # For backwards compatibility end -- cgit v1.2.1 From 895c8718463029135cc1fe9b3084ff25957ac94d Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 27 Aug 2015 14:40:32 -0700 Subject: Create StructHelper --- lib/chef/win32/api/net.rb | 80 +++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb index a7e20d0aeb..90154e7661 100644 --- a/lib/chef/win32/api/net.rb +++ b/lib/chef/win32/api/net.rb @@ -59,37 +59,7 @@ class Chef ffi_lib "netapi32" - class USER_INFO_3 < FFI::Struct - layout :usri3_name, :LPWSTR, - :usri3_password, :LPWSTR, - :usri3_password_age, :DWORD, - :usri3_priv, :DWORD, - :usri3_home_dir, :LPWSTR, - :usri3_comment, :LPWSTR, - :usri3_flags, :DWORD, - :usri3_script_path, :LPWSTR, - :usri3_auth_flags, :DWORD, - :usri3_full_name, :LPWSTR, - :usri3_usr_comment, :LPWSTR, - :usri3_parms, :LPWSTR, - :usri3_workstations, :LPWSTR, - :usri3_last_logon, :DWORD, - :usri3_last_logoff, :DWORD, - :usri3_acct_expires, :DWORD, - :usri3_max_storage, :DWORD, - :usri3_units_per_week, :DWORD, - :usri3_logon_hours, :PBYTE, - :usri3_bad_pw_count, :DWORD, - :usri3_num_logons, :DWORD, - :usri3_logon_server, :LPWSTR, - :usri3_country_code, :DWORD, - :usri3_code_page, :DWORD, - :usri3_user_id, :DWORD, - :usri3_primary_group_id, :DWORD, - :usri3_profile, :LPWSTR, - :usri3_home_dir_drive, :LPWSTR, - :usri3_password_expired, :DWORD - + module StructHelpers def set(key, val) val = if val.is_a? String encoded = if val.encoding == Encoding::UTF_16LE @@ -121,6 +91,47 @@ class Chef end end + def as_ruby + members.inject({}) do |memo, key| + memo[key] = get(key) + memo + end + end + end + + + class USER_INFO_3 < FFI::Struct + include StructHelpers + layout :usri3_name, :LPWSTR, + :usri3_password, :LPWSTR, + :usri3_password_age, :DWORD, + :usri3_priv, :DWORD, + :usri3_home_dir, :LPWSTR, + :usri3_comment, :LPWSTR, + :usri3_flags, :DWORD, + :usri3_script_path, :LPWSTR, + :usri3_auth_flags, :DWORD, + :usri3_full_name, :LPWSTR, + :usri3_usr_comment, :LPWSTR, + :usri3_parms, :LPWSTR, + :usri3_workstations, :LPWSTR, + :usri3_last_logon, :DWORD, + :usri3_last_logoff, :DWORD, + :usri3_acct_expires, :DWORD, + :usri3_max_storage, :DWORD, + :usri3_units_per_week, :DWORD, + :usri3_logon_hours, :PBYTE, + :usri3_bad_pw_count, :DWORD, + :usri3_num_logons, :DWORD, + :usri3_logon_server, :LPWSTR, + :usri3_country_code, :DWORD, + :usri3_code_page, :DWORD, + :usri3_user_id, :DWORD, + :usri3_primary_group_id, :DWORD, + :usri3_profile, :LPWSTR, + :usri3_home_dir_drive, :LPWSTR, + :usri3_password_expired, :DWORD + def usri3_logon_hours val = self[:usri3_logon_hours] if !val.nil? && !val.null? @@ -129,13 +140,6 @@ class Chef nil end end - - def as_ruby - members.inject({}) do |memo, key| - memo[key] = get(key) - memo - end - end end class LOCALGROUP_MEMBERS_INFO_0 < FFI::Struct -- cgit v1.2.1 From 093e2cb93d5f8e14fc86d6eb67f174945b7b05af Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 27 Aug 2015 15:05:20 -0700 Subject: FFI NetUseGetInfo --- lib/chef/util/windows/net_use.rb | 23 ++++++++++++----------- lib/chef/win32/api/net.rb | 22 ++++++++++++++++++++++ lib/chef/win32/net.rb | 16 ++++++++++++++++ 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb index 34835b6d3d..b9c67a53ac 100644 --- a/lib/chef/util/windows/net_use.rb +++ b/lib/chef/util/windows/net_use.rb @@ -95,19 +95,20 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows end end - def get_info - ptr = 0.chr * PTR_SIZE - rc = NetUseGetInfo.call(nil, @name, 2, ptr) - - if rc != NERR_Success - raise ArgumentError, get_last_error(rc) + def from_use_info_struct(ui2_hash) + ui2_hash.inject({}) do |memo, (k,v)| + memo[k.to_s.sub('ui2_', '').to_sym] = v + memo end + end - ptr = ptr.unpack('L')[0] - buffer = 0.chr * SIZEOF_USE_INFO_2 - memcpy(buffer, ptr, buffer.size) - NetApiBufferFree(ptr) - use_info_2_unpack(buffer) + def get_info + begin + ui2 = Chef::ReservedNames::Win32::Net.net_use_get_info_l2(nil, use_name) + from_use_info_struct(ui2) + rescue Chef::Exceptions::Win32APIError => e + raise ArgumentError, e + end end def device diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb index 90154e7661..b4be47e418 100644 --- a/lib/chef/win32/api/net.rb +++ b/lib/chef/win32/api/net.rb @@ -154,6 +154,21 @@ class Chef layout :lgrpi0_name, :LPWSTR end + class USE_INFO_2 < FFI::Struct + include StructHelpers + + layout :ui2_local, :LMSTR, + :ui2_remote, :LMSTR, + :ui2_password, :LMSTR, + :ui2_status, :DWORD, + :ui2_asg_type, :DWORD, + :ui2_refcount, :DWORD, + :ui2_usecount, :DWORD, + :ui2_username, :LPWSTR, + :ui2_domainname, :LMSTR + end + + #NET_API_STATUS NetLocalGroupAdd( #_In_ LPCWSTR servername, #_In_ DWORD level, @@ -287,6 +302,13 @@ class Chef #); safe_attach_function :NetUseDel, [:LMSTR, :LMSTR, :DWORD], :DWORD +#NET_API_STATUS NetUseGetInfo( + #_In_ LMSTR UncServerName, + #_In_ LMSTR UseName, + #_In_ DWORD Level, + #_Out_ LPBYTE *BufPtr +#); + safe_attach_function :NetUseGetInfo, [:LMSTR, :LMSTR, :DWORD, :pointer], :DWORD end end end diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb index e481be7ddb..2520e7a117 100644 --- a/lib/chef/win32/net.rb +++ b/lib/chef/win32/net.rb @@ -306,6 +306,22 @@ END net_api_error!(rc) end end + + def self.net_use_get_info_l2(server_name, use_name) + server_name = wstring(server_name) + use_name = wstring(use_name) + ui2_p = FFI::MemoryPointer.new(:pointer) + + rc = NetUseGetInfo(server_name, use_name, 2, ui2_p) + if rc != NERR_Success + net_api_error!(rc) + end + + ui2 = USE_INFO_2.new(ui2_p.read_pointer).as_ruby + NetApiBufferFree(ui2_p.read_pointer) + + ui2 + end end NetUser = Net # For backwards compatibility end -- cgit v1.2.1 From 9b4a7da265329ffc4a41f565f3932c2b0d681a62 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 27 Aug 2015 15:23:39 -0700 Subject: FFI NetUseAdd --- lib/chef/util/windows/net_use.rb | 18 +++++++++++++----- lib/chef/win32/api/net.rb | 8 ++++++++ lib/chef/win32/net.rb | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb index b9c67a53ac..e2a91db1a4 100644 --- a/lib/chef/util/windows/net_use.rb +++ b/lib/chef/util/windows/net_use.rb @@ -80,6 +80,13 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows @use_name = localname end + def to_ui2_struct(use_info) + use_info.inject({}) do |memo, (k,v)| + memo["ui2_#{k}".to_sym] = v + memo + end + end + def add(args) if args.class == String remote = args @@ -87,11 +94,12 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows args[:remote] = remote end args[:local] ||= @localname - use = use_info_2(args) - buffer = use_info_2_pack(use) - rc = NetUseAdd.call(nil, 2, buffer, nil) - if rc != NERR_Success - raise ArgumentError, get_last_error(rc) + ui2_hash = to_ui2_struct(args) + + begin + Chef::ReservedNames::Win32::Net.net_use_add_l2(nil, ui2_hash) + rescue Chef::Exceptions::Win32APIError => e + raise ArgumentError, e end end diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb index b4be47e418..6828d7ee2f 100644 --- a/lib/chef/win32/api/net.rb +++ b/lib/chef/win32/api/net.rb @@ -309,6 +309,14 @@ class Chef #_Out_ LPBYTE *BufPtr #); safe_attach_function :NetUseGetInfo, [:LMSTR, :LMSTR, :DWORD, :pointer], :DWORD + +#NET_API_STATUS NetUseAdd( + #_In_ LMSTR UncServerName, + #_In_ DWORD Level, + #_In_ LPBYTE Buf, + #_Out_ LPDWORD ParmError +#); + safe_attach_function :NetUseAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD end end end diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb index 2520e7a117..c1ef3e83ce 100644 --- a/lib/chef/win32/net.rb +++ b/lib/chef/win32/net.rb @@ -322,6 +322,22 @@ END ui2 end + + def self.net_use_add_l2(server_name, ui2_hash) + server_name = wstring(server_name) + group_name = wstring(group_name) + + buf = USE_INFO_2.new + + ui2_hash.each do |(k,v)| + buf.set(k,v) + end + + rc = NetUseAdd(server_name, 2, buf, nil) + if rc != NERR_Success + net_api_error!(rc) + end + end end NetUser = Net # For backwards compatibility end -- cgit v1.2.1 From a84f30da26f47aeeab2d13a775cb4d33e8c34fd6 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 27 Aug 2015 15:34:05 -0700 Subject: Remove unused things --- lib/chef/util/windows/net_use.rb | 54 +--------------------------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb index e2a91db1a4..b94576e702 100644 --- a/lib/chef/util/windows/net_use.rb +++ b/lib/chef/util/windows/net_use.rb @@ -24,59 +24,7 @@ require 'chef/util/windows' require 'chef/win32/net' class Chef::Util::Windows::NetUse < Chef::Util::Windows - - private - - USE_NOFORCE = 0 - USE_FORCE = 1 - USE_LOTS_OF_FORCE = 2 #every windows API should support this flag - - USE_INFO_2 = [ - [:local, nil], - [:remote, nil], - [:password, nil], - [:status, 0], - [:asg_type, 0], - [:refcount, 0], - [:usecount, 0], - [:username, nil], - [:domainname, nil] - ] - - USE_INFO_2_TEMPLATE = - USE_INFO_2.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join - - SIZEOF_USE_INFO_2 = #sizeof(USE_INFO_2) - USE_INFO_2.inject(0) do |sum, item| - sum + (item[1].class == Fixnum ? 4 : PTR_SIZE) - end - - def use_info_2(args) - USE_INFO_2.collect { |field| - args.include?(field[0]) ? args[field[0]] : field[1] - } - end - - def use_info_2_pack(use) - use.collect { |v| - v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v)) - }.pack(USE_INFO_2_TEMPLATE) - end - - def use_info_2_unpack(buffer) - use = Hash.new - USE_INFO_2.each_with_index do |field,offset| - use[field[0]] = field[1].class == Fixnum ? - dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset) - end - use - end - - public - def initialize(localname) - @localname = localname - @name = multi_to_wide(localname) @use_name = localname end @@ -93,7 +41,7 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows args = Hash.new args[:remote] = remote end - args[:local] ||= @localname + args[:local] ||= use_name ui2_hash = to_ui2_struct(args) begin -- cgit v1.2.1 From 3a853284da6d566bf96c21a8a5af0413c73c0b6a Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Fri, 28 Aug 2015 18:17:13 -0700 Subject: Remove unused instance variable --- lib/chef/util/windows/net_user.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb index 26fbe53db6..4ce051228a 100644 --- a/lib/chef/util/windows/net_user.rb +++ b/lib/chef/util/windows/net_user.rb @@ -88,7 +88,6 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows def initialize(username) @username = username - @name = multi_to_wide(username) end LOGON32_PROVIDER_DEFAULT = Security::LOGON32_PROVIDER_DEFAULT -- cgit v1.2.1 From 828847d322c3e8239a73c5615b323c28ded4ad03 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 27 Aug 2015 10:15:51 -0700 Subject: Remove dependency on windows-pr --- chef-windows.gemspec | 1 - lib/chef/util/windows.rb | 32 -------------------------------- 2 files changed, 33 deletions(-) diff --git a/chef-windows.gemspec b/chef-windows.gemspec index 428174889f..2a7ad86f92 100644 --- a/chef-windows.gemspec +++ b/chef-windows.gemspec @@ -12,7 +12,6 @@ gemspec.add_dependency "win32-mutex", "~> 0.4.2" gemspec.add_dependency "win32-process", "~> 0.7.5" gemspec.add_dependency "win32-service", "~> 0.8.7" gemspec.add_dependency "windows-api", "~> 0.4.4" -gemspec.add_dependency "windows-pr", "~> 1.2.4" gemspec.add_dependency "wmi-lite", "~> 1.0" gemspec.extensions << "ext/win32-eventlog/Rakefile" gemspec.files += %w(ext/win32-eventlog/Rakefile ext/win32-eventlog/chef-log.man) diff --git a/lib/chef/util/windows.rb b/lib/chef/util/windows.rb index 777fe4adbb..7d29a67ac5 100644 --- a/lib/chef/util/windows.rb +++ b/lib/chef/util/windows.rb @@ -15,42 +15,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # -#requires: gem install windows-pr -require 'windows/api' -require 'windows/error' -require 'windows/handle' -require 'windows/unicode' -require 'windows/msvcrt/buffer' -require 'windows/msvcrt/string' -require 'windows/network/management' class Chef class Util class Windows - protected - - include ::Windows::Error - include ::Windows::Unicode - include ::Windows::MSVCRT::Buffer - include ::Windows::MSVCRT::String - include ::Windows::Network::Management - - PTR_SIZE = 4 #XXX 64-bit - - def lpwstr_to_s(buffer, offset) - str = 0.chr * (256 * 2) #XXX unhardcode this length (*2 for WCHAR) - wcscpy str, buffer[offset*PTR_SIZE,PTR_SIZE].unpack('L')[0] - wide_to_multi str - end - - def dword_to_i(buffer, offset) - buffer[offset*PTR_SIZE,PTR_SIZE].unpack('i')[0] || 0 - end - - #return pointer for use with pack('L') - def str_to_ptr(v) - [v].pack('p*').unpack('L')[0] - end end end end -- cgit v1.2.1 From 998a1341989dc6447061c46629e8d2da8a28e53a Mon Sep 17 00:00:00 2001 From: chefsalim Date: Sat, 29 Aug 2015 21:07:21 -0700 Subject: Prep for Registry FFI; Convert RegDeleteKeyEx to FFI --- lib/chef/win32/api/registry.rb | 45 ++++++++++++++++++++++++++++++++++++++++++ lib/chef/win32/registry.rb | 14 +++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 lib/chef/win32/api/registry.rb diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb new file mode 100644 index 0000000000..45b91d7d32 --- /dev/null +++ b/lib/chef/win32/api/registry.rb @@ -0,0 +1,45 @@ +# +# Author:: Salim Alam () +# Copyright:: Copyright 2015 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/win32/api' + +class Chef + module ReservedNames::Win32 + module API + module Registry + extend Chef::ReservedNames::Win32::API + + ############################################### + # Win32 API Bindings + ############################################### + + ffi_lib 'advapi32' + + # LONG WINAPI RegDeleteKeyEx( + # _In_ HKEY hKey, + # _In_ LPCTSTR lpSubKey, + # _In_ REGSAM samDesired, + # _Reserved_ DWORD Reserved + # ); + safe_attach_function :RegDeleteKeyExW, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG + safe_attach_function :RegDeleteKeyExA, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG + + end + end + end +end \ No newline at end of file diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index 1a1aa12fad..ba41a4af1d 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -17,8 +17,11 @@ # limitations under the License. # require 'chef/reserved_names' +require 'chef/win32/api' +require 'chef/mixin/wstring' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require 'chef/win32/api/registry' require 'win32/registry' require 'win32/api' end @@ -27,6 +30,14 @@ class Chef class Win32 class Registry + if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + include Chef::ReservedNames::Win32::API::Registry + extend Chef::ReservedNames::Win32::API::Registry + end + + include Chef::Mixin::WideString + extend Chef::Mixin::WideString + attr_accessor :run_context attr_accessor :architecture @@ -142,9 +153,8 @@ class Chef #Using the 'RegDeleteKeyEx' Windows API that correctly supports WOW64 systems (Win2003) #instead of the 'RegDeleteKey' def delete_key_ex(hive, key) - regDeleteKeyEx = ::Win32::API.new('RegDeleteKeyEx', 'LPLL', 'L', 'advapi32') hive_num = hive.hkey - (1 << 32) - regDeleteKeyEx.call(hive_num, key, ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0) + RegDeleteKeyExW(hive_num, wstring(key), ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0) == ERROR_SUCCESS end def key_exists?(key_path) -- cgit v1.2.1 From 0047f4cd275a52aacc14ad8f45c323da3cb93b86 Mon Sep 17 00:00:00 2001 From: Salim Alam Date: Mon, 31 Aug 2015 10:35:45 -0700 Subject: Fix constant --- lib/chef/win32/registry.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index ba41a4af1d..2bb7b78965 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -154,7 +154,7 @@ class Chef #instead of the 'RegDeleteKey' def delete_key_ex(hive, key) hive_num = hive.hkey - (1 << 32) - RegDeleteKeyExW(hive_num, wstring(key), ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0) == ERROR_SUCCESS + RegDeleteKeyExW(hive_num, wstring(key), ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0) == 0 end def key_exists?(key_path) -- cgit v1.2.1 From 5d4d979768029a6e396608ab683014946aa1148d Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Wed, 26 Aug 2015 11:08:03 -0700 Subject: Patch Win32::Registry#write on Ruby 2.1, resolves encoding errors --- lib/chef/win32/unicode.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/chef/win32/unicode.rb b/lib/chef/win32/unicode.rb index e7399d5255..ea10dc71d0 100644 --- a/lib/chef/win32/unicode.rb +++ b/lib/chef/win32/unicode.rb @@ -53,3 +53,30 @@ class String Chef::ReservedNames::Win32::Unicode.utf8_to_wide(self) end end + +# https://bugs.ruby-lang.org/issues/11439 +if RUBY_VERSION =~ /^2\.1/ + module Win32 + class Registry + def write(name, type, data) + case type + when REG_SZ, REG_EXPAND_SZ + data = data.to_s.encode(WCHAR) + WCHAR_NUL + when REG_MULTI_SZ + data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL + when REG_BINARY + data = data.to_s + when REG_DWORD + data = API.packdw(data.to_i) + when REG_DWORD_BIG_ENDIAN + data = [data.to_i].pack('N') + when REG_QWORD + data = API.packqw(data.to_i) + else + raise TypeError, "Unsupported type #{type}" + end + API.SetValue(@hkey, name, type, data, data.bytesize) + end + end + end +end \ No newline at end of file -- cgit v1.2.1 From 1537d66f97a9b89f5b646dd0c7eb9d0c120b02dc Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Mon, 31 Aug 2015 17:09:52 -0700 Subject: Use same mixlib-shellout version pin in chef, ohai, and chef-config --- chef.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chef.gemspec b/chef.gemspec index da17e8b586..f28cde21e7 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.add_dependency "mixlib-cli", "~> 1.4" s.add_dependency "mixlib-log", "~> 1.3" s.add_dependency "mixlib-authentication", "~> 1.3" - s.add_dependency "mixlib-shellout", ">= 2.0.0.rc.0", "< 3.0" + s.add_dependency "mixlib-shellout", "~> 2.0" s.add_dependency "ohai", ">= 8.6.0.alpha.1", "< 9" s.add_dependency "ffi-yajl", "~> 2.2" -- cgit v1.2.1 From 6c8f6d834c30216aa8d566345c96ae62520013e8 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Tue, 1 Sep 2015 10:11:52 -0700 Subject: more warning to debug 1. the deploy resource and other internal chef resources trigger this. 2. this is largely a non-problem since people have been happily using the deploy resource for years and nobody has showed up in any of our bugtrackers due to mutating the default hash. we simply don't need to annoy people this badly over this. --- lib/chef/resource.rb | 2 +- spec/unit/property_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 5bef40625f..8b248877e3 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -809,7 +809,7 @@ class Chef end if !options[:default].frozen? && (options[:default].is_a?(Array) || options[:default].is_a?(Hash)) - Chef::Log.warn("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") + Chef::Log.debug("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") end local_properties = properties(false) diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 55eaead9ba..b2e9e2acde 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -476,8 +476,8 @@ describe "Chef::Resource.property" do end end - it "when a property is declared with default: {}, a warning is issued" do - expect(Chef::Log).to receive(:warn).with(match(/^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/)) + it "when a property is declared with default: {}, a debug level warning is issued" do + expect(Chef::Log).to receive(:debug).with(match(/^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/)) resource_class.class_eval("property :x, default: {}", __FILE__, __LINE__) expect(resource.x).to eq({}) end -- cgit v1.2.1 From 632e4cb3083f3932df3b7fec89a1d52b5d5ed570 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Tue, 1 Sep 2015 10:53:55 -0700 Subject: Revert "more warning to debug" This reverts commit 6c8f6d834c30216aa8d566345c96ae62520013e8. --- lib/chef/resource.rb | 2 +- spec/unit/property_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 8b248877e3..5bef40625f 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -809,7 +809,7 @@ class Chef end if !options[:default].frozen? && (options[:default].is_a?(Array) || options[:default].is_a?(Hash)) - Chef::Log.debug("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") + Chef::Log.warn("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") end local_properties = properties(false) diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index b2e9e2acde..55eaead9ba 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -476,8 +476,8 @@ describe "Chef::Resource.property" do end end - it "when a property is declared with default: {}, a debug level warning is issued" do - expect(Chef::Log).to receive(:debug).with(match(/^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/)) + it "when a property is declared with default: {}, a warning is issued" do + expect(Chef::Log).to receive(:warn).with(match(/^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/)) resource_class.class_eval("property :x, default: {}", __FILE__, __LINE__) expect(resource.x).to eq({}) end -- cgit v1.2.1 From 12425d32e29fd612a2afaa99a1978df1b600b941 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Tue, 1 Sep 2015 11:36:25 -0700 Subject: Refactor Chef::Mixin::WideString to remove implicit Windows dependency. --- lib/chef/mixin/wide_string.rb | 72 ++++++++++++++++++++++++++++++++++++ lib/chef/mixin/windows_env_helper.rb | 5 ++- lib/chef/mixin/wstring.rb | 31 ---------------- lib/chef/win32/api/file.rb | 1 + lib/chef/win32/api/net.rb | 1 + lib/chef/win32/api/unicode.rb | 43 --------------------- lib/chef/win32/crypto.rb | 1 + lib/chef/win32/file.rb | 9 +++-- lib/chef/win32/mutex.rb | 3 +- lib/chef/win32/net.rb | 2 +- lib/chef/win32/registry.rb | 2 +- lib/chef/win32/security.rb | 2 +- lib/chef/win32/security/token.rb | 2 +- lib/chef/win32/unicode.rb | 9 ++++- 14 files changed, 96 insertions(+), 87 deletions(-) create mode 100644 lib/chef/mixin/wide_string.rb delete mode 100644 lib/chef/mixin/wstring.rb diff --git a/lib/chef/mixin/wide_string.rb b/lib/chef/mixin/wide_string.rb new file mode 100644 index 0000000000..0c32b76365 --- /dev/null +++ b/lib/chef/mixin/wide_string.rb @@ -0,0 +1,72 @@ +# +# Author:: Jay Mundrawala() +# Copyright:: Copyright 2015 Chef Software +# 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. +# + +class Chef + module Mixin + module WideString + + def wstring(str) + if str.nil? || str.encoding == Encoding::UTF_16LE + str + else + utf8_to_wide(str) + end + end + + def utf8_to_wide(ustring) + # ensure it is actually UTF-8 + # Ruby likes to mark binary data as ASCII-8BIT + ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8" + + # ensure we have the double-null termination Windows Wide likes + ustring = ustring + "\000\000" if ustring.length == 0 or ustring[-1].chr != "\000" + + # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode + ustring = begin + if ustring.respond_to?(:encode) + ustring.encode('UTF-16LE') + else + require 'iconv' + Iconv.conv("UTF-16LE", "UTF-8", ustring) + end + end + ustring + end + + def wide_to_utf8(wstring) + # ensure it is actually UTF-16LE + # Ruby likes to mark binary data as ASCII-8BIT + wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding) + + # encode it all as UTF-8 + wstring = begin + if wstring.respond_to?(:encode) + wstring.encode('UTF-8') + else + require 'iconv' + Iconv.conv("UTF-8", "UTF-16LE", wstring) + end + end + # remove trailing CRLF and NULL characters + wstring.strip! + wstring + end + + end + end +end diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb index a126801a28..cd12b4254a 100644 --- a/lib/chef/mixin/windows_env_helper.rb +++ b/lib/chef/mixin/windows_env_helper.rb @@ -18,6 +18,7 @@ require 'chef/exceptions' +require 'chef/mixin/wide_string' require 'chef/platform/query_helpers' require 'chef/win32/error' if Chef::Platform.windows? require 'chef/win32/api/system' if Chef::Platform.windows? @@ -26,6 +27,8 @@ require 'chef/win32/api/unicode' if Chef::Platform.windows? class Chef module Mixin module WindowsEnvHelper + include Chef::Mixin::WideString + if Chef::Platform.windows? include Chef::ReservedNames::Win32::API::System end @@ -45,7 +48,7 @@ class Chef Chef::ReservedNames::Win32::Error.raise! end if ( SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string( - Chef::ReservedNames::Win32::Unicode.utf8_to_wide('Environment') + utf8_to_wide('Environment') ).address, flags, 5000, nil) == 0 ) Chef::ReservedNames::Win32::Error.raise! end diff --git a/lib/chef/mixin/wstring.rb b/lib/chef/mixin/wstring.rb deleted file mode 100644 index bb6fdf4884..0000000000 --- a/lib/chef/mixin/wstring.rb +++ /dev/null @@ -1,31 +0,0 @@ -# -# Author:: Jay Mundrawala() -# Copyright:: Copyright 2015 Chef Software -# 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. -# - -class Chef - module Mixin - module WideString - def wstring(str) - if str.nil? || str.encoding == Encoding::UTF_16LE - str - else - str.to_wstring - end - end - end - end -end diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb index 728a6c14df..9ff1ad40d6 100644 --- a/lib/chef/win32/api/file.rb +++ b/lib/chef/win32/api/file.rb @@ -20,6 +20,7 @@ require 'chef/win32/api' require 'chef/win32/api/security' require 'chef/win32/api/system' +require 'chef/win32/unicode' class Chef module ReservedNames::Win32 diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb index 6828d7ee2f..b173987a05 100644 --- a/lib/chef/win32/api/net.rb +++ b/lib/chef/win32/api/net.rb @@ -17,6 +17,7 @@ # require 'chef/win32/api' +require 'chef/win32/unicode' class Chef module ReservedNames::Win32 diff --git a/lib/chef/win32/api/unicode.rb b/lib/chef/win32/api/unicode.rb index 2e3a599f0a..2a9166aa99 100644 --- a/lib/chef/win32/api/unicode.rb +++ b/lib/chef/win32/api/unicode.rb @@ -129,49 +129,6 @@ int WideCharToMultiByte( =end safe_attach_function :WideCharToMultiByte, [:UINT, :DWORD, :LPCWSTR, :int, :LPSTR, :int, :LPCSTR, :LPBOOL], :int - ############################################### - # Helpers - ############################################### - - def utf8_to_wide(ustring) - # ensure it is actually UTF-8 - # Ruby likes to mark binary data as ASCII-8BIT - ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8" - - # ensure we have the double-null termination Windows Wide likes - ustring = ustring + "\000\000" if ustring.length == 0 or ustring[-1].chr != "\000" - - # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode - ustring = begin - if ustring.respond_to?(:encode) - ustring.encode('UTF-16LE') - else - require 'iconv' - Iconv.conv("UTF-16LE", "UTF-8", ustring) - end - end - ustring - end - - def wide_to_utf8(wstring) - # ensure it is actually UTF-16LE - # Ruby likes to mark binary data as ASCII-8BIT - wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding) - - # encode it all as UTF-8 - wstring = begin - if wstring.respond_to?(:encode) - wstring.encode('UTF-8') - else - require 'iconv' - Iconv.conv("UTF-8", "UTF-16LE", wstring) - end - end - # remove trailing CRLF and NULL characters - wstring.strip! - wstring - end - end end end diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb index 79cf51b002..aa20c2dfd4 100644 --- a/lib/chef/win32/crypto.rb +++ b/lib/chef/win32/crypto.rb @@ -19,6 +19,7 @@ require 'chef/win32/error' require 'chef/win32/api/memory' require 'chef/win32/api/crypto' +require 'chef/win32/unicode' require 'digest' class Chef diff --git a/lib/chef/win32/file.rb b/lib/chef/win32/file.rb index 57347643fc..700ddb24d3 100644 --- a/lib/chef/win32/file.rb +++ b/lib/chef/win32/file.rb @@ -17,10 +17,11 @@ # limitations under the License. # +require 'chef/mixin/wide_string' require 'chef/win32/api/file' require 'chef/win32/api/security' require 'chef/win32/error' -require 'chef/mixin/wstring' +require 'chef/win32/unicode' class Chef module ReservedNames::Win32 @@ -161,9 +162,9 @@ class Chef def self.file_access_check(path, desired_access) security_descriptor = Chef::ReservedNames::Win32::Security.get_file_security(path) - token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE | + token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE | Chef::ReservedNames::Win32::Security::TOKEN_QUERY | - Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE | + Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE | Chef::ReservedNames::Win32::Security::STANDARD_RIGHTS_READ token = Chef::ReservedNames::Win32::Security.open_process_token( Chef::ReservedNames::Win32::Process.get_current_process, @@ -176,7 +177,7 @@ class Chef mapping[:GenericExecute] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_EXECUTE mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS - Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token, + Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token, desired_access, mapping) end diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb index 0b7d99f111..f4755e9019 100644 --- a/lib/chef/win32/mutex.rb +++ b/lib/chef/win32/mutex.rb @@ -17,6 +17,7 @@ # require 'chef/win32/api/synchronization' +require 'chef/win32/unicode' class Chef module ReservedNames::Win32 @@ -113,5 +114,3 @@ if the mutex is attempted to be acquired by other threads.") end end end - - diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb index c1ef3e83ce..59f29c4d1b 100644 --- a/lib/chef/win32/net.rb +++ b/lib/chef/win32/net.rb @@ -18,7 +18,7 @@ require 'chef/win32/api/net' require 'chef/win32/error' -require 'chef/mixin/wstring' +require 'chef/mixin/wide_string' class Chef module ReservedNames::Win32 diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index 2bb7b78965..b25ce7937e 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -18,7 +18,7 @@ # require 'chef/reserved_names' require 'chef/win32/api' -require 'chef/mixin/wstring' +require 'chef/mixin/wide_string' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require 'chef/win32/api/registry' diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb index 5c83180bc0..bc80517d80 100644 --- a/lib/chef/win32/security.rb +++ b/lib/chef/win32/security.rb @@ -22,7 +22,7 @@ require 'chef/win32/memory' require 'chef/win32/process' require 'chef/win32/unicode' require 'chef/win32/security/token' -require 'chef/mixin/wstring' +require 'chef/mixin/wide_string' class Chef module ReservedNames::Win32 diff --git a/lib/chef/win32/security/token.rb b/lib/chef/win32/security/token.rb index 9e494a73b9..8d4e54ad8c 100644 --- a/lib/chef/win32/security/token.rb +++ b/lib/chef/win32/security/token.rb @@ -18,7 +18,7 @@ require 'chef/win32/security' require 'chef/win32/api/security' - +require 'chef/win32/unicode' require 'ffi' class Chef diff --git a/lib/chef/win32/unicode.rb b/lib/chef/win32/unicode.rb index ea10dc71d0..562301a040 100644 --- a/lib/chef/win32/unicode.rb +++ b/lib/chef/win32/unicode.rb @@ -17,6 +17,7 @@ # limitations under the License. # +require 'chef/mixin/wide_string' require 'chef/win32/api/unicode' class Chef @@ -30,6 +31,8 @@ end module FFI class Pointer + include Chef::Mixin::WideString + def read_wstring(num_wchars = nil) if num_wchars.nil? # Find the length of the string @@ -43,14 +46,16 @@ module FFI num_wchars = length end - Chef::ReservedNames::Win32::Unicode.wide_to_utf8(self.get_bytes(0, num_wchars*2)) + wide_to_utf8(self.get_bytes(0, num_wchars*2)) end end end class String + include Chef::Mixin::WideString + def to_wstring - Chef::ReservedNames::Win32::Unicode.utf8_to_wide(self) + utf8_to_wide(self) end end -- cgit v1.2.1 From b63f742a826be3b97e9eea34f725fbdbdfc87aa8 Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Tue, 1 Sep 2015 18:22:40 -0700 Subject: Refactor knife ssh options stuff. This allows most config options to work with the SSH gateway too. The most important of these is the identity file stuffs. --- lib/chef/knife/ssh.rb | 74 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 68e01cf94f..a34e790c30 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -132,15 +132,18 @@ class Chef if config[:ssh_gateway] gw_host, gw_user = config[:ssh_gateway].split('@').reverse gw_host, gw_port = gw_host.split(':') - gw_opts = gw_port ? { :port => gw_port } : {} + gw_opts = session_options(gw_host, gw_port, gw_user) - session.via(gw_host, gw_user || config[:ssh_user], gw_opts) + begin + # Try to connect with a key. + session.via(gw_host, gw_opts[:user], gw_opts) + rescue Net::SSH::AuthenticationFailed + prompt = "Enter the password for #{user}@#{gw_host}: " + gw_opts[:password] = prompt_for_password(prompt) + # Try again with a password. + session.via(gw_host, user, gw_opts) + end end - rescue Net::SSH::AuthenticationFailed - user = gw_user || config[:ssh_user] - prompt = "Enter the password for #{user}@#{gw_host}: " - gw_opts.merge!(:password => prompt_for_password(prompt)) - session.via(gw_host, user, gw_opts) end def configure_session @@ -204,32 +207,45 @@ class Chef list end - def session_from_list(list) - list.each do |item| - host, ssh_port = item - Chef::Log.debug("Adding #{host}") - session_opts = {} - - ssh_config = Net::SSH.configuration_for(host) - + # Net::SSH session options hash for global options. These should be + # options that will apply to the gateway connection in addition to the + # main one. + # + # @since 12.5.0 + # @param host [String] Hostname for this session. + # @param port [String] SSH port for this session. + # @param user [String] Optional username for this session. + # @return [Hash] + def session_options(host, port, user=nil) + ssh_config = Net::SSH.configuration_for(host) + {}.tap do |opts| # Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user] - user = config[:ssh_user] || ssh_config[:user] - hostspec = user ? "#{user}@#{host}" : host - session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file] - session_opts[:keys_only] = true if config[:identity_file] - session_opts[:password] = config[:ssh_password] if config[:ssh_password] - session_opts[:forward_agent] = config[:forward_agent] - session_opts[:port] = config[:ssh_port] || - ssh_port || # Use cloud port if available - Chef::Config[:knife][:ssh_port] || - ssh_config[:port] - session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug - + opts[:user] = user || config[:ssh_user] || ssh_config[:user] + if config[:identity_file] + opts[:keys] = File.expand_path(config[:identity_file]) + opts[:keys_only] = true + end + opts[:forward_agent] = config[:forward_agent] || ssh_config[:forward_agent] + opts[:port] = port || ssh_config[:port] + opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug if !config[:host_key_verify] - session_opts[:paranoid] = false - session_opts[:user_known_hosts_file] = "/dev/null" + opts[:paranoid] = false + opts[:user_known_hosts_file] = '/dev/null' end + end + end + def session_from_list(list) + list.each do |item| + host, ssh_port = item + Chef::Log.debug("Adding #{host}") + session_opts = session_options(host, ssh_port) + # Handle port overrides for the main connection. + session_opts[:port] = Chef::Config[:knife][:ssh_port] if Chef::Config[:knife][:ssh_port] + session_opts[:port] = config[:ssh_port] if config[:ssh_port] + # Create the hostspec. + hostspec = session_opts[:user] ? "#{session_opts[:user]}@#{host}" : host + # Connect a new session on the multi. session.use(hostspec, session_opts) @longest = host.length if host.length > @longest -- cgit v1.2.1 From ca0726ef3c7c89533d5bd4146fcb3f20e2a50b80 Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Tue, 1 Sep 2015 18:28:28 -0700 Subject: Missed a variable while moving code around. --- lib/chef/knife/ssh.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index a34e790c30..5ccef3000d 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -141,7 +141,7 @@ class Chef prompt = "Enter the password for #{user}@#{gw_host}: " gw_opts[:password] = prompt_for_password(prompt) # Try again with a password. - session.via(gw_host, user, gw_opts) + session.via(gw_host, gw_opts[:user], gw_opts) end end end -- cgit v1.2.1 From 25dac92eeb1ffa83ec549bfed0b19672c5847d80 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Mon, 31 Aug 2015 14:38:50 -0700 Subject: Pass deprecations through formatter instead of logs --- lib/chef/application.rb | 2 +- lib/chef/chef_class.rb | 16 ++++++++++ lib/chef/cookbook_version.rb | 6 ++-- lib/chef/deprecation/warnings.rb | 5 ++- lib/chef/dsl/recipe.rb | 5 ++- lib/chef/dsl/resources.rb | 4 +-- lib/chef/event_dispatch/base.rb | 15 ++++++--- lib/chef/event_dispatch/dispatcher.rb | 4 ++- lib/chef/formatters/base.rb | 3 ++ lib/chef/formatters/doc.rb | 36 ++++++++++++++++++++++ lib/chef/knife/core/subcommand_loader.rb | 6 ++-- lib/chef/log.rb | 3 +- lib/chef/mixin/deprecation.rb | 16 +++++----- lib/chef/node_map.rb | 4 +-- lib/chef/property.rb | 2 +- lib/chef/provider.rb | 2 +- lib/chef/provider_resolver.rb | 4 +-- lib/chef/resource.rb | 6 ++-- lib/chef/resource/chef_gem.rb | 6 ++-- lib/chef/resource/file/verification.rb | 2 +- lib/chef/resource_resolver.rb | 6 ++-- lib/chef/run_context.rb | 8 ++--- spec/integration/client/client_spec.rb | 53 ++++++++++++++++++++++++++++++++ spec/unit/property_spec.rb | 28 +++++++++-------- 24 files changed, 180 insertions(+), 62 deletions(-) diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 0563822ede..f43b6bbd8d 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -382,7 +382,7 @@ class Chef def emit_warnings if Chef::Config[:chef_gem_compile_time] - Chef::Log.deprecation "setting chef_gem_compile_time to true is deprecated" + Chef.log.deprecation "setting chef_gem_compile_time to true is deprecated" end end diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index 458ac82467..e825bedb34 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -190,6 +190,22 @@ class Chef def resource_handler_map @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance end + + # + # @overload log + # Get the current log object. + # + # @return An object that supports `deprecation(message)` + # + # @example + # run_context.log.deprecation("Deprecated!") + # + # @api private + def log + # `run_context.events` is the primary deprecation target if we're in a run. If we + # are not yet in a run, print to `Chef::Log`. + (run_context && run_context.events) || Chef::Log + end end reset! diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 8d302eeec2..0a7e1df2f7 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -51,12 +51,12 @@ class Chef attr_accessor :metadata_filenames def status=(new_status) - Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed") + Chef.log.deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) @status = new_status end def status - Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed") + Chef.log.deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) @status end @@ -480,7 +480,7 @@ class Chef # @deprecated This method was used by the Ruby Chef Server and is no longer # needed. There is no replacement. def generate_manifest_with_urls(&url_generator) - Chef::Log.deprecation("Deprecated method #generate_manifest_with_urls called from #{caller(1).first}") + Chef.log.deprecation("Deprecated method #generate_manifest_with_urls.", caller(1..1)) rendered_manifest = manifest.dup COOKBOOK_SEGMENTS.each do |segment| diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb index 34f468ff53..616f179d53 100644 --- a/lib/chef/deprecation/warnings.rb +++ b/lib/chef/deprecation/warnings.rb @@ -26,9 +26,8 @@ class Chef define_method(name) do |*args| message = [] message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12." - message << "Please update your cookbooks accordingly. Accessed from:" - caller[0..3].each {|l| message << l} - Chef::Log.deprecation message + message << "Please update your cookbooks accordingly." + Chef.log.deprecation(message, caller(0..3)) super(*args) end end diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb index 29cfcd478c..e3b91a7eab 100644 --- a/lib/chef/dsl/recipe.rb +++ b/lib/chef/dsl/recipe.rb @@ -140,8 +140,7 @@ class Chef # method_missing manually. Not a fan. Not. A. Fan. # if respond_to?(method_symbol) - Chef::Log.deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13.") - Chef::Log.deprecation("Use public_send() or send() instead.") + Chef.log.deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.") return send(method_symbol, *args, &block) end @@ -150,7 +149,7 @@ class Chef # never called. DEPRECATED. # if run_context.definitions.has_key?(method_symbol.to_sym) - Chef::Log.deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.") + Chef.log.deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.") Chef::DSL::Definitions.add_definition(method_symbol) return send(method_symbol, *args, &block) end diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb index f15beaeab0..8e75223b0d 100644 --- a/lib/chef/dsl/resources.rb +++ b/lib/chef/dsl/resources.rb @@ -11,14 +11,14 @@ class Chef begin module_eval(<<-EOM, __FILE__, __LINE__+1) def #{dsl_name}(*args, &block) - Chef::Log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1 + Chef.log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1 declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block) end EOM rescue SyntaxError # Handle the case where dsl_name has spaces, etc. define_method(dsl_name.to_sym) do |*args, &block| - Chef::Log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1 + Chef.log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1 declare_resource(dsl_name, args[0], caller[0], &block) end end diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb index 0ae5101029..1c9a58be23 100644 --- a/lib/chef/event_dispatch/base.rb +++ b/lib/chef/event_dispatch/base.rb @@ -47,14 +47,19 @@ class Chef def ohai_completed(node) end - # Already have a client key, assuming this node has registered. + # Announce that we're not going to register the client. Generally because + # we already have the private key, or because we're deliberately not using + # a key. def skipping_registration(node_name, config) end - # About to attempt to register as +node_name+ + # About to attempt to create a private key registered to the server with + # client +node_name+. def registration_start(node_name, config) end + # Successfully created the private key and registered this client with the + # server. def registration_completed end @@ -340,7 +345,6 @@ class Chef def resource_completed(resource) end - # A stream has opened. def stream_opened(stream, options = {}) end @@ -376,8 +380,9 @@ class Chef def whyrun_assumption(action, resource, message) end - ## TODO: deprecation warning. this way we can queue them up and present - # them all at once. + # Emit a message about something being deprecated. + def deprecation(message, location=caller(2..2)[0]) + end # An uncategorized message. This supports the case that a user needs to # pass output that doesn't fit into one of the callbacks above. Note that diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb index 9e17d78507..0c5b27514c 100644 --- a/lib/chef/event_dispatch/dispatcher.rb +++ b/lib/chef/event_dispatch/dispatcher.rb @@ -28,6 +28,9 @@ class Chef # Define a method that will be forwarded to all def self.def_forwarding_method(method_name) define_method(method_name) do |*args| + if method_name == :deprecation && args.size == 1 + args << caller(2..2)[0] + end @subscribers.each do |s| # Skip new/unsupported event names. if s.respond_to?(method_name) @@ -49,4 +52,3 @@ class Chef end end end - diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb index c901068aa0..d3756ef00c 100644 --- a/lib/chef/formatters/base.rb +++ b/lib/chef/formatters/base.rb @@ -212,6 +212,9 @@ class Chef file_load_failed(path, exception) end + def deprecation(message, location=caller(2..2)[0]) + Chef::Log.deprecation("#{message} at #{location}") + end end diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb index a5d7e210c5..614cc44e6d 100644 --- a/lib/chef/formatters/doc.rb +++ b/lib/chef/formatters/doc.rb @@ -43,6 +43,26 @@ class Chef def run_completed(node) @end_time = Time.now + # Print out deprecations. + if !deprecations.empty? + puts_line "" + puts_line "Deprecated features used!" + deprecations.each do |message, locations| + if locations.size == 1 + puts_line " #{message} at #{locations.size} location:" + else + puts_line " #{message} at #{locations.size} locations:" + end + locations.each do |location| + prefix = " - " + Array(location).each do |line| + puts_line "#{prefix}#{line}" + prefix = " " + end + end + end + puts_line "" + end if Chef::Config[:why_run] puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated" else @@ -336,6 +356,16 @@ class Chef end end + def deprecation(message, location=caller(2..2)[0]) + if Chef::Config[:treat_deprecation_warnings_as_errors] + super + end + + # Save deprecations to the screen until the end + deprecations[message] ||= Set.new + deprecations[message] << location + end + def indent indent_by(2) end @@ -343,6 +373,12 @@ class Chef def unindent indent_by(-2) end + + protected + + def deprecations + @deprecations ||= {} + end end end end diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index 1d359ffd53..646a75a21c 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -51,7 +51,7 @@ class Chef Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}") Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest) elsif custom_manifest? - Chef::Log.deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.") + Chef.log.deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.") Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest) else Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir) @@ -84,7 +84,7 @@ class Chef # Deprecated and un-used instance variable. @env = env unless env.nil? - Chef::Log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") + Chef.log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") end end @@ -149,7 +149,7 @@ class Chef # to get in the past. # def subcommand_files - Chef::Log.deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated. + Chef.log.deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated. Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)" @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest? Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files diff --git a/lib/chef/log.rb b/lib/chef/log.rb index 9b27778a40..2cf08324c8 100644 --- a/lib/chef/log.rb +++ b/lib/chef/log.rb @@ -37,7 +37,8 @@ class Chef end end - def self.deprecation(msg=nil, &block) + def self.deprecation(msg=nil, location=caller(2..2)[0], &block) + msg = Array(msg) + [ location ] if location if Chef::Config[:treat_deprecation_warnings_as_errors] error(msg, &block) raise Chef::Exceptions::DeprecatedFeatureError.new(msg) diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb index a3eacf75cb..c90daee4f5 100644 --- a/lib/chef/mixin/deprecation.rb +++ b/lib/chef/mixin/deprecation.rb @@ -102,20 +102,20 @@ class Chef def deprecated_attr_reader(name, alternative, level=:warn) define_method(name) do - Chef::Log.deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.") - Chef::Log.deprecation(alternative) - Chef::Log.deprecation("Called from:") - caller[0..3].each {|c| Chef::Log.deprecation(c)} + Chef.log.deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.") + Chef.log.deprecation(alternative) + Chef.log.deprecation("Called from:") + caller[0..3].each {|c| Chef.log.deprecation(c)} instance_variable_get("@#{name}") end end def deprecated_attr_writer(name, alternative, level=:warn) define_method("#{name}=") do |value| - Chef::Log.deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.") - Chef::Log.deprecation(alternative) - Chef::Log.deprecation("Called from:") - caller[0..3].each {|c| Chef::Log.deprecation(c)} + Chef.log.deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.") + Chef.log.deprecation(alternative) + Chef.log.deprecation("Called from:") + caller[0..3].each {|c| Chef.log.deprecation(c)} instance_variable_set("@#{name}", value) end end diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb index d905c8779e..2e62054b80 100644 --- a/lib/chef/node_map.rb +++ b/lib/chef/node_map.rb @@ -32,8 +32,8 @@ class Chef # @return [NodeMap] Returns self for possible chaining # def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block) - Chef::Log.deprecation "The on_platform option to node_map has been deprecated" if on_platform - Chef::Log.deprecation "The on_platforms option to node_map has been deprecated" if on_platforms + Chef.log.deprecation("The on_platform option to node_map has been deprecated") if on_platform + Chef.log.deprecation("The on_platforms option to node_map has been deprecated") if on_platforms platform ||= on_platform || on_platforms filters = {} filters[:platform] = platform if platform diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 1a3b8ec72c..ccdc711698 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -228,7 +228,7 @@ class Chef if value.nil? && !explicitly_accepts_nil?(resource) # If you say "my_property nil" and the property explicitly accepts # nil values, we consider this a get. - Chef::Log.deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.") + Chef.log.deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.") return get(resource) end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index f2a493c3e6..5f2430e26e 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -421,7 +421,7 @@ class Chef module DeprecatedLWRPClass def const_missing(class_name) if deprecated_constants[class_name.to_sym] - Chef::Log.deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.") + Chef.log.deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.") deprecated_constants[class_name.to_sym] else raise NameError, "uninitialized constant Chef::Provider::#{class_name}" diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb index 8459bc1328..113b1081ee 100644 --- a/lib/chef/provider_resolver.rb +++ b/lib/chef/provider_resolver.rb @@ -157,8 +157,8 @@ class Chef # perf concern otherwise.) handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) } handlers.each do |handler| - Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!") - Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") + Chef.log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!") + Chef.log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end end handlers diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 5bef40625f..6b8c5434f5 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -809,7 +809,7 @@ class Chef end if !options[:default].frozen? && (options[:default].is_a?(Array) || options[:default].is_a?(Hash)) - Chef::Log.warn("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") + Chef.log.deprecation("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") end local_properties = properties(false) @@ -1211,7 +1211,7 @@ class Chef # @deprecated Use resource_name instead. # def self.dsl_name - Chef::Log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." + Chef.log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." if name name = self.name.split('::')[-1] convert_to_snake_case(name) @@ -1288,7 +1288,7 @@ class Chef # def self.provider_base(arg=nil) if arg - Chef::Log.deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") + Chef.log.deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") end @provider_base ||= arg || Chef::Provider end diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb index 0c2fdfa819..4d198421ce 100644 --- a/lib/chef/resource/chef_gem.rb +++ b/lib/chef/resource/chef_gem.rb @@ -50,9 +50,9 @@ class Chef # Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled # Action could be an array of symbols, but probably won't (think install + enable for a package) if compile_time.nil? - Chef::Log.deprecation "#{self} chef_gem compile_time installation is deprecated" - Chef::Log.deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior." - Chef::Log.deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required." + Chef.log.deprecation "#{self} chef_gem compile_time installation is deprecated" + Chef.log.deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior." + Chef.log.deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required." end if compile_time || compile_time.nil? diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb index faf4791884..654f2d72ce 100644 --- a/lib/chef/resource/file/verification.rb +++ b/lib/chef/resource/file/verification.rb @@ -108,7 +108,7 @@ class Chef def verify_command(path, opts) # First implementation interpolated `file`; docs & RFC claim `path` # is interpolated. Until `file` can be deprecated, interpolate both. - Chef::Log.deprecation( + Chef.log.deprecation( '%{file} is deprecated in verify command and will not be '\ 'supported in Chef 13. Please use %{path} instead.' ) if @command.include?('%{file}') diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index 47b3df18af..14b0ff849d 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -56,7 +56,7 @@ class Chef attr_reader :resource_name # @api private def resource - Chef::Log.deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.") + Chef.log.deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.") resource_name end # @api private @@ -174,8 +174,8 @@ class Chef if handlers.empty? handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) } handlers.each do |handler| - Chef::Log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") - Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") + Chef.log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") + Chef.log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end end handlers diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index b1113f594e..dd2da0d7ac 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -533,22 +533,22 @@ ERROR_MESSAGE # These need to be settable so deploy can run a resource_collection # independent of any cookbooks via +recipe_eval+ def resource_collection=(value) - Chef::Log.deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log.deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @resource_collection = value end def audits=(value) - Chef::Log.deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log.deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @audits = value end def immediate_notification_collection=(value) - Chef::Log.deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log.deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @immediate_notification_collection = value end def delayed_notification_collection=(value) - Chef::Log.deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log.deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @delayed_notification_collection = value end end diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index 8c72048965..1a030c130b 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -303,6 +303,59 @@ EOM end + when_the_repository "has a cookbook that generates deprecation warnings" do + before do + file 'cookbooks/x/recipes/default.rb', <<-EOM + class ::MyResource < Chef::Resource + use_automatic_resource_name + property :x, default: [] + property :y, default: {} + end + + my_resource 'blah' do + 1.upto(10) do + x nil + end + x nil + end + EOM + end + + def match_indices(regex, str) + result = [] + pos = 0 + while match = regex.match(str, pos) + result << match.begin(0) + pos = match.end(0) + 1 + end + result + end + + it "should output each deprecation warning only once, at the end of the run" do + file 'config/client.rb', < chef_dir) + expect(result.error?).to be_falsey + + # Search to the end of the client run in the output + run_complete = result.stdout.index("Running handlers complete") + expect(run_complete).to be >= 0 + + # Make sure there is exactly one result for each, and that it occurs *after* the complete message. + expect(match_indices(/MyResource.x has an array or hash default/, result.stdout)).to match([ be > run_complete ]) + expect(match_indices(/MyResource.y has an array or hash default/, result.stdout)).to match([ be > run_complete ]) + expect(match_indices(/nil currently does not overwrite the value of/, result.stdout)).to match([ be > run_complete ]) + end + end + when_the_repository "has a cookbook with only an audit recipe" do before do diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 55eaead9ba..a9b592ec46 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -462,22 +462,26 @@ describe "Chef::Resource.property" do end context "hash default" do - with_property ':x, default: {}' do - it "when x is not set, it returns {}" do - expect(resource.x).to eq({}) - end - it "The same exact value is returned multiple times in a row" do - value = resource.x - expect(value).to eq({}) - expect(resource.x.object_id).to eq(value.object_id) - end - it "Multiple instances of x receive the exact same value" do - expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id) + context "(deprecations allowed)" do + before { Chef::Config[:treat_deprecation_warnings_as_errors] = false } + + with_property ':x, default: {}' do + it "when x is not set, it returns {}" do + expect(resource.x).to eq({}) + end + it "The same exact value is returned multiple times in a row" do + value = resource.x + expect(value).to eq({}) + expect(resource.x.object_id).to eq(value.object_id) + end + it "Multiple instances of x receive the exact same value" do + expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id) + end end end it "when a property is declared with default: {}, a warning is issued" do - expect(Chef::Log).to receive(:warn).with(match(/^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/)) + expect(Chef::Log).to receive(:deprecation).with(match(/^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/)) resource_class.class_eval("property :x, default: {}", __FILE__, __LINE__) expect(resource.x).to eq({}) end -- cgit v1.2.1 From 310f0104d0807f46b77e6055bb20bb20276f33ad Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 13:01:42 -0700 Subject: Make Dispatcher metaprogramming more straightforward --- lib/chef/event_dispatch/dispatcher.rb | 36 +++++++++++++++++------------------ lib/chef/log.rb | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb index 0c5b27514c..0f05365ab1 100644 --- a/lib/chef/event_dispatch/dispatcher.rb +++ b/lib/chef/event_dispatch/dispatcher.rb @@ -25,30 +25,30 @@ class Chef # define the forwarding in one go: # - # Define a method that will be forwarded to all - def self.def_forwarding_method(method_name) - define_method(method_name) do |*args| - if method_name == :deprecation && args.size == 1 - args << caller(2..2)[0] - end - @subscribers.each do |s| - # Skip new/unsupported event names. - if s.respond_to?(method_name) - mth = s.method(method_name) - # Anything with a *args is arity -1, so use all arguments. - arity = mth.arity < 0 ? args.length : mth.arity - # Trim arguments to match what the subscriber expects to allow - # adding new arguments without breaking compat. - mth.call(*args.take(arity)) - end - end + def call_subscribers(method_name, *args) + @subscribers.each do |s| + # Skip new/unsupported event names. + next if !s.respond_to?(method_name) + mth = s.method(method_name) + # Trim arguments to match what the subscriber expects to allow + # adding new arguments without breaking compat. + args = args.take(arity) if mth.arity < args.size && mth.arity >= 0 + mth.call(*args) end end (Base.instance_methods - Object.instance_methods).each do |method_name| - def_forwarding_method(method_name) + class_eval <<-EOM + def #{method_name}(*args) + call_subscribers(#{method_name.inspect}, *args) + end + EOM end + # Special case deprecation, since it needs to know its caller + def deprecation(message, location=caller(2..2)[0]) + call_subscribers(:deprecation, message, location) + end end end end diff --git a/lib/chef/log.rb b/lib/chef/log.rb index 2cf08324c8..aff6252e12 100644 --- a/lib/chef/log.rb +++ b/lib/chef/log.rb @@ -38,7 +38,7 @@ class Chef end def self.deprecation(msg=nil, location=caller(2..2)[0], &block) - msg = Array(msg) + [ location ] if location + msg << " at #{Array(location).join("\n")}" if Chef::Config[:treat_deprecation_warnings_as_errors] error(msg, &block) raise Chef::Exceptions::DeprecatedFeatureError.new(msg) -- cgit v1.2.1 From 386468df5441f4a75865bccfd9314f883e5f39ff Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 13:05:40 -0700 Subject: Pass the buck on deprecations from Chef 12 -> 13 --- lib/chef/deprecation/mixin/template.rb | 3 +-- lib/chef/deprecation/provider/cookbook_file.rb | 2 +- lib/chef/deprecation/provider/file.rb | 2 +- lib/chef/deprecation/provider/remote_file.rb | 3 +-- lib/chef/deprecation/provider/template.rb | 2 +- lib/chef/deprecation/warnings.rb | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/chef/deprecation/mixin/template.rb b/lib/chef/deprecation/mixin/template.rb index 36d18ad90d..58a661c4bd 100644 --- a/lib/chef/deprecation/mixin/template.rb +++ b/lib/chef/deprecation/mixin/template.rb @@ -25,7 +25,7 @@ class Chef # == Deprecation::Provider::Mixin::Template # This module contains the deprecated functions of # Chef::Mixin::Template. These functions are refactored to different - # components. They are frozen and will be removed in Chef 12. + # components. They are frozen and will be removed in Chef 13. # module Template @@ -46,4 +46,3 @@ class Chef end end end - diff --git a/lib/chef/deprecation/provider/cookbook_file.rb b/lib/chef/deprecation/provider/cookbook_file.rb index dfbf4a39a4..92f5ce3623 100644 --- a/lib/chef/deprecation/provider/cookbook_file.rb +++ b/lib/chef/deprecation/provider/cookbook_file.rb @@ -24,7 +24,7 @@ class Chef # == Deprecation::Provider::CookbookFile # This module contains the deprecated functions of # Chef::Provider::CookbookFile. These functions are refactored to - # different components. They are frozen and will be removed in Chef 12. + # different components. They are frozen and will be removed in Chef 13. # module CookbookFile diff --git a/lib/chef/deprecation/provider/file.rb b/lib/chef/deprecation/provider/file.rb index 125f31fe10..31038ab3d8 100644 --- a/lib/chef/deprecation/provider/file.rb +++ b/lib/chef/deprecation/provider/file.rb @@ -25,7 +25,7 @@ class Chef # == Deprecation::Provider::File # This module contains the deprecated functions of # Chef::Provider::File. These functions are refactored to different - # components. They are frozen and will be removed in Chef 12. + # components. They are frozen and will be removed in Chef 13. # module File diff --git a/lib/chef/deprecation/provider/remote_file.rb b/lib/chef/deprecation/provider/remote_file.rb index 4452de67cd..c06a5cc695 100644 --- a/lib/chef/deprecation/provider/remote_file.rb +++ b/lib/chef/deprecation/provider/remote_file.rb @@ -23,7 +23,7 @@ class Chef # == Deprecation::Provider::RemoteFile # This module contains the deprecated functions of # Chef::Provider::RemoteFile. These functions are refactored to different - # components. They are frozen and will be removed in Chef 12. + # components. They are frozen and will be removed in Chef 13. # module RemoteFile @@ -83,4 +83,3 @@ class Chef end end end - diff --git a/lib/chef/deprecation/provider/template.rb b/lib/chef/deprecation/provider/template.rb index d7a228e97a..34e5f54b7e 100644 --- a/lib/chef/deprecation/provider/template.rb +++ b/lib/chef/deprecation/provider/template.rb @@ -25,7 +25,7 @@ class Chef # == Deprecation::Provider::Template # This module contains the deprecated functions of # Chef::Provider::Template. These functions are refactored to different - # components. They are frozen and will be removed in Chef 12. + # components. They are frozen and will be removed in Chef 13. # module Template diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb index 616f179d53..d6a9cbe374 100644 --- a/lib/chef/deprecation/warnings.rb +++ b/lib/chef/deprecation/warnings.rb @@ -25,7 +25,7 @@ class Chef m = instance_method(name) define_method(name) do |*args| message = [] - message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12." + message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13." message << "Please update your cookbooks accordingly." Chef.log.deprecation(message, caller(0..3)) super(*args) -- cgit v1.2.1 From f975355dc9cb9ef73ff86471a32bfac459efeb7a Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 13:21:02 -0700 Subject: Rename log.deprecation to log_deprecation --- lib/chef/application.rb | 2 +- lib/chef/chef_class.rb | 27 +++++++++++++++++---------- lib/chef/cookbook_version.rb | 6 +++--- lib/chef/deprecation/warnings.rb | 2 +- lib/chef/dsl/recipe.rb | 4 ++-- lib/chef/dsl/resources.rb | 4 ++-- lib/chef/knife/core/subcommand_loader.rb | 6 +++--- lib/chef/mixin/deprecation.rb | 16 ++++++++-------- lib/chef/node_map.rb | 4 ++-- lib/chef/property.rb | 2 +- lib/chef/provider.rb | 2 +- lib/chef/provider_resolver.rb | 4 ++-- lib/chef/resource.rb | 6 +++--- lib/chef/resource/chef_gem.rb | 6 +++--- lib/chef/resource/file/verification.rb | 2 +- lib/chef/resource_resolver.rb | 6 +++--- lib/chef/run_context.rb | 8 ++++---- 17 files changed, 57 insertions(+), 50 deletions(-) diff --git a/lib/chef/application.rb b/lib/chef/application.rb index f43b6bbd8d..970544c068 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -382,7 +382,7 @@ class Chef def emit_warnings if Chef::Config[:chef_gem_compile_time] - Chef.log.deprecation "setting chef_gem_compile_time to true is deprecated" + Chef.log_deprecation "setting chef_gem_compile_time to true is deprecated" end end diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index e825bedb34..c2cb9e2b24 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -192,19 +192,26 @@ class Chef end # - # @overload log - # Get the current log object. + # Emit a deprecation message. # - # @return An object that supports `deprecation(message)` + # @param message The message to send. + # @param location The location. Defaults to the caller who called you (since + # generally the person who triggered the check is the one that needs to be + # fixed). # # @example - # run_context.log.deprecation("Deprecated!") - # - # @api private - def log - # `run_context.events` is the primary deprecation target if we're in a run. If we - # are not yet in a run, print to `Chef::Log`. - (run_context && run_context.events) || Chef::Log + # Chef.deprecation("Deprecated!") + # + # @api private this will likely be removed in favor of an as-yet unwritten + # `Chef.log` + def log_deprecation(message, location=caller(2..2)[0]) + # `run_context.events` is the primary deprecation target if we're in a + # run. If we are not yet in a run, print to `Chef::Log`. + if run_context && run_context.events + run_context.events.deprecation(message, location) + else + Chef::Log.deprecation(message, location) + end end end diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index 0a7e1df2f7..bff3146572 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -51,12 +51,12 @@ class Chef attr_accessor :metadata_filenames def status=(new_status) - Chef.log.deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) + Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) @status = new_status end def status - Chef.log.deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) + Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) @status end @@ -480,7 +480,7 @@ class Chef # @deprecated This method was used by the Ruby Chef Server and is no longer # needed. There is no replacement. def generate_manifest_with_urls(&url_generator) - Chef.log.deprecation("Deprecated method #generate_manifest_with_urls.", caller(1..1)) + Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.", caller(1..1)) rendered_manifest = manifest.dup COOKBOOK_SEGMENTS.each do |segment| diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb index d6a9cbe374..376629710e 100644 --- a/lib/chef/deprecation/warnings.rb +++ b/lib/chef/deprecation/warnings.rb @@ -27,7 +27,7 @@ class Chef message = [] message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13." message << "Please update your cookbooks accordingly." - Chef.log.deprecation(message, caller(0..3)) + Chef.log_deprecation(message, caller(0..3)) super(*args) end end diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb index e3b91a7eab..26c0ec6768 100644 --- a/lib/chef/dsl/recipe.rb +++ b/lib/chef/dsl/recipe.rb @@ -140,7 +140,7 @@ class Chef # method_missing manually. Not a fan. Not. A. Fan. # if respond_to?(method_symbol) - Chef.log.deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.") + Chef.log_deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.") return send(method_symbol, *args, &block) end @@ -149,7 +149,7 @@ class Chef # never called. DEPRECATED. # if run_context.definitions.has_key?(method_symbol.to_sym) - Chef.log.deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.") + Chef.log_deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.") Chef::DSL::Definitions.add_definition(method_symbol) return send(method_symbol, *args, &block) end diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb index 8e75223b0d..49588ed516 100644 --- a/lib/chef/dsl/resources.rb +++ b/lib/chef/dsl/resources.rb @@ -11,14 +11,14 @@ class Chef begin module_eval(<<-EOM, __FILE__, __LINE__+1) def #{dsl_name}(*args, &block) - Chef.log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1 + Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1 declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block) end EOM rescue SyntaxError # Handle the case where dsl_name has spaces, etc. define_method(dsl_name.to_sym) do |*args, &block| - Chef.log.deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1 + Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1 declare_resource(dsl_name, args[0], caller[0], &block) end end diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index 646a75a21c..808e053c40 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -51,7 +51,7 @@ class Chef Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}") Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest) elsif custom_manifest? - Chef.log.deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.") + Chef.log_deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.") Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest) else Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir) @@ -84,7 +84,7 @@ class Chef # Deprecated and un-used instance variable. @env = env unless env.nil? - Chef.log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") + Chef.log_deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") end end @@ -149,7 +149,7 @@ class Chef # to get in the past. # def subcommand_files - Chef.log.deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated. + Chef.log_deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated. Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)" @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest? Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb index c90daee4f5..562af541bd 100644 --- a/lib/chef/mixin/deprecation.rb +++ b/lib/chef/mixin/deprecation.rb @@ -102,20 +102,20 @@ class Chef def deprecated_attr_reader(name, alternative, level=:warn) define_method(name) do - Chef.log.deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.") - Chef.log.deprecation(alternative) - Chef.log.deprecation("Called from:") - caller[0..3].each {|c| Chef.log.deprecation(c)} + Chef.log_deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.") + Chef.log_deprecation(alternative) + Chef.log_deprecation("Called from:") + caller[0..3].each {|c| Chef.log_deprecation(c)} instance_variable_get("@#{name}") end end def deprecated_attr_writer(name, alternative, level=:warn) define_method("#{name}=") do |value| - Chef.log.deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.") - Chef.log.deprecation(alternative) - Chef.log.deprecation("Called from:") - caller[0..3].each {|c| Chef.log.deprecation(c)} + Chef.log_deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.") + Chef.log_deprecation(alternative) + Chef.log_deprecation("Called from:") + caller[0..3].each {|c| Chef.log_deprecation(c)} instance_variable_set("@#{name}", value) end end diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb index 2e62054b80..751f9576f6 100644 --- a/lib/chef/node_map.rb +++ b/lib/chef/node_map.rb @@ -32,8 +32,8 @@ class Chef # @return [NodeMap] Returns self for possible chaining # def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block) - Chef.log.deprecation("The on_platform option to node_map has been deprecated") if on_platform - Chef.log.deprecation("The on_platforms option to node_map has been deprecated") if on_platforms + Chef.log_deprecation("The on_platform option to node_map has been deprecated") if on_platform + Chef.log_deprecation("The on_platforms option to node_map has been deprecated") if on_platforms platform ||= on_platform || on_platforms filters = {} filters[:platform] = platform if platform diff --git a/lib/chef/property.rb b/lib/chef/property.rb index ccdc711698..09198d90f1 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -228,7 +228,7 @@ class Chef if value.nil? && !explicitly_accepts_nil?(resource) # If you say "my_property nil" and the property explicitly accepts # nil values, we consider this a get. - Chef.log.deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.") + Chef.log_deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.") return get(resource) end diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb index 5f2430e26e..3138704a55 100644 --- a/lib/chef/provider.rb +++ b/lib/chef/provider.rb @@ -421,7 +421,7 @@ class Chef module DeprecatedLWRPClass def const_missing(class_name) if deprecated_constants[class_name.to_sym] - Chef.log.deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.") + Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.") deprecated_constants[class_name.to_sym] else raise NameError, "uninitialized constant Chef::Provider::#{class_name}" diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb index 113b1081ee..82a24fc078 100644 --- a/lib/chef/provider_resolver.rb +++ b/lib/chef/provider_resolver.rb @@ -157,8 +157,8 @@ class Chef # perf concern otherwise.) handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) } handlers.each do |handler| - Chef.log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!") - Chef.log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") + Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!") + Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end end handlers diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 6b8c5434f5..ee75dec3b9 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -809,7 +809,7 @@ class Chef end if !options[:default].frozen? && (options[:default].is_a?(Array) || options[:default].is_a?(Hash)) - Chef.log.deprecation("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") + Chef.log_deprecation("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") end local_properties = properties(false) @@ -1211,7 +1211,7 @@ class Chef # @deprecated Use resource_name instead. # def self.dsl_name - Chef.log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." + Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." if name name = self.name.split('::')[-1] convert_to_snake_case(name) @@ -1288,7 +1288,7 @@ class Chef # def self.provider_base(arg=nil) if arg - Chef.log.deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") + Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") end @provider_base ||= arg || Chef::Provider end diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb index 4d198421ce..7e9d21ebd2 100644 --- a/lib/chef/resource/chef_gem.rb +++ b/lib/chef/resource/chef_gem.rb @@ -50,9 +50,9 @@ class Chef # Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled # Action could be an array of symbols, but probably won't (think install + enable for a package) if compile_time.nil? - Chef.log.deprecation "#{self} chef_gem compile_time installation is deprecated" - Chef.log.deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior." - Chef.log.deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required." + Chef.log_deprecation "#{self} chef_gem compile_time installation is deprecated" + Chef.log_deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior." + Chef.log_deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required." end if compile_time || compile_time.nil? diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb index 654f2d72ce..ba0bb08201 100644 --- a/lib/chef/resource/file/verification.rb +++ b/lib/chef/resource/file/verification.rb @@ -108,7 +108,7 @@ class Chef def verify_command(path, opts) # First implementation interpolated `file`; docs & RFC claim `path` # is interpolated. Until `file` can be deprecated, interpolate both. - Chef.log.deprecation( + Chef.log_deprecation( '%{file} is deprecated in verify command and will not be '\ 'supported in Chef 13. Please use %{path} instead.' ) if @command.include?('%{file}') diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb index 14b0ff849d..67cf134c62 100644 --- a/lib/chef/resource_resolver.rb +++ b/lib/chef/resource_resolver.rb @@ -56,7 +56,7 @@ class Chef attr_reader :resource_name # @api private def resource - Chef.log.deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.") + Chef.log_deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.") resource_name end # @api private @@ -174,8 +174,8 @@ class Chef if handlers.empty? handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) } handlers.each do |handler| - Chef.log.deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") - Chef.log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") + Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") + Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end end handlers diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index dd2da0d7ac..0c8d3d1a48 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -533,22 +533,22 @@ ERROR_MESSAGE # These need to be settable so deploy can run a resource_collection # independent of any cookbooks via +recipe_eval+ def resource_collection=(value) - Chef.log.deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log_deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @resource_collection = value end def audits=(value) - Chef.log.deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log_deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @audits = value end def immediate_notification_collection=(value) - Chef.log.deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log_deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @immediate_notification_collection = value end def delayed_notification_collection=(value) - Chef.log.deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") + Chef.log_deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @delayed_notification_collection = value end end -- cgit v1.2.1 From 86d422017d0d990ac396ac5109320a8a4bbbe878 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 14:00:44 -0700 Subject: Fix dispatching events to methods with arity < args --- lib/chef/event_dispatch/dispatcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb index 0f05365ab1..966a3f32ec 100644 --- a/lib/chef/event_dispatch/dispatcher.rb +++ b/lib/chef/event_dispatch/dispatcher.rb @@ -32,7 +32,7 @@ class Chef mth = s.method(method_name) # Trim arguments to match what the subscriber expects to allow # adding new arguments without breaking compat. - args = args.take(arity) if mth.arity < args.size && mth.arity >= 0 + args = args.take(mth.arity) if mth.arity < args.size && mth.arity >= 0 mth.call(*args) end end -- cgit v1.2.1 From 8379bcbdf656f09dbf62a6dfffed5217bf6a146f Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 14:01:17 -0700 Subject: Send a string, not an array --- lib/chef/log.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/chef/log.rb b/lib/chef/log.rb index aff6252e12..bf846c2072 100644 --- a/lib/chef/log.rb +++ b/lib/chef/log.rb @@ -38,7 +38,10 @@ class Chef end def self.deprecation(msg=nil, location=caller(2..2)[0], &block) - msg << " at #{Array(location).join("\n")}" + if msg + msg << " at #{Array(location).join("\n")}" + msg = msg.join("") if msg.respond_to?(:join) + end if Chef::Config[:treat_deprecation_warnings_as_errors] error(msg, &block) raise Chef::Exceptions::DeprecatedFeatureError.new(msg) -- cgit v1.2.1 From 7c1287cd12fdfcdabe26c4a6e1d548edb0d0f35e Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 14:01:47 -0700 Subject: Simplify deprecation spec --- spec/unit/deprecation_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb index 2e1f3c39f3..674de5ec1d 100644 --- a/spec/unit/deprecation_spec.rb +++ b/spec/unit/deprecation_spec.rb @@ -65,19 +65,16 @@ describe Chef::Deprecation do end context 'deprecation warning messages' do - before(:each) do - @warning_output = [ ] - allow(Chef::Log).to receive(:warn) { |msg| @warning_output << msg } - end + RSpec::Matchers.define_negated_matcher :a_non_empty_array, :be_empty it 'should be enabled for deprecated methods' do + expect(Chef::Log).to receive(:warn).with(a_non_empty_array) TestClass.new.deprecated_method(10) - expect(@warning_output).not_to be_empty end it 'should contain stack trace' do + expect(Chef::Log).to receive(:warn).with(a_string_including(".rb")) TestClass.new.deprecated_method(10) - expect(@warning_output.join("").include?(".rb")).to be_truthy end end -- cgit v1.2.1 From b7afa0ffca2d160b3dfc5c3b1a5e714bf15e0296 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 14:03:01 -0700 Subject: Reset Chef main class between tests --- spec/spec_helper.rb | 2 ++ spec/unit/chef_class_spec.rb | 4 ---- spec/unit/event_dispatch/dsl_spec.rb | 4 ---- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d0d02f1a2a..aadf55f64b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -185,6 +185,8 @@ RSpec.configure do |config| config.run_all_when_everything_filtered = true config.before(:each) do + Chef.reset! + Chef::Config.reset # By default, treat deprecation warnings as errors in tests. diff --git a/spec/unit/chef_class_spec.rb b/spec/unit/chef_class_spec.rb index d6bf74572e..f1b877520c 100644 --- a/spec/unit/chef_class_spec.rb +++ b/spec/unit/chef_class_spec.rb @@ -46,10 +46,6 @@ describe "Chef class" do Chef.set_provider_priority_map(provider_priority_map) end - after do - Chef.reset! - end - context "priority maps" do context "#get_provider_priority_array" do it "should use the current node to get the right priority_map" do diff --git a/spec/unit/event_dispatch/dsl_spec.rb b/spec/unit/event_dispatch/dsl_spec.rb index f467ea81ea..0f7adce7a8 100644 --- a/spec/unit/event_dispatch/dsl_spec.rb +++ b/spec/unit/event_dispatch/dsl_spec.rb @@ -32,10 +32,6 @@ describe Chef::EventDispatch::DSL do Chef.set_run_context(run_context) end - after do - Chef.reset! - end - subject{ described_class.new('test') } it 'set handler name' do -- cgit v1.2.1 From a8d5109721deb5d90687f38e10bb37765a5ac65a Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 1 Sep 2015 19:29:53 -0700 Subject: Make file verification deprecation print the proper source loc --- lib/chef/resource/file/verification.rb | 3 ++- spec/unit/property_spec.rb | 2 +- spec/unit/resource/file/verification_spec.rb | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb index ba0bb08201..9b0788fad3 100644 --- a/lib/chef/resource/file/verification.rb +++ b/lib/chef/resource/file/verification.rb @@ -110,7 +110,8 @@ class Chef # is interpolated. Until `file` can be deprecated, interpolate both. Chef.log_deprecation( '%{file} is deprecated in verify command and will not be '\ - 'supported in Chef 13. Please use %{path} instead.' + 'supported in Chef 13. Please use %{path} instead.', + caller(2..2)[0] ) if @command.include?('%{file}') command = @command % {:file => path, :path => path} interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts) diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index a9b592ec46..50764aa7a2 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -481,7 +481,7 @@ describe "Chef::Resource.property" do end it "when a property is declared with default: {}, a warning is issued" do - expect(Chef::Log).to receive(:deprecation).with(match(/^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/)) + expect(Chef::Log).to receive(:deprecation).with( /^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/, /property_spec\.rb/ ) resource_class.class_eval("property :x, default: {}", __FILE__, __LINE__) expect(resource.x).to eq({}) end diff --git a/spec/unit/resource/file/verification_spec.rb b/spec/unit/resource/file/verification_spec.rb index 04ae9ad629..6b929789c8 100644 --- a/spec/unit/resource/file/verification_spec.rb +++ b/spec/unit/resource/file/verification_spec.rb @@ -88,7 +88,7 @@ describe Chef::Resource::File::Verification do end it "warns about deprecation when \%{file} is used" do - expect(Chef::Log).to receive(:deprecation).with(/%{file} is deprecated/) + expect(Chef::Log).to receive(:deprecation).with(/%{file} is deprecated/, /verification_spec\.rb/) test_command = platform_specific_verify_command('file') Chef::Resource::File::Verification.new(parent_resource, test_command, {}) .verify(temp_path) -- cgit v1.2.1 From 716a86dfd5a95cc07908616b66cd3e854052aa10 Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Tue, 1 Sep 2015 20:17:50 -0700 Subject: Stub Net::SSH.configuration_for. --- spec/functional/knife/ssh_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb index 6608d05771..1c56cce9c2 100644 --- a/spec/functional/knife/ssh_spec.rb +++ b/spec/functional/knife/ssh_spec.rb @@ -31,6 +31,11 @@ describe Chef::Knife::Ssh do @server.stop end + let(:ssh_config) { Hash.new } + before do + allow(Net::SSH).to receive(:configuration_for).and_return(ssh_config) + end + describe "identity file" do context "when knife[:ssh_identity_file] is set" do before do -- cgit v1.2.1 From 3fa5f5c09f018c08ba64913d82bef6956550fa0b Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Tue, 1 Sep 2015 20:18:01 -0700 Subject: Don't leak extra keys in the options. --- lib/chef/knife/ssh.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index 5ccef3000d..bb3d9d78bb 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -133,15 +133,16 @@ class Chef gw_host, gw_user = config[:ssh_gateway].split('@').reverse gw_host, gw_port = gw_host.split(':') gw_opts = session_options(gw_host, gw_port, gw_user) + user = gw_opts.delete(:user) begin # Try to connect with a key. - session.via(gw_host, gw_opts[:user], gw_opts) + session.via(gw_host, user, gw_opts) rescue Net::SSH::AuthenticationFailed prompt = "Enter the password for #{user}@#{gw_host}: " gw_opts[:password] = prompt_for_password(prompt) # Try again with a password. - session.via(gw_host, gw_opts[:user], gw_opts) + session.via(gw_host, user, gw_opts) end end end @@ -225,8 +226,11 @@ class Chef opts[:keys] = File.expand_path(config[:identity_file]) opts[:keys_only] = true end - opts[:forward_agent] = config[:forward_agent] || ssh_config[:forward_agent] - opts[:port] = port || ssh_config[:port] + # Don't set the keys to nil if we don't have them. + forward_agent = config[:forward_agent] || ssh_config[:forward_agent] + opts[:forward_agent] = forward_agent unless forward_agent.nil? + port ||= ssh_config[:port] + opts[:port] = port unless port.nil? opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug if !config[:host_key_verify] opts[:paranoid] = false @@ -244,7 +248,7 @@ class Chef session_opts[:port] = Chef::Config[:knife][:ssh_port] if Chef::Config[:knife][:ssh_port] session_opts[:port] = config[:ssh_port] if config[:ssh_port] # Create the hostspec. - hostspec = session_opts[:user] ? "#{session_opts[:user]}@#{host}" : host + hostspec = session_opts[:user] ? "#{session_opts.delete(:user)}@#{host}" : host # Connect a new session on the multi. session.use(hostspec, session_opts) -- cgit v1.2.1 From 4c10ab73872c88626392412529abcc3d4e3974bf Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Tue, 1 Sep 2015 20:59:43 -0700 Subject: Don't leak log_level=debug out of the cookbook_delete_spec test. --- spec/functional/knife/cookbook_delete_spec.rb | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/spec/functional/knife/cookbook_delete_spec.rb b/spec/functional/knife/cookbook_delete_spec.rb index 15ac8f55ab..bffad8cbed 100644 --- a/spec/functional/knife/cookbook_delete_spec.rb +++ b/spec/functional/knife/cookbook_delete_spec.rb @@ -40,20 +40,30 @@ describe Chef::Knife::CookbookDelete do end context "when the cookbook doesn't exist" do - before do - @log_output = StringIO.new - - Chef::Log.logger = Logger.new(@log_output) - Chef::Log.level = :debug + let(:log_output) { StringIO.new } + before do @knife.name_args = %w{no-such-cookbook} @api.get("/cookbooks/no-such-cookbook", 404, Chef::JSONCompat.to_json({'error'=>'dear Tim, no. -Sent from my iPad'})) end + around do |ex| + old_logger = Chef::Log.logger + old_level = Chef::Log.level + begin + Chef::Log.logger = Logger.new(log_output) + Chef::Log.level = :debug + ex.run + ensure + Chef::Log.logger = old_logger + Chef::Log.level = old_level + end + end + it "logs an error and exits" do - allow(@knife.ui).to receive(:stderr).and_return(@log_output) + allow(@knife.ui).to receive(:stderr).and_return(log_output) expect {@knife.run}.to raise_error(SystemExit) - expect(@log_output.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/) + expect(log_output.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/) end end -- cgit v1.2.1 From 30ff3159f1d8d4c4a704241fa76b9b75f62c2663 Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Tue, 1 Sep 2015 22:00:40 -0700 Subject: Lots of tests leak the log level so just fix it here. --- spec/functional/knife/ssh_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb index 1c56cce9c2..51524b7009 100644 --- a/spec/functional/knife/ssh_spec.rb +++ b/spec/functional/knife/ssh_spec.rb @@ -36,6 +36,17 @@ describe Chef::Knife::Ssh do allow(Net::SSH).to receive(:configuration_for).and_return(ssh_config) end + # Force log level to info. + around do |ex| + old_level = Chef::Log.level + begin + Chef::Log.level = :info + ex.run + ensure + Chef::Log.level = old_level + end + end + describe "identity file" do context "when knife[:ssh_identity_file] is set" do before do -- cgit v1.2.1 From a4ca79d7a3a6e0a992c9710cbf6c7445534cad44 Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Tue, 25 Aug 2015 16:07:23 -0400 Subject: Human friendly elapsed time in log --- lib/chef/formatters/doc.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb index 614cc44e6d..deeb5d3e96 100644 --- a/lib/chef/formatters/doc.rb +++ b/lib/chef/formatters/doc.rb @@ -26,7 +26,15 @@ class Chef end def elapsed_time - end_time - start_time + time = end_time - start_time + if time < 60 then + message = Time.at(time).utc.strftime("%S seconds") + elsif time < 3600 then + message = Time.at(time).utc.strftime("%M minutes %S seconds") + else + message = Time.at(time).utc.strftime("%H hour %M minutes %S seconds") + end + message end def run_start(version) @@ -66,7 +74,7 @@ class Chef if Chef::Config[:why_run] puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated" else - puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{elapsed_time} seconds" + puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{elapsed_time}" if total_audits > 0 puts_line " #{successful_audits}/#{total_audits} controls succeeded" end @@ -78,7 +86,7 @@ class Chef if Chef::Config[:why_run] puts_line "Chef Client failed. #{@updated_resources} resources would have been updated" else - puts_line "Chef Client failed. #{@updated_resources} resources updated in #{elapsed_time} seconds" + puts_line "Chef Client failed. #{@updated_resources} resources updated in #{elapsed_time}" if total_audits > 0 puts_line " #{successful_audits} controls succeeded" end -- cgit v1.2.1 From f2de463a69508cb91e952f3034f7e951c9ab1af7 Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Tue, 25 Aug 2015 18:08:16 -0400 Subject: Add unit tests for elapsed_time --- Gemfile | 1 + lib/chef/formatters/doc.rb | 2 +- spec/unit/formatters/doc_spec.rb | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index af0bef493c..0f547c1645 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ group(:development, :test) do gem "simplecov" gem 'rack', "~> 1.5.1" gem 'cheffish', "~> 1.3" + gem 'timecop' gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/) end diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb index deeb5d3e96..93039d6171 100644 --- a/lib/chef/formatters/doc.rb +++ b/lib/chef/formatters/doc.rb @@ -32,7 +32,7 @@ class Chef elsif time < 3600 then message = Time.at(time).utc.strftime("%M minutes %S seconds") else - message = Time.at(time).utc.strftime("%H hour %M minutes %S seconds") + message = Time.at(time).utc.strftime("%H hours %M minutes %S seconds") end message end diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb index eb98f5abd3..f208508aba 100644 --- a/spec/unit/formatters/doc_spec.rb +++ b/spec/unit/formatters/doc_spec.rb @@ -18,12 +18,21 @@ # require 'spec_helper' +require 'timecop' describe Chef::Formatters::Base do let(:out) { StringIO.new } let(:err) { StringIO.new } + before do + Timecop.freeze(Time.local(2008, 9, 9, 9, 9, 9)) + end + + after do + Timecop.return + end + subject(:formatter) { Chef::Formatters::Doc.new(out, err) } it "prints a policyfile's name and revision ID" do @@ -49,4 +58,30 @@ describe Chef::Formatters::Base do expect(out.string).to include("- apache2 (1.2.3") end + it "prints only seconds when elapsed time is less than 60 seconds" do + f = Chef::Formatters::Doc.new(out, err) + Timecop.freeze(2008, 9, 9, 9, 9, 19) do + f.run_completed(nil) + expect(f.elapsed_time).to include("10 seconds") + expect(f.elapsed_time).not_to include("minutes") + expect(f.elapsed_time).not_to include("hours") + end + end + + it "prints minutes and seconds when elapsed time is more than 60 seconds" do + f = Chef::Formatters::Doc.new(out, err) + Timecop.freeze(2008, 9, 9, 9, 19, 19) do + f.run_completed(nil) + expect(f.elapsed_time).to include("10 minutes 10 seconds") + expect(f.elapsed_time).not_to include("hours") + end + end + + it "prints hours, minutes and seconds when elapsed time is more than 3600 seconds" do + f = Chef::Formatters::Doc.new(out, err) + Timecop.freeze(2008, 9, 9, 19, 19, 19) do + f.run_completed(nil) + expect(f.elapsed_time).to include("10 hours 10 minutes 10 seconds") + end + end end -- cgit v1.2.1 From 5f6589f3c05c965d3c555613bf21c635978a36be Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Tue, 25 Aug 2015 19:05:04 -0400 Subject: Refactor to reduce duplicated instancialization --- spec/unit/formatters/doc_spec.rb | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb index f208508aba..15af595020 100644 --- a/spec/unit/formatters/doc_spec.rb +++ b/spec/unit/formatters/doc_spec.rb @@ -25,15 +25,15 @@ describe Chef::Formatters::Base do let(:out) { StringIO.new } let(:err) { StringIO.new } - before do - Timecop.freeze(Time.local(2008, 9, 9, 9, 9, 9)) - end - after do Timecop.return end - subject(:formatter) { Chef::Formatters::Doc.new(out, err) } + subject(:formatter) { + Timecop.freeze(Time.local(2008, 9, 9, 9, 9, 9)) do + Chef::Formatters::Doc.new(out, err) + end + } it "prints a policyfile's name and revision ID" do minimal_policyfile = { @@ -59,29 +59,26 @@ describe Chef::Formatters::Base do end it "prints only seconds when elapsed time is less than 60 seconds" do - f = Chef::Formatters::Doc.new(out, err) Timecop.freeze(2008, 9, 9, 9, 9, 19) do - f.run_completed(nil) - expect(f.elapsed_time).to include("10 seconds") - expect(f.elapsed_time).not_to include("minutes") - expect(f.elapsed_time).not_to include("hours") + formatter.run_completed(nil) + expect(formatter.elapsed_time).to include("10 seconds") + expect(formatter.elapsed_time).not_to include("minutes") + expect(formatter.elapsed_time).not_to include("hours") end end it "prints minutes and seconds when elapsed time is more than 60 seconds" do - f = Chef::Formatters::Doc.new(out, err) Timecop.freeze(2008, 9, 9, 9, 19, 19) do - f.run_completed(nil) - expect(f.elapsed_time).to include("10 minutes 10 seconds") - expect(f.elapsed_time).not_to include("hours") + formatter.run_completed(nil) + expect(formatter.elapsed_time).to include("10 minutes 10 seconds") + expect(formatter.elapsed_time).not_to include("hours") end end it "prints hours, minutes and seconds when elapsed time is more than 3600 seconds" do - f = Chef::Formatters::Doc.new(out, err) Timecop.freeze(2008, 9, 9, 19, 19, 19) do - f.run_completed(nil) - expect(f.elapsed_time).to include("10 hours 10 minutes 10 seconds") + formatter.run_completed(nil) + expect(formatter.elapsed_time).to include("10 hours 10 minutes 10 seconds") end end end -- cgit v1.2.1 From 850bded619ac14e62bed15b85cc34d6ef3b99202 Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Mon, 31 Aug 2015 08:31:55 -0400 Subject: Keep elapsed_time and add pretty_elapsed_time --- lib/chef/formatters/doc.rb | 10 +++++++--- spec/unit/formatters/doc_spec.rb | 15 +++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb index 93039d6171..2b0161667b 100644 --- a/lib/chef/formatters/doc.rb +++ b/lib/chef/formatters/doc.rb @@ -26,7 +26,11 @@ class Chef end def elapsed_time - time = end_time - start_time + end_time - start_time + end + + def pretty_elapsed_time + time = elapsed_time if time < 60 then message = Time.at(time).utc.strftime("%S seconds") elsif time < 3600 then @@ -74,7 +78,7 @@ class Chef if Chef::Config[:why_run] puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated" else - puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{elapsed_time}" + puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{pretty_elapsed_time}" if total_audits > 0 puts_line " #{successful_audits}/#{total_audits} controls succeeded" end @@ -86,7 +90,7 @@ class Chef if Chef::Config[:why_run] puts_line "Chef Client failed. #{@updated_resources} resources would have been updated" else - puts_line "Chef Client failed. #{@updated_resources} resources updated in #{elapsed_time}" + puts_line "Chef Client failed. #{@updated_resources} resources updated in #{pretty_elapsed_time}" if total_audits > 0 puts_line " #{successful_audits} controls succeeded" end diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb index 15af595020..7db5ebc348 100644 --- a/spec/unit/formatters/doc_spec.rb +++ b/spec/unit/formatters/doc_spec.rb @@ -61,24 +61,27 @@ describe Chef::Formatters::Base do it "prints only seconds when elapsed time is less than 60 seconds" do Timecop.freeze(2008, 9, 9, 9, 9, 19) do formatter.run_completed(nil) - expect(formatter.elapsed_time).to include("10 seconds") - expect(formatter.elapsed_time).not_to include("minutes") - expect(formatter.elapsed_time).not_to include("hours") + expect(formatter.elapsed_time).to eql(10.0) + expect(formatter.pretty_elapsed_time).to include("10 seconds") + expect(formatter.pretty_elapsed_time).not_to include("minutes") + expect(formatter.pretty_elapsed_time).not_to include("hours") end end it "prints minutes and seconds when elapsed time is more than 60 seconds" do Timecop.freeze(2008, 9, 9, 9, 19, 19) do formatter.run_completed(nil) - expect(formatter.elapsed_time).to include("10 minutes 10 seconds") - expect(formatter.elapsed_time).not_to include("hours") + expect(formatter.elapsed_time).to eql(610.0) + expect(formatter.pretty_elapsed_time).to include("10 minutes 10 seconds") + expect(formatter.pretty_elapsed_time).not_to include("hours") end end it "prints hours, minutes and seconds when elapsed time is more than 3600 seconds" do Timecop.freeze(2008, 9, 9, 19, 19, 19) do formatter.run_completed(nil) - expect(formatter.elapsed_time).to include("10 hours 10 minutes 10 seconds") + expect(formatter.elapsed_time).to eql(36610.0) + expect(formatter.pretty_elapsed_time).to include("10 hours 10 minutes 10 seconds") end end end -- cgit v1.2.1 From 51e98c798e455c9e758d9ace22d1391b7aad596c Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Tue, 1 Sep 2015 17:32:16 -0400 Subject: Remove Timecop dependency --- Gemfile | 1 - lib/chef/formatters/doc.rb | 2 +- spec/unit/formatters/doc_spec.rb | 47 ++++++++++++++++------------------------ 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/Gemfile b/Gemfile index 0f547c1645..af0bef493c 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,6 @@ group(:development, :test) do gem "simplecov" gem 'rack', "~> 1.5.1" gem 'cheffish', "~> 1.3" - gem 'timecop' gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/) end diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb index 2b0161667b..70108f547b 100644 --- a/lib/chef/formatters/doc.rb +++ b/lib/chef/formatters/doc.rb @@ -33,7 +33,7 @@ class Chef time = elapsed_time if time < 60 then message = Time.at(time).utc.strftime("%S seconds") - elsif time < 3600 then + elsif time < 3600 then message = Time.at(time).utc.strftime("%M minutes %S seconds") else message = Time.at(time).utc.strftime("%H hours %M minutes %S seconds") diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb index 7db5ebc348..7266afc320 100644 --- a/spec/unit/formatters/doc_spec.rb +++ b/spec/unit/formatters/doc_spec.rb @@ -18,22 +18,13 @@ # require 'spec_helper' -require 'timecop' describe Chef::Formatters::Base do let(:out) { StringIO.new } let(:err) { StringIO.new } - after do - Timecop.return - end - - subject(:formatter) { - Timecop.freeze(Time.local(2008, 9, 9, 9, 9, 9)) do - Chef::Formatters::Doc.new(out, err) - end - } + subject(:formatter) { Chef::Formatters::Doc.new(out, err) } it "prints a policyfile's name and revision ID" do minimal_policyfile = { @@ -59,29 +50,29 @@ describe Chef::Formatters::Base do end it "prints only seconds when elapsed time is less than 60 seconds" do - Timecop.freeze(2008, 9, 9, 9, 9, 19) do - formatter.run_completed(nil) - expect(formatter.elapsed_time).to eql(10.0) - expect(formatter.pretty_elapsed_time).to include("10 seconds") - expect(formatter.pretty_elapsed_time).not_to include("minutes") - expect(formatter.pretty_elapsed_time).not_to include("hours") - end + @now = Time.now + allow(Time).to receive(:now).and_return(@now, @now + 10.0) + formatter.run_completed(nil) + expect(formatter.elapsed_time).to eql(10.0) + expect(formatter.pretty_elapsed_time).to include("10 seconds") + expect(formatter.pretty_elapsed_time).not_to include("minutes") + expect(formatter.pretty_elapsed_time).not_to include("hours") end it "prints minutes and seconds when elapsed time is more than 60 seconds" do - Timecop.freeze(2008, 9, 9, 9, 19, 19) do - formatter.run_completed(nil) - expect(formatter.elapsed_time).to eql(610.0) - expect(formatter.pretty_elapsed_time).to include("10 minutes 10 seconds") - expect(formatter.pretty_elapsed_time).not_to include("hours") - end + @now = Time.now + allow(Time).to receive(:now).and_return(@now, @now + 610.0) + formatter.run_completed(nil) + expect(formatter.elapsed_time).to eql(610.0) + expect(formatter.pretty_elapsed_time).to include("10 minutes 10 seconds") + expect(formatter.pretty_elapsed_time).not_to include("hours") end it "prints hours, minutes and seconds when elapsed time is more than 3600 seconds" do - Timecop.freeze(2008, 9, 9, 19, 19, 19) do - formatter.run_completed(nil) - expect(formatter.elapsed_time).to eql(36610.0) - expect(formatter.pretty_elapsed_time).to include("10 hours 10 minutes 10 seconds") - end + @now = Time.now + allow(Time).to receive(:now).and_return(@now, @now + 36610.0) + formatter.run_completed(nil) + expect(formatter.elapsed_time).to eql(36610.0) + expect(formatter.pretty_elapsed_time).to include("10 hours 10 minutes 10 seconds") end end -- cgit v1.2.1 From 9b102712b7e28e7dda2033096dcef9179c7ed1a1 Mon Sep 17 00:00:00 2001 From: Thom May Date: Wed, 2 Sep 2015 13:49:19 +0100 Subject: Update Changelog --- CHANGELOG.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f02eee2f..6df1861cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,13 +18,25 @@ * [**James Belchamber**](https://github.com/JamesBelchamber): [pr#1796](https://github.com/chef/chef/pull/1796): make mount options aware * [**Nate Walck**](https://github.com/natewalck): - [pr#3594](https://github.com/chef/chef/pull/3594): Update service provider for OSX 10.11 -* [**Nate Walck**](https://github.com/natewalck): - [pr#3704](https://github.com/chef/chef/pull/3704): Add SIP (OS X 10.11) support + - [pr#3594](https://github.com/chef/chef/pull/3594): Update service provider for OSX 10.11 + - [pr#3704](https://github.com/chef/chef/pull/3704): Add SIP (OS X 10.11) support * [**Phil Dibowitz**](https://github.com/jaymzh): [pr#3805](https://github.com/chef/chef/pull/3805) LWRP parameter validators should use truthiness * [**Igor Shpakov**](https://github.com/Igorshp): [pr#3743](https://github.com/chef/chef/pull/3743) speed improvement for `remote_directory` resource +* [**James FitzGibbon**](https://github.com/jf647): + [pr#3027](https://github.com/chef/chef/pull/3027) Add warnings to 'knife node run list remove ...' +* [**Backslasher**](https://github.com/backslasher): + [pr#3172](https://github.com/chef/chef/pull/3172) Migrated deploy resource to use shell\_out instead of run\_command +* [**Sean Walberg**](https://github.com/swalberg): + [pr#3190](https://github.com/chef/chef/pull/3190) Allow tags to be set on a node during bootstrap +* [**ckaushik**](https://github.com/ckaushik) and [**Sam Dunne**](https://github.com/samdunne): + [pr#3510](https://github.com/chef/chef/pull/3510) Fix broken rendering +of partial templates. +* [**Simon Detheridge**](https://github.com/gh2k): + [pr#3806](https://github.com/chef/chef/pull/3806) Replace output\_of\_command with shell\_out! in subversion provider +* [**Joel Handwell**](https://github.com/joelhandwell): + [pr#3821](https://github.com/chef/chef/pull/3821) Human friendly elapsed time in log * [pr#3799](https://github.com/chef/chef/pull/3799) fix supports hash issues in service providers * [pr#3817](https://github.com/chef/chef/pull/3817) Remove now-useless forcing of ruby Garbage Collector run -- cgit v1.2.1 From a3ac271fdd43a2f6ecf4c6dbe825b0f03f154fa3 Mon Sep 17 00:00:00 2001 From: Dave Eddy Date: Mon, 16 Mar 2015 20:23:42 -0400 Subject: fix locking/unlocking users on SmartOS --- lib/chef/provider/user/solaris.rb | 36 ++++++++++++++-- spec/unit/provider/user/solaris_spec.rb | 75 +++++++++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 13 deletions(-) diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb index b242095f0c..c16db22ad4 100644 --- a/lib/chef/provider/user/solaris.rb +++ b/lib/chef/provider/user/solaris.rb @@ -1,7 +1,9 @@ # # Author:: Stephen Nelson-Smith () # Author:: Jon Ramsey () +# Author:: Dave Eddy () # Copyright:: Copyright (c) 2012 Opscode, Inc. +# Copyright:: Copyright 2015, Dave Eddy # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +25,6 @@ class Chef class User class Solaris < Chef::Provider::User::Useradd provides :user, platform: %w(omnios solaris2) - UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]] attr_writer :password_file @@ -43,6 +44,32 @@ class Chef super end + def check_lock + shadow_line = shell_out!('getent', 'shadow', new_resource.username).stdout.strip rescue nil + + # if the command fails we return nil, this can happen if the user + # in question doesn't exist + return nil if shadow_line.nil? + + # convert "dave:NP:16507::::::\n" to "NP" + fields = shadow_line.split(':') + + # '*LK*...' and 'LK' are both considered locked, + # so look for LK at the beginning of the shadow entry + # optionally surrounded by '*' + @locked = !!fields[1].match(/^\*?LK\*?/) + + @locked + end + + def lock_user + shell_out!('passwd', '-l', new_resource.username) + end + + def unlock_user + shell_out!('passwd', '-u', new_resource.username) + end + private def manage_password @@ -67,9 +94,10 @@ class Chef buffer.close # FIXME: mostly duplicates code with file provider deploying a file - mode = ::File.stat(@password_file).mode & 07777 - uid = ::File.stat(@password_file).uid - gid = ::File.stat(@password_file).gid + s = ::File.stat(@password_file) + mode = s.mode & 07777 + uid = s.uid + gid = s.gid FileUtils.chown uid, gid, buffer.path FileUtils.chmod mode, buffer.path diff --git a/spec/unit/provider/user/solaris_spec.rb b/spec/unit/provider/user/solaris_spec.rb index ef62fd1d5a..a3c17a9a56 100644 --- a/spec/unit/provider/user/solaris_spec.rb +++ b/spec/unit/provider/user/solaris_spec.rb @@ -1,7 +1,9 @@ # # Author:: Adam Jacob () # Author:: Daniel DeLeo () +# Author:: Dave Eddy () # Copyright:: Copyright (c) 2008, 2010 Opscode, Inc. +# Copyright:: Copyright (c) 2015, Dave Eddy # # License:: Apache License, Version 2.0 # @@ -18,6 +20,9 @@ # limitations under the License. # +ShellCmdResult = Struct.new(:stdout, :stderr, :exitstatus) + +require 'mixlib/shellout' require 'spec_helper' describe Chef::Provider::User::Solaris do @@ -31,15 +36,6 @@ describe Chef::Provider::User::Solaris do p end - supported_useradd_options = { - 'comment' => "-c", - 'gid' => "-g", - 'uid' => "-u", - 'shell' => "-s" - } - - include_examples "a useradd-based user provider", supported_useradd_options - describe "when we want to set a password" do before(:each) do @node = Chef::Node.new @@ -77,4 +73,65 @@ describe Chef::Provider::User::Solaris do end end + describe 'when managing user locked status' do + before(:each) do + @node = Chef::Node.new + @events = Chef::EventDispatch::Dispatcher.new + @run_context = Chef::RunContext.new(@node, {}, @events) + + @new_resource = Chef::Resource::User.new('dave') + @current_resource = @new_resource.dup + + @provider = Chef::Provider::User::Solaris.new(@new_resource, @run_context) + @provider.current_resource = @current_resource + end + describe 'when determining if the user is locked' do + + # locked shadow lines + [ + 'dave:LK:::::::', + 'dave:*LK*:::::::', + 'dave:*LK*foobar:::::::', + 'dave:*LK*bahamas10:::::::', + 'dave:*LK*L....:::::::', + ].each do |shadow| + it "should return true if user is locked with #{shadow}" do + shell_return = ShellCmdResult.new(shadow + "\n", '', 0) + expect(provider).to receive(:shell_out!).with('getent', 'shadow', @new_resource.username).and_return(shell_return) + expect(provider.check_lock).to eql(true) + end + end + + # unlocked shadow lines + [ + 'dave:NP:::::::', + 'dave:*NP*:::::::', + 'dave:foobar:::::::', + 'dave:bahamas10:::::::', + 'dave:L...:::::::', + ].each do |shadow| + it "should return false if user is unlocked with #{shadow}" do + shell_return = ShellCmdResult.new(shadow + "\n", '', 0) + expect(provider).to receive(:shell_out!).with('getent', 'shadow', @new_resource.username).and_return(shell_return) + expect(provider.check_lock).to eql(false) + end + end + end + + describe 'when locking the user' do + it 'should run passwd -l with the new resources username' do + shell_return = ShellCmdResult.new('', '', 0) + expect(provider).to receive(:shell_out!).with('passwd', '-l', @new_resource.username).and_return(shell_return) + provider.lock_user + end + end + + describe 'when unlocking the user' do + it 'should run passwd -u with the new resources username' do + shell_return = ShellCmdResult.new('', '', 0) + expect(provider).to receive(:shell_out!).with('passwd', '-u', @new_resource.username).and_return(shell_return) + provider.unlock_user + end + end + end end -- cgit v1.2.1 From c9a7f550d8c01f56f980a2a6fb09b4260518c755 Mon Sep 17 00:00:00 2001 From: Thom May Date: Wed, 2 Sep 2015 13:49:19 +0100 Subject: Update Changelog --- CHANGELOG.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f02eee2f..6df1861cec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,13 +18,25 @@ * [**James Belchamber**](https://github.com/JamesBelchamber): [pr#1796](https://github.com/chef/chef/pull/1796): make mount options aware * [**Nate Walck**](https://github.com/natewalck): - [pr#3594](https://github.com/chef/chef/pull/3594): Update service provider for OSX 10.11 -* [**Nate Walck**](https://github.com/natewalck): - [pr#3704](https://github.com/chef/chef/pull/3704): Add SIP (OS X 10.11) support + - [pr#3594](https://github.com/chef/chef/pull/3594): Update service provider for OSX 10.11 + - [pr#3704](https://github.com/chef/chef/pull/3704): Add SIP (OS X 10.11) support * [**Phil Dibowitz**](https://github.com/jaymzh): [pr#3805](https://github.com/chef/chef/pull/3805) LWRP parameter validators should use truthiness * [**Igor Shpakov**](https://github.com/Igorshp): [pr#3743](https://github.com/chef/chef/pull/3743) speed improvement for `remote_directory` resource +* [**James FitzGibbon**](https://github.com/jf647): + [pr#3027](https://github.com/chef/chef/pull/3027) Add warnings to 'knife node run list remove ...' +* [**Backslasher**](https://github.com/backslasher): + [pr#3172](https://github.com/chef/chef/pull/3172) Migrated deploy resource to use shell\_out instead of run\_command +* [**Sean Walberg**](https://github.com/swalberg): + [pr#3190](https://github.com/chef/chef/pull/3190) Allow tags to be set on a node during bootstrap +* [**ckaushik**](https://github.com/ckaushik) and [**Sam Dunne**](https://github.com/samdunne): + [pr#3510](https://github.com/chef/chef/pull/3510) Fix broken rendering +of partial templates. +* [**Simon Detheridge**](https://github.com/gh2k): + [pr#3806](https://github.com/chef/chef/pull/3806) Replace output\_of\_command with shell\_out! in subversion provider +* [**Joel Handwell**](https://github.com/joelhandwell): + [pr#3821](https://github.com/chef/chef/pull/3821) Human friendly elapsed time in log * [pr#3799](https://github.com/chef/chef/pull/3799) fix supports hash issues in service providers * [pr#3817](https://github.com/chef/chef/pull/3817) Remove now-useless forcing of ruby Garbage Collector run -- cgit v1.2.1 From c81cf7f5606480d06ae321b913925d1e341f986b Mon Sep 17 00:00:00 2001 From: Thom May Date: Wed, 2 Sep 2015 13:56:14 +0100 Subject: Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df1861cec..ccefa07544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ * [**John Kerry**](https://github.com/jkerry): [pr#3539](https://github.com/chef/chef/pull/3539) Fix issue: registry\_key resource is case sensitive in chef but not on windows * [**David Eddy**](https://github.com/bahamas10): - [pr#3443](https://github.com/chef/chef/pull/3443) remove extraneous space + - [pr#3443](https://github.com/chef/chef/pull/3443) remove extraneous space + - [pr#3091](https://github.com/chef/chef/pull/3091) fix locking/unlocking users on SmartOS * [**margueritepd**](https://github.com/margueritepd): [pr#3693](https://github.com/chef/chef/pull/3693) Interpolate `%{path}` in verify command * [**Jeremy Fleischman**](https://github.com/jfly): -- cgit v1.2.1 From 7580ec078e87225766ceced6233f72f3a728008b Mon Sep 17 00:00:00 2001 From: Matt Wrock Date: Wed, 2 Sep 2015 09:09:38 -0700 Subject: adding matt wrock as maintainer --- MAINTAINERS.md | 2 ++ MAINTAINERS.toml | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 3c777366f8..0e4bc4d214 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -40,6 +40,7 @@ another component. * [Steven Murawski](https://github.com/smurawski) * [Tyler Ball](https://github.com/tyler-ball) * [Ranjib Dey](https://github.com/ranjib) +* [Matt Wrock](https://github.com/mwrock) ## Dev Tools @@ -102,6 +103,7 @@ The specific components of Chef related to a given platform - including (but not * [Kartik Cating-Subramanian](https://github.com/ksubrama) * [Steven Murawski](https://github.com/smurawski) * [Salim Alam](https://github.com/chefsalim) +* [Matt Wrock](https://github.com/mwrock) ## Solaris diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml index 61c75c1a30..3754618796 100644 --- a/MAINTAINERS.toml +++ b/MAINTAINERS.toml @@ -44,7 +44,8 @@ another component. "mcquin", "smurawski", "tyler-ball", - "ranjib" + "ranjib", + "mwrock" ] [Org.Components.DevTools] @@ -112,7 +113,8 @@ The specific components of Chef related to a given platform - including (but not "jdmundrawala", "ksubrama", "smurawski", - "chefsalim" + "chefsalim", + "mwrock" ] [Org.Components.Subsystems.Solaris] @@ -305,3 +307,7 @@ The specific components of Chef related to a given platform - including (but not [people.chefsalim] Name = "Salim Alam" GitHub = "chefsalim" + + [people.mwrock] + Name = "Matt Wrock" + GitHub = "mwrock" -- cgit v1.2.1 From 2528c6707beed1b5cdd8029d77d56eee54eff30d Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Fri, 28 Aug 2015 10:59:20 -0700 Subject: refactor remote_directory provider - Huge speed and memory perf boost. In the prior code if you were copying 10 dotfiles to a home directory with a million files in it, would slurp all million files into a ruby Set object even if you were not ultimately going to purge the unmanaged files. This code inverts the logic and tracks managed files and then iterates through the filesystem without slurping a list into memory. - Only do file purging logic if purging is set. - Fixes mutation of new_resource.overwrite. - Fixes mutation of new_resource.rights (subtle). - Adds helper delegators to the new_resource properties. - Deprecates (instead of removes) now-unused methods. - Renamed a method (with deprecated alias preserved) for consistency. - Adds YARD for everything. - Changes protected to private because protected is largely useless in ruby. - Removes whyrun_supported? because the superclass sets that. --- lib/chef/deprecation/provider/remote_directory.rb | 52 ++++ lib/chef/provider/remote_directory.rb | 294 ++++++++++++++-------- spec/unit/provider/remote_directory_spec.rb | 3 +- 3 files changed, 245 insertions(+), 104 deletions(-) create mode 100644 lib/chef/deprecation/provider/remote_directory.rb diff --git a/lib/chef/deprecation/provider/remote_directory.rb b/lib/chef/deprecation/provider/remote_directory.rb new file mode 100644 index 0000000000..b55a304696 --- /dev/null +++ b/lib/chef/deprecation/provider/remote_directory.rb @@ -0,0 +1,52 @@ +# +# Author:: Serdar Sutay () +# Copyright:: Copyright (c) 2013-2015 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. +# + +class Chef + module Deprecation + module Provider + module RemoteDirectory + + def directory_root_in_cookbook_cache + Chef::Log.deprecation "the Chef::Provider::RemoteDirectory#directory_root_in_cookbook_cache method is deprecated" + + @directory_root_in_cookbook_cache ||= + begin + cookbook = run_context.cookbook_collection[resource_cookbook] + cookbook.preferred_filename_on_disk_location(node, :files, source, path) + end + end + + # List all excluding . and .. + def ls(path) + files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'), + ::File::FNM_DOTMATCH) + + # Remove current directory and previous directory + files = files.reject do |name| + basename = Pathname.new(name).basename().to_s + ['.', '..'].include?(basename) + end + + # Clean all the paths... this is required because of the join + files.map {|f| Chef::Util::PathHelper.cleanpath(f)} + end + + end + end + end +end diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb index 85ceb5cdae..56bad5ac42 100644 --- a/lib/chef/provider/remote_directory.rb +++ b/lib/chef/provider/remote_directory.rb @@ -1,6 +1,6 @@ # # Author:: Adam Jacob () -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,178 +16,268 @@ # limitations under the License. # -require 'chef/provider/file' require 'chef/provider/directory' +require 'chef/resource/file' require 'chef/resource/directory' -require 'chef/resource/remote_file' +require 'chef/resource/cookbook_file' require 'chef/mixin/file_class' -require 'chef/platform' -require 'uri' -require 'tempfile' -require 'net/https' -require 'set' +require 'chef/platform/query_helpers' require 'chef/util/path_helper' +require 'chef/deprecation/warnings' +require 'chef/deprecation/provider/remote_directory' + +require 'forwardable' class Chef class Provider class RemoteDirectory < Chef::Provider::Directory + extend Forwardable + include Chef::Mixin::FileClass provides :remote_directory - include Chef::Mixin::FileClass + def_delegators :@new_resource, :purge, :path, :source, :cookbook, :cookbook_name + def_delegators :@new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup + def_delegators :@new_resource, :rights, :mode, :group, :owner + + attr_accessor :overwrite + + # The overwrite property on the resource. Delegates to new_resource but can be mutated. + # + # @return [Boolean] if we are overwriting + # + def overwrite + @overwrite = new_resource.overwrite if @overwrite.nil? + @overwrite + end + + attr_accessor :managed_files + # Hash containing keys of the paths for all the files that we sync, plus all their + # parent directories. + # + # @return [Hash{String => TrueClass}] Hash of files we've managed. + # + def managed_files + @managed_files ||= {} + end + + # Handle action :create. + # def action_create super - # Mark all files as needing to be purged - files_to_purge = Set.new(ls(@new_resource.path)) # Make sure each path is clean # Transfer files files_to_transfer.each do |cookbook_file_relative_path| create_cookbook_file(cookbook_file_relative_path) - # parent directories and file being transferred are removed from the purge list - Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(@new_resource.path, cookbook_file_relative_path))).descend do |d| - files_to_purge.delete(d.to_s) - end + # parent directories and file being transferred need to not be removed in the purge + add_managed_file(cookbook_file_relative_path) end - purge_unmanaged_files(files_to_purge) + purge_unmanaged_files end + # Handle action :create_if_missing. + # def action_create_if_missing # if this action is called, ignore the existing overwrite flag - @new_resource.overwrite(false) + @overwrite = false action_create end - protected - - # List all excluding . and .. - def ls(path) - files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'), - ::File::FNM_DOTMATCH) + private - # Remove current directory and previous directory - files = files.reject do |name| - basename = Pathname.new(name).basename().to_s - ['.', '..'].include?(basename) + # Add a file and its parent directories to the managed_files Hash. + # + # @param [String] cookbook_file_relative_path relative path to the file + # @api private + # + def add_managed_file(cookbook_file_relative_path) + if purge + Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(path, cookbook_file_relative_path))).descend do |d| + managed_files[d.to_s] = true + end end - - # Clean all the paths... this is required because of the join - files.map {|f| Chef::Util::PathHelper.cleanpath(f)} end - def purge_unmanaged_files(unmanaged_files) - if @new_resource.purge - unmanaged_files.sort.reverse.each do |f| - # file_class comes from Chef::Mixin::FileClass - if ::File.directory?(f) && !Chef::Platform.windows? && !file_class.symlink?(f.dup) - # Linux treats directory symlinks as files - # Remove a directory as a directory when not on windows if it is not a symlink - purge_directory(f) - elsif ::File.directory?(f) && Chef::Platform.windows? - # Windows treats directory symlinks as directories so we delete them here - purge_directory(f) - else - converge_by("delete unmanaged file #{f}") do - ::File.delete(f) - Chef::Log.debug("#{@new_resource} deleted file #{f}") + # Remove all files not in the managed_files Hash. + # + # @api private + # + def purge_unmanaged_files + if purge + Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'), ::File::FNM_DOTMATCH).sort!.reverse!.each do |file| + # skip '.' and '..' + next if ['.','..'].include?(Pathname.new(file).basename().to_s) + + # Clean the path. This is required because of the ::File.join + file = Chef::Util::PathHelper.cleanpath(file) + + # Skip files that we've sync'd and their parent dirs + next if managed_files.include?(file) + + if ::File.directory?(file) + if !Chef::Platform.windows? && file_class.symlink?(file.dup) + # Unix treats dir symlinks as files + purge_file(file) + else + # Unix dirs are dirs, Windows dirs and dir symlinks are dirs + purge_directory(file) end + else + purge_file(file) end end end end + # Use a Chef directory sub-resource to remove a directory. + # + # @param [String] dir The path of the directory to remove + # @api private + # def purge_directory(dir) - converge_by("delete unmanaged directory #{dir}") do - Dir::rmdir(dir) - Chef::Log.debug("#{@new_resource} removed directory #{dir}") - end + res = Chef::Resource::Directory.new(dir, run_context) + res.run_action(:delete) + new_resource.updated_by_last_action(true) if res.updated? + end + + # Use a Chef file sub-resource to remove a file. + # + # @param [String] file The path of the file to remove + # @api private + # + def purge_file(file) + res = Chef::Resource::File.new(file, run_context) + res.run_action(:delete) + new_resource.updated_by_last_action(true) if res.updated? end + # Get the files to tranfer. This returns files in lexicographical sort order. + # + # FIXME: it should do breadth-first, see CHEF-5080 (please use a performant sort) + # + # @return Array The list of files to transfer + # @api private + # def files_to_transfer cookbook = run_context.cookbook_collection[resource_cookbook] - files = cookbook.relative_filenames_in_preferred_directory(node, :files, @new_resource.source) - files.sort.reverse + files = cookbook.relative_filenames_in_preferred_directory(node, :files, source) + files.sort!.reverse! end - def directory_root_in_cookbook_cache - @directory_root_in_cookbook_cache ||= begin - cookbook = run_context.cookbook_collection[resource_cookbook] - cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path) - end + # Either the explicit cookbook that the user sets on the resource, or the implicit + # cookbook_name that the resource was declared in. + # + # @return [String] Cookbook to get file from. + # @api private + # + def resource_cookbook + cookbook || cookbook_name end - # Determine the cookbook to get the file from. If new resource sets an - # explicit cookbook, use it, otherwise fall back to the implicit cookbook - # i.e., the cookbook the resource was declared in. - def resource_cookbook - @new_resource.cookbook || @new_resource.cookbook_name + # If we are overwriting, then cookbook_file sub-resources should all be action :create, + # otherwise they should be :create_if_missing + # + # @return [Symbol] Action to take on cookbook_file sub-resources + # @api private + # + def action_for_cookbook_file + overwrite ? :create : :create_if_missing end + # This creates and uses a cookbook_file resource to sync a single file from the cookbook. + # + # @param [String] cookbook_file_relative_path The relative path to the cookbook file + # @api private + # def create_cookbook_file(cookbook_file_relative_path) - full_path = ::File.join(@new_resource.path, cookbook_file_relative_path) + full_path = ::File.join(path, cookbook_file_relative_path) ensure_directory_exists(::File.dirname(full_path)) - file_to_fetch = cookbook_file_resource(full_path, cookbook_file_relative_path) - if @new_resource.overwrite - file_to_fetch.run_action(:create) - else - file_to_fetch.run_action(:create_if_missing) - end - @new_resource.updated_by_last_action(true) if file_to_fetch.updated? + res = cookbook_file_resource(full_path, cookbook_file_relative_path) + res.run_action(action_for_cookbook_file) + new_resource.updated_by_last_action(true) if res.updated? end + # This creates the cookbook_file resource for use by create_cookbook_file. + # + # @param [String] target_path Path on the system to create + # @param [String] relative_source_path Relative path in the cookbook to the base source + # @return [Chef::Resource::CookbookFile] The built cookbook_file resource + # @api private + # def cookbook_file_resource(target_path, relative_source_path) - cookbook_file = Chef::Resource::CookbookFile.new(target_path, run_context) - cookbook_file.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name - cookbook_file.source(::File.join(@new_resource.source, relative_source_path)) - if Chef::Platform.windows? && @new_resource.files_rights - @new_resource.files_rights.each_pair do |permission, *args| - cookbook_file.rights(permission, *args) + res = Chef::Resource::CookbookFile.new(target_path, run_context) + res.cookbook_name = resource_cookbook + res.source(::File.join(source, relative_source_path)) + if Chef::Platform.windows? && files_rights + files_rights.each_pair do |permission, *args| + res.rights(permission, *args) end end - cookbook_file.mode(@new_resource.files_mode) if @new_resource.files_mode - cookbook_file.group(@new_resource.files_group) if @new_resource.files_group - cookbook_file.owner(@new_resource.files_owner) if @new_resource.files_owner - cookbook_file.backup(@new_resource.files_backup) if @new_resource.files_backup + res.mode(files_mode) if files_mode + res.group(files_group) if files_group + res.owner(files_owner) if files_owner + res.backup(files_backup) if files_backup - cookbook_file + res end - def ensure_directory_exists(path) - unless ::File.directory?(path) - directory_to_create = resource_for_directory(path) - directory_to_create.run_action(:create) - @new_resource.updated_by_last_action(true) if directory_to_create.updated? + # This creates and uses a directory resource to create a directory if it is needed. + # + # @param [String] dir The path to the directory to create. + # @api private + # + def ensure_directory_exists(dir) + # doing the check here and skipping the resource should be more performant + unless ::File.directory?(dir) + res = directory_resource(dir) + res.run_action(:create) + new_resource.updated_by_last_action(true) if res.updated? end end - def resource_for_directory(path) - dir = Chef::Resource::Directory.new(path, run_context) - dir.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name - if Chef::Platform.windows? && @new_resource.rights + # This creates the directory resource for ensure_directory_exists. + # + # @param [String] dir Directory path on the system + # @return [Chef::Resource::Directory] The built directory resource + # @api private + # + def directory_resource(dir) + res = Chef::Resource::Directory.new(dir, run_context) + res.cookbook_name = resource_cookbook + if Chef::Platform.windows? && rights # rights are only meant to be applied to the toppest-level directory; # Windows will handle inheritance. - if path == @new_resource.path - @new_resource.rights.each do |rights| #rights is a hash - permissions = rights.delete(:permissions) #delete will return the value or nil if not found - principals = rights.delete(:principals) - dir.rights(permissions, principals, rights) + if dir == path + rights.each do |r| + r = r.dup # do not update the new_resource + permissions = r.delete(:permissions) + principals = r.delete(:principals) + res.rights(permissions, principals, r) end end end - dir.mode(@new_resource.mode) if @new_resource.mode - dir.group(@new_resource.group) - dir.owner(@new_resource.owner) - dir.recursive(true) - dir - end + res.mode(mode) if mode + res.group(group) if group + res.owner(owner) if owner + res.recursive(true) - def whyrun_supported? - true + res end + # + # Add back deprecated methods and aliases that are internally unused and should be removed in Chef-13 + # + extend Chef::Deprecation::Warnings + include Chef::Deprecation::Provider::RemoteDirectory + add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteDirectory.instance_methods) + + alias_method :resource_for_directory, :directory_resource + add_deprecation_warnings_for([:resource_for_directory]) + end end end diff --git a/spec/unit/provider/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb index 99e2fe285c..6426dafd79 100644 --- a/spec/unit/provider/remote_directory_spec.rb +++ b/spec/unit/provider/remote_directory_spec.rb @@ -79,7 +79,7 @@ describe Chef::Provider::RemoteDirectory do end it "configures access control on intermediate directorys" do - directory_resource = @provider.send(:resource_for_directory, File.join(Dir.tmpdir, "intermediate_dir")) + directory_resource = @provider.send(:directory_resource, File.join(Dir.tmpdir, "intermediate_dir")) expect(directory_resource.path).to eq(File.join(Dir.tmpdir, "intermediate_dir")) expect(directory_resource.mode).to eq("0750") expect(directory_resource.group).to eq("wheel") @@ -219,4 +219,3 @@ describe Chef::Provider::RemoteDirectory do end end - -- cgit v1.2.1 From d9695334d70471dbcb2548960d31a13a0f19fab6 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Mon, 31 Aug 2015 15:36:05 -0700 Subject: review fixes - convert to Set - use #override? --- lib/chef/provider/remote_directory.rb | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb index 56bad5ac42..56c2ff0caf 100644 --- a/lib/chef/provider/remote_directory.rb +++ b/lib/chef/provider/remote_directory.rb @@ -40,15 +40,13 @@ class Chef def_delegators :@new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup def_delegators :@new_resource, :rights, :mode, :group, :owner - attr_accessor :overwrite - # The overwrite property on the resource. Delegates to new_resource but can be mutated. # # @return [Boolean] if we are overwriting # - def overwrite + def overwrite? @overwrite = new_resource.overwrite if @overwrite.nil? - @overwrite + !!@overwrite end attr_accessor :managed_files @@ -56,10 +54,10 @@ class Chef # Hash containing keys of the paths for all the files that we sync, plus all their # parent directories. # - # @return [Hash{String => TrueClass}] Hash of files we've managed. + # @return [Set] Ruby Set of the files that we manage # def managed_files - @managed_files ||= {} + @managed_files ||= Set.new end # Handle action :create. @@ -95,12 +93,12 @@ class Chef def add_managed_file(cookbook_file_relative_path) if purge Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(path, cookbook_file_relative_path))).descend do |d| - managed_files[d.to_s] = true + managed_files.add(d.to_s) end end end - # Remove all files not in the managed_files Hash. + # Remove all files not in the managed_files Set. # # @api private # @@ -183,7 +181,7 @@ class Chef # @api private # def action_for_cookbook_file - overwrite ? :create : :create_if_missing + overwrite? ? :create : :create_if_missing end # This creates and uses a cookbook_file resource to sync a single file from the cookbook. -- cgit v1.2.1 From 6109fb06c81b63e2a6da8b9c179b691f4c276412 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Wed, 2 Sep 2015 17:50:35 -0700 Subject: add CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccefa07544..f66a1400ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ of partial templates. * [**Joel Handwell**](https://github.com/joelhandwell): [pr#3821](https://github.com/chef/chef/pull/3821) Human friendly elapsed time in log +* [pr#3837](https://github.com/chef/chef/pull/3837) refactor remote_directory provider for mem+perf improvement * [pr#3799](https://github.com/chef/chef/pull/3799) fix supports hash issues in service providers * [pr#3817](https://github.com/chef/chef/pull/3817) Remove now-useless forcing of ruby Garbage Collector run * [pr#3774](https://github.com/chef/chef/pull/3774) Add support for yum-deprecated in yum provider -- cgit v1.2.1 From 55a6507b98b69652d9b5c74f4209c6472e356834 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Tue, 1 Sep 2015 15:27:48 -0700 Subject: Monkey-patch Win32::Registry::API::DeleteValue for Win32::Registry#delete_value. --- lib/chef/monkey_patches/win32/registry.rb | 23 +++++++++++++++++++++++ lib/chef/win32/api/registry.rb | 6 ++++++ lib/chef/win32/registry.rb | 4 ++++ 3 files changed, 33 insertions(+) create mode 100644 lib/chef/monkey_patches/win32/registry.rb diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb new file mode 100644 index 0000000000..c7279ced92 --- /dev/null +++ b/lib/chef/monkey_patches/win32/registry.rb @@ -0,0 +1,23 @@ + +require 'chef/win32/api/registry' +require 'chef/win32/unicode' +require 'win32/registry' + +module Win32 + class Registry + module API + + extend Chef::ReservedNames::Win32::API::Registry + + module_function + + # ::Win32::Registry#delete_value is broken in Ruby 2.1 (up to Ruby 2.1.6p336). + # This should be resolved a later release (see note #9 in link below). + # https://bugs.ruby-lang.org/issues/10820 + def DeleteValue(hkey, name) + check RegDeleteValueW(hkey, name.to_wstring) + end + + end + end +end \ No newline at end of file diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb index 45b91d7d32..cbbf6b66bb 100644 --- a/lib/chef/win32/api/registry.rb +++ b/lib/chef/win32/api/registry.rb @@ -39,6 +39,12 @@ class Chef safe_attach_function :RegDeleteKeyExW, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG safe_attach_function :RegDeleteKeyExA, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG + # LONG WINAPI RegDeleteValue( + # _In_ HKEY hKey, + # _In_opt_ LPCTSTR lpValueName + # ); + safe_attach_function :RegDeleteValueW, [ :HKEY, :LPCTSTR ], :LONG + end end end diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index b25ce7937e..877c2ebff3 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -21,6 +21,10 @@ require 'chef/win32/api' require 'chef/mixin/wide_string' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + if RUBY_VERSION =~ /^2\.1/ + require 'chef/monkey_patches/win32/registry' + end + require 'chef/win32/api/registry' require 'win32/registry' require 'win32/api' -- cgit v1.2.1 From 30b588f7035c2a219bc96a0b055f1d0eb8e16d3c Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Wed, 2 Sep 2015 09:11:26 -0700 Subject: Monkey-patch Win32::Registry::API::DeleteKey for Win32::Registry#delete_key --- lib/chef/monkey_patches/win32/registry.rb | 19 ++++++++++++++----- lib/chef/win32/registry.rb | 5 +---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb index c7279ced92..a754949ca0 100644 --- a/lib/chef/monkey_patches/win32/registry.rb +++ b/lib/chef/monkey_patches/win32/registry.rb @@ -11,11 +11,20 @@ module Win32 module_function - # ::Win32::Registry#delete_value is broken in Ruby 2.1 (up to Ruby 2.1.6p336). - # This should be resolved a later release (see note #9 in link below). - # https://bugs.ruby-lang.org/issues/10820 - def DeleteValue(hkey, name) - check RegDeleteValueW(hkey, name.to_wstring) + if RUBY_VERSION =~ /^2\.1/ + # ::Win32::Registry#delete_value is broken in Ruby 2.1 (up to Ruby 2.1.6). + # This should be resolved in a later release (see note #9 in link below). + # https://bugs.ruby-lang.org/issues/10820 + def DeleteValue(hkey, name) + check RegDeleteValueW(hkey, name.to_wstring) + end + end + + # ::Win32::Registry#delete_key uses RegDeleteKeyW. We need to use + # RegDeleteKeyExW to properly support WOW64 systems. + def DeleteKey(hkey, name) + arch_mask = win64? ? 0x0100 : 0x0200 + check RegDeleteKeyExW(hkey, name.to_wstring, KEY_WRITE | arch_mask, 0) end end diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index 877c2ebff3..3a01553445 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -21,10 +21,7 @@ require 'chef/win32/api' require 'chef/mixin/wide_string' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ - if RUBY_VERSION =~ /^2\.1/ - require 'chef/monkey_patches/win32/registry' - end - + require 'chef/monkey_patches/win32/registry' require 'chef/win32/api/registry' require 'win32/registry' require 'win32/api' -- cgit v1.2.1 From 89aaa6076b7c4c4aea0e797d8cfcf6639c90816a Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Thu, 3 Sep 2015 10:11:58 -0700 Subject: Delete Root key and subkeys --- spec/functional/win32/registry_helper_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/functional/win32/registry_helper_spec.rb b/spec/functional/win32/registry_helper_spec.rb index 9ef6fd006f..ba0ae12495 100644 --- a/spec/functional/win32/registry_helper_spec.rb +++ b/spec/functional/win32/registry_helper_spec.rb @@ -556,11 +556,11 @@ describe 'Chef::Win32::Registry', :windows_only do end after(:all) do - ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| - reg.delete_key("Trunk", true) + ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| + reg.delete_key("Root", true) end - ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| - reg.delete_key("Trunk", true) + ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| + reg.delete_key("Root", true) end end -- cgit v1.2.1 From c00a3bdc8e00e512f4c3cbb2ff7169ff20a66672 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Thu, 3 Sep 2015 10:16:49 -0700 Subject: Remove access mask to RegDeleteKeyExW, it's not useful --- lib/chef/monkey_patches/win32/registry.rb | 3 +-- lib/chef/win32/registry.rb | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb index a754949ca0..9471777c57 100644 --- a/lib/chef/monkey_patches/win32/registry.rb +++ b/lib/chef/monkey_patches/win32/registry.rb @@ -23,8 +23,7 @@ module Win32 # ::Win32::Registry#delete_key uses RegDeleteKeyW. We need to use # RegDeleteKeyExW to properly support WOW64 systems. def DeleteKey(hkey, name) - arch_mask = win64? ? 0x0100 : 0x0200 - check RegDeleteKeyExW(hkey, name.to_wstring, KEY_WRITE | arch_mask, 0) + check RegDeleteKeyExW(hkey, name.to_wstring, 0, 0) end end diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index 3a01553445..e699a9bc06 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -154,8 +154,7 @@ class Chef #Using the 'RegDeleteKeyEx' Windows API that correctly supports WOW64 systems (Win2003) #instead of the 'RegDeleteKey' def delete_key_ex(hive, key) - hive_num = hive.hkey - (1 << 32) - RegDeleteKeyExW(hive_num, wstring(key), ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0) == 0 + RegDeleteKeyExW(hive.hkey, wstring(key), 0, 0) == 0 end def key_exists?(key_path) -- cgit v1.2.1 From 25b77f4e175e8a56865d3c908f29147c6d41c93a Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Thu, 3 Sep 2015 11:15:27 -0700 Subject: Implement delete_key with Win32::Registry#delete_key --- lib/chef/win32/registry.rb | 39 +++++++++++++-------------------------- spec/unit/registry_helper_spec.rb | 15 ++++++--------- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb index e699a9bc06..12ad08a965 100644 --- a/lib/chef/win32/registry.rb +++ b/lib/chef/win32/registry.rb @@ -127,36 +127,23 @@ class Chef Chef::Log.debug("Registry key #{key_path}, does not exist, not deleting") return true end - #key_path is in the form "HKLM\Software\Opscode" for example, extracting - #hive = HKLM, - #hive_namespace = ::Win32::Registry::HKEY_LOCAL_MACHINE - hive = key_path.split("\\").shift - hive_namespace, key_including_parent = get_hive_and_key(key_path) - if has_subkeys?(key_path) - if recursive == true - subkeys = get_subkeys(key_path) - subkeys.each do |key| - keypath_to_check = hive+"\\"+key_including_parent+"\\"+key - Chef::Log.debug("Deleting registry key #{key_path} recursively") - delete_key(keypath_to_check, true) - end - delete_key_ex(hive_namespace, key_including_parent) - else - raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified" - end - else - delete_key_ex(hive_namespace, key_including_parent) - return true + if has_subkeys?(key_path) && !recursive + raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified" + end + hive, key_including_parent = get_hive_and_key(key_path) + # key_including_parent: Software\\Root\\Branch\\Fruit + # key => Fruit + # key_parent => Software\\Root\\Branch + key_parts = key_including_parent.split("\\") + key = key_parts.pop + key_parent = key_parts.join("\\") + hive.open(key_parent, ::Win32::Registry::KEY_WRITE | registry_system_architecture) do |reg| + reg.delete_key(key, recursive) end + Chef::Log.debug("Registry key #{key_path} deleted") true end - #Using the 'RegDeleteKeyEx' Windows API that correctly supports WOW64 systems (Win2003) - #instead of the 'RegDeleteKey' - def delete_key_ex(hive, key) - RegDeleteKeyExW(hive.hkey, wstring(key), 0, 0) == 0 - end - def key_exists?(key_path) hive, key = get_hive_and_key(key_path) begin diff --git a/spec/unit/registry_helper_spec.rb b/spec/unit/registry_helper_spec.rb index b2d0b7b125..a30608add7 100644 --- a/spec/unit/registry_helper_spec.rb +++ b/spec/unit/registry_helper_spec.rb @@ -176,28 +176,25 @@ describe Chef::Provider::RegistryKey do describe "delete_key", :windows_only do it "deletes key if it has subkeys and recursive is set to true" do expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) - expect(@registry).to receive(:get_subkeys).with(key_path).and_return([sub_key]) - expect(@registry).to receive(:key_exists?).with(key_path+"\\"+sub_key).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path+"\\"+sub_key).and_return([@hive_mock, key+"\\"+sub_key]) - expect(@registry).to receive(:has_subkeys?).with(key_path+"\\"+sub_key).and_return(false) - expect(@registry).to receive(:delete_key_ex).twice + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) @registry.delete_key(key_path, true) end it "raises an exception if it has subkeys but recursive is set to false" do expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) expect{@registry.delete_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "deletes key if the key exists and has no subkeys" do expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(false) - expect(@registry).to receive(:delete_key_ex) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) @registry.delete_key(key_path, true) end end -- cgit v1.2.1 From 86869f9aba02d8b40b3ee8c84f0cfaf50295a96e Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Thu, 3 Sep 2015 13:14:14 -0700 Subject: Add file header. --- lib/chef/monkey_patches/win32/registry.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb index 9471777c57..5ebe67c45d 100644 --- a/lib/chef/monkey_patches/win32/registry.rb +++ b/lib/chef/monkey_patches/win32/registry.rb @@ -1,3 +1,19 @@ +# +# Copyright:: Copyright 2015 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/win32/api/registry' require 'chef/win32/unicode' -- cgit v1.2.1 From 38c05678d7cfb266ad4cc5a74dc9769205c3ed00 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 3 Sep 2015 19:31:47 -0500 Subject: Pin chef-zero to ~> 4.2.3 --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index af0bef493c..ccd0735732 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,10 @@ gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" gem 'chef-config', path: "chef-config" +# We are pinning chef-zero to 4.2.x until ChefFS can deal +# with V1 api calls or chef-zero supports both v0 and v1 +gem "chef-zero", "~> 4.2.3" + group(:docgen) do gem "tomlrb" gem "yard" -- cgit v1.2.1 From c79db55f1c40b1a744cf026e2ff69dfef2fa10af Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 3 Sep 2015 19:32:08 -0500 Subject: Pin cheffish to ~> 1.3.1 --- Gemfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index ccd0735732..6660b8c802 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,11 @@ end group(:development, :test) do gem "simplecov" gem 'rack', "~> 1.5.1" - gem 'cheffish', "~> 1.3" + + #gem 'cheffish', "~> 1.3" + # We are pinning cheffish to 1.3.1 until https://github.com/chef/cheffish/pull/73 + # is released + gem 'cheffish', "~> 1.3.1" gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/) end -- cgit v1.2.1 From 138cbbe0f1437b92a5251ca86deeda65571810ba Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 3 Sep 2015 20:09:10 -0500 Subject: Pin chef-zero in pedant --- pedant.gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pedant.gemfile b/pedant.gemfile index 8e64fc039c..888e6b83f3 100644 --- a/pedant.gemfile +++ b/pedant.gemfile @@ -9,6 +9,10 @@ gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" gem "mixlib-shellout", github: "opscode/mixlib-shellout", branch: "master" gem "ohai", github: "opscode/ohai", branch: "master" +# We are pinning chef-zero to 4.2.x until ChefFS can deal +# with V1 api calls or chef-zero supports both v0 and v1 +gem "chef-zero", "~> 4.2.3" + group(:docgen) do gem "tomlrb" gem "yard" -- cgit v1.2.1 From a4cc5624c40e547122fb4c08ee228cf6d063793e Mon Sep 17 00:00:00 2001 From: John Keiser Date: Thu, 3 Sep 2015 21:28:40 -0700 Subject: Run against Cheffish changes --- Gemfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 6660b8c802..0a86f47b7e 100644 --- a/Gemfile +++ b/Gemfile @@ -18,10 +18,7 @@ group(:development, :test) do gem "simplecov" gem 'rack', "~> 1.5.1" - #gem 'cheffish', "~> 1.3" - # We are pinning cheffish to 1.3.1 until https://github.com/chef/cheffish/pull/73 - # is released - gem 'cheffish', "~> 1.3.1" + gem 'cheffish', github: 'chef/cheffish', branch: 'jk/cheffish_fix' # "~> 1.3" gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/) end -- cgit v1.2.1 From bc890071b8b1e0eba9aa04fa51a36311848ab1cc Mon Sep 17 00:00:00 2001 From: John Keiser Date: Thu, 3 Sep 2015 21:46:08 -0700 Subject: Run against the latest cheffish (1.3.2) --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 0a86f47b7e..6aae2c5b9e 100644 --- a/Gemfile +++ b/Gemfile @@ -18,7 +18,7 @@ group(:development, :test) do gem "simplecov" gem 'rack', "~> 1.5.1" - gem 'cheffish', github: 'chef/cheffish', branch: 'jk/cheffish_fix' # "~> 1.3" + gem 'cheffish', "~> 1.3", "!= 1.3.1" gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/) end -- cgit v1.2.1 From 33fa66713f26456992cfb6f7dff8ebbe2d9e2140 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Fri, 4 Sep 2015 10:09:34 -0700 Subject: Move Win32::Registry#write monkeypatch into win32/registry monkeypatch file. --- lib/chef/monkey_patches/win32/registry.rb | 31 ++++++++++++++++++++++++++++--- lib/chef/win32/unicode.rb | 27 --------------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb index 5ebe67c45d..2ce2ac97f1 100644 --- a/lib/chef/monkey_patches/win32/registry.rb +++ b/lib/chef/monkey_patches/win32/registry.rb @@ -21,8 +21,9 @@ require 'win32/registry' module Win32 class Registry + module API - + extend Chef::ReservedNames::Win32::API::Registry module_function @@ -41,7 +42,31 @@ module Win32 def DeleteKey(hkey, name) check RegDeleteKeyExW(hkey, name.to_wstring, 0, 0) end - + + end + + if RUBY_VERSION =~ /^2.1/ + # ::Win32::Registry#write does not correctly handle data in Ruby 2.1 (up to Ruby 2.1.6). + # https://bugs.ruby-lang.org/issues/11439 + def write(name, type, data) + case type + when REG_SZ, REG_EXPAND_SZ + data = data.to_s.encode(WCHAR) + WCHAR_NUL + when REG_MULTI_SZ + data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL + when REG_BINARY + data = data.to_s + when REG_DWORD + data = API.packdw(data.to_i) + when REG_DWORD_BIG_ENDIAN + data = [data.to_i].pack('N') + when REG_QWORD + data = API.packqw(data.to_i) + else + raise TypeError, "Unsupported type #{type}" + end + API.SetValue(@hkey, name, type, data, data.bytesize) + end end end -end \ No newline at end of file +end diff --git a/lib/chef/win32/unicode.rb b/lib/chef/win32/unicode.rb index 562301a040..d63b9790b9 100644 --- a/lib/chef/win32/unicode.rb +++ b/lib/chef/win32/unicode.rb @@ -58,30 +58,3 @@ class String utf8_to_wide(self) end end - -# https://bugs.ruby-lang.org/issues/11439 -if RUBY_VERSION =~ /^2\.1/ - module Win32 - class Registry - def write(name, type, data) - case type - when REG_SZ, REG_EXPAND_SZ - data = data.to_s.encode(WCHAR) + WCHAR_NUL - when REG_MULTI_SZ - data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL - when REG_BINARY - data = data.to_s - when REG_DWORD - data = API.packdw(data.to_i) - when REG_DWORD_BIG_ENDIAN - data = [data.to_i].pack('N') - when REG_QWORD - data = API.packqw(data.to_i) - else - raise TypeError, "Unsupported type #{type}" - end - API.SetValue(@hkey, name, type, data, data.bytesize) - end - end - end -end \ No newline at end of file -- cgit v1.2.1 From c6312348f0639de965309009a190b8700289ce60 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Fri, 4 Sep 2015 10:33:15 -0700 Subject: Modify registry specs to adhere to spec naming conventions --- spec/functional/win32/registry_helper_spec.rb | 644 -------------------------- spec/functional/win32/registry_spec.rb | 625 +++++++++++++++++++++++++ spec/unit/registry_helper_spec.rb | 387 ---------------- spec/unit/win32/registry_spec.rb | 387 ++++++++++++++++ 4 files changed, 1012 insertions(+), 1031 deletions(-) delete mode 100644 spec/functional/win32/registry_helper_spec.rb create mode 100644 spec/functional/win32/registry_spec.rb delete mode 100644 spec/unit/registry_helper_spec.rb create mode 100644 spec/unit/win32/registry_spec.rb diff --git a/spec/functional/win32/registry_helper_spec.rb b/spec/functional/win32/registry_helper_spec.rb deleted file mode 100644 index ba0ae12495..0000000000 --- a/spec/functional/win32/registry_helper_spec.rb +++ /dev/null @@ -1,644 +0,0 @@ -# -# Author:: Prajakta Purohit () -# Author:: Lamont Granquist () -# Copyright:: Copyright (c) 2012 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/win32/registry' - -describe Chef::Resource::RegistryKey, :unix_only do - before(:all) do - events = Chef::EventDispatch::Dispatcher.new - node = Chef::Node.new - ohai = Ohai::System.new - ohai.all_plugins - node.consume_external_attrs(ohai.data,{}) - run_context = Chef::RunContext.new(node, {}, events) - @resource = Chef::Resource::RegistryKey.new("HKCU\\Software", run_context) - end - context "when load_current_resource is run on a non-windows node" do - it "throws an exception because you don't have a windows registry (derp)" do - @resource.key("HKCU\\Software\\Opscode") - @resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}]) - expect{@resource.run_action(:create)}.to raise_error(Chef::Exceptions::Win32NotWindows) - end - end -end - -describe 'Chef::Win32::Registry', :windows_only do - - before(:all) do - #Create a registry item - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root" - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch" - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Flower" - ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root', Win32::Registry::KEY_ALL_ACCESS) do |reg| - reg['RootType1', Win32::Registry::REG_SZ] = 'fibrous' - reg.write('Roots', Win32::Registry::REG_MULTI_SZ, ["strong roots", "healthy tree"]) - end - ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch', Win32::Registry::KEY_ALL_ACCESS) do |reg| - reg['Strong', Win32::Registry::REG_SZ] = 'bird nest' - end - ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Flower', Win32::Registry::KEY_ALL_ACCESS) do |reg| - reg['Petals', Win32::Registry::REG_MULTI_SZ] = ["Pink", "Delicate"] - end - - #Create the node with ohai data - events = Chef::EventDispatch::Dispatcher.new - @node = Chef::Node.new - ohai = Ohai::System.new - ohai.all_plugins - @node.consume_external_attrs(ohai.data,{}) - @run_context = Chef::RunContext.new(@node, {}, events) - - #Create a registry object that has access ot the node previously created - @registry = Chef::Win32::Registry.new(@run_context) - end - - #Delete what is left of the registry key-values previously created - after(:all) do - ::Win32::Registry::HKEY_CURRENT_USER.open("Software") do |reg| - reg.delete_key("Root", true) - end - end - - # Server Versions - # it "succeeds if server versiion is 2003R2, 2008, 2008R2, 2012" do - # end - # it "falis if the server versions are anything else" do - # end - - describe "hive_exists?" do - it "returns true if the hive exists" do - expect(@registry.hive_exists?("HKCU\\Software\\Root")).to eq(true) - end - - it "returns false if the hive does not exist" do - hive = expect(@registry.hive_exists?("LYRU\\Software\\Root")).to eq(false) - end - end - - describe "key_exists?" do - it "returns true if the key path exists" do - expect(@registry.key_exists?("HKCU\\Software\\Root\\Branch\\Flower")).to eq(true) - end - - it "returns false if the key path does not exist" do - expect(@registry.key_exists?("HKCU\\Software\\Branch\\Flower")).to eq(false) - end - - it "throws an exception if the hive does not exist" do - expect {@registry.key_exists?("JKLM\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - end - - describe "key_exists!" do - it "returns true if the key path exists" do - expect(@registry.key_exists!("HKCU\\Software\\Root\\Branch\\Flower")).to eq(true) - end - - it "throws an exception if the key path does not exist" do - expect {@registry.key_exists!("HKCU\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - - it "throws an exception if the hive does not exist" do - expect {@registry.key_exists!("JKLM\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - end - - describe "value_exists?" do - it "throws an exception if the hive does not exist" do - expect {@registry.value_exists?("JKLM\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - it "throws an exception if the key does not exist" do - expect {@registry.value_exists?("HKCU\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - it "returns true if the value exists" do - expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true) - end - it "returns true if the value exists with a case mismatch on the value name" do - expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true) - end - it "returns false if the value does not exist" do - expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})).to eq(false) - end - end - - describe "value_exists!" do - it "throws an exception if the hive does not exist" do - expect {@registry.value_exists!("JKLM\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - it "throws an exception if the key does not exist" do - expect {@registry.value_exists!("HKCU\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - it "returns true if the value exists" do - expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true) - end - it "returns true if the value exists with a case mismatch on the value name" do - expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true) - end - it "throws an exception if the value does not exist" do - expect {@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})}.to raise_error(Chef::Exceptions::Win32RegValueMissing) - end - end - - describe "data_exists?" do - it "throws an exception if the hive does not exist" do - expect {@registry.data_exists?("JKLM\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - it "throws an exception if the key does not exist" do - expect {@registry.data_exists?("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - it "returns true if all the data matches" do - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) - end - it "returns true if all the data matches with a case mismatch on the data name" do - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) - end - it "returns false if the name does not exist" do - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(false) - end - it "returns false if the types do not match" do - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Pink"})).to eq(false) - end - it "returns false if the data does not match" do - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Mauve", "Delicate"]})).to eq(false) - end - end - - describe "data_exists!" do - it "throws an exception if the hive does not exist" do - expect {@registry.data_exists!("JKLM\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - it "throws an exception if the key does not exist" do - expect {@registry.data_exists!("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - it "returns true if all the data matches" do - expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) - end - it "returns true if all the data matches with a case mismatch on the data name" do - expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) - end - it "throws an exception if the name does not exist" do - expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegDataMissing) - end - it "throws an exception if the types do not match" do - expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Pink"})}.to raise_error(Chef::Exceptions::Win32RegDataMissing) - end - it "throws an exception if the data does not match" do - expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Mauve", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegDataMissing) - end - end - - describe "get_values" do - it "returns all values for a key if it exists" do - values = @registry.get_values("HKCU\\Software\\Root") - expect(values).to be_an_instance_of Array - expect(values).to eq([{:name=>"RootType1", :type=>:string, :data=>"fibrous"}, - {:name=>"Roots", :type=>:multi_string, :data=>["strong roots", "healthy tree"]}]) - end - - it "throws an exception if the key does not exist" do - expect {@registry.get_values("HKCU\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - - it "throws an exception if the hive does not exist" do - expect {@registry.get_values("JKLM\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - end - - describe "set_value" do - it "updates a value if the key, value exist and type matches and value different" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) - end - - it "updates a value if the type does match and the values are different" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Yellow"})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Yellow"})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(false) - end - - it "creates a value if key exists and value does not" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) - end - - it "does nothing if data,type and name parameters for the value are same" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(false) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) - end - - it "throws an exception if the key does not exist" do - expect {@registry.set_value("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - - it "throws an exception if the hive does not exist" do - expect {@registry.set_value("JKLM\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - - # we are validating that the data gets .to_i called on it when type is a :dword - - it "casts an integer string given as a dword into an integer" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe32767", :type=>:dword, :data=>"32767"})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe32767", :type=>:dword, :data=>32767})).to eq(true) - end - - it "casts a nonsense string given as a dword into zero" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeZero", :type=>:dword, :data=>"whatdoesthisdo"})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeZero", :type=>:dword, :data=>0})).to eq(true) - end - - it "throws an exception when trying to cast an array to an int for a dword" do - expect {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldThrow", :type=>:dword, :data=>["one","two"]})}.to raise_error - end - - # we are validating that the data gets .to_s called on it when type is a :string - - it "stores the string representation of an array into a string if you pass it an array" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBePainful", :type=>:string, :data=>["one","two"]})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBePainful", :type=>:string, :data=>'["one", "two"]'})).to eq(true) - end - - it "stores the string representation of a number into a string if you pass it an number" do - expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe65535", :type=>:string, :data=>65535})).to eq(true) - expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe65535", :type=>:string, :data=>"65535"})).to eq(true) - end - - # we are validating that the data gets .to_a called on it when type is a :multi_string - - it "throws an exception when a multi-string is passed a number" do - expect {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldThrow", :type=>:multi_string, :data=>65535})}.to raise_error - end - - it "throws an exception when a multi-string is passed a string" do - expect {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeWat", :type=>:multi_string, :data=>"foo"})}.to raise_error - end - end - - describe "create_key" do - before(:all) do - ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root") do |reg| - begin - reg.delete_key("Trunk", true) - rescue - end - end - end - - it "throws an exception if the path has missing keys but recursive set to false" do - expect {@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) - expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(false) - end - - it "creates the key_path if the keys were missing but recursive was set to true" do - expect(@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", true)).to eq(true) - expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) - end - - it "does nothing if the key already exists" do - expect(@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)).to eq(true) - expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) - end - - it "throws an exception of the hive does not exist" do - expect {@registry.create_key("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - end - - describe "delete_value" do - before(:all) do - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker" - ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg| - reg['Peter', Win32::Registry::REG_SZ] = 'Tiny' - end - end - - it "deletes values if the value exists" do - expect(@registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(true) - expect(@registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(false) - end - - it "does nothing if value does not exist" do - expect(@registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(true) - expect(@registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(false) - end - - it "throws an exception if the key does not exist?" do - expect {@registry.delete_value("HKCU\\Software\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - - it "throws an exception if the hive does not exist" do - expect {@registry.delete_value("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - end - - describe "delete_key" do - before (:all) do - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Fruit" - ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Fruit', Win32::Registry::KEY_ALL_ACCESS) do |reg| - reg['Apple', Win32::Registry::REG_MULTI_SZ] = ["Red", "Juicy"] - end - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker" - ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg| - reg['Peter', Win32::Registry::REG_SZ] = 'Tiny' - end - end - - it "deletes a key if it has no subkeys" do - expect(@registry.delete_key("HKCU\\Software\\Root\\Branch\\Fruit", false)).to eq(true) - expect(@registry.key_exists?("HKCU\\Software\\Root\\Branch\\Fruit")).to eq(false) - end - - it "throws an exception if key to delete has subkeys and recursive is false" do - expect { @registry.delete_key("HKCU\\Software\\Root\\Trunk", false) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) - expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) - end - - it "deletes a key if it has subkeys and recursive true" do - expect(@registry.delete_key("HKCU\\Software\\Root\\Trunk", true)).to eq(true) - expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk")).to eq(false) - end - - it "does nothing if the key does not exist" do - expect(@registry.delete_key("HKCU\\Software\\Root\\Trunk", true)).to eq(true) - expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk")).to eq(false) - end - - it "throws an exception if the hive does not exist" do - expect {@registry.delete_key("JKLM\\Software\\Root\\Branch\\Flower", false)}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - end - - describe "has_subkeys?" do - before(:all) do - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk" - ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root\\Trunk") do |reg| - begin - reg.delete_key("Red", true) - rescue - end - end - end - - it "throws an exception if the hive was missing" do - expect {@registry.has_subkeys?("LMNO\\Software\\Root")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - - it "throws an exception if the key is missing" do - expect {@registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - - it "returns true if the key has subkeys" do - expect(@registry.has_subkeys?("HKCU\\Software\\Root")).to eq(true) - end - - it "returns false if the key has no subkeys" do - ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Red" - expect(@registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red")).to eq(false) - end - end - - describe "get_subkeys" do - it "throws an exception if the key is missing" do - expect {@registry.get_subkeys("HKCU\\Software\\Trunk\\Red")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - it "throws an exception if the hive does not exist" do - expect {@registry.get_subkeys("JKLM\\Software\\Root")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) - end - it "returns the array of subkeys for a given key" do - subkeys = @registry.get_subkeys("HKCU\\Software\\Root") - reg_subkeys = [] - ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root", Win32::Registry::KEY_ALL_ACCESS) do |reg| - reg.each_key{|name| reg_subkeys << name} - end - expect(reg_subkeys).to eq(subkeys) - end - end - - describe "architecture" do - describe "on 32-bit" do - before(:all) do - @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine] - @node.automatic_attrs[:kernel][:machine] = :i386 - end - - after(:all) do - @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine - end - - context "registry constructor" do - it "throws an exception if requested architecture is 64bit but running on 32bit" do - expect {Chef::Win32::Registry.new(@run_context, :x86_64)}.to raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect) - end - - it "can correctly set the requested architecture to 32-bit" do - @r = Chef::Win32::Registry.new(@run_context, :i386) - expect(@r.architecture).to eq(:i386) - expect(@r.registry_system_architecture).to eq(0x0200) - end - - it "can correctly set the requested architecture to :machine" do - @r = Chef::Win32::Registry.new(@run_context, :machine) - expect(@r.architecture).to eq(:machine) - expect(@r.registry_system_architecture).to eq(0x0200) - end - end - - context "architecture setter" do - it "throws an exception if requested architecture is 64bit but running on 32bit" do - expect {@registry.architecture = :x86_64}.to raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect) - end - - it "sets the requested architecture to :machine if passed :machine" do - @registry.architecture = :machine - expect(@registry.architecture).to eq(:machine) - expect(@registry.registry_system_architecture).to eq(0x0200) - end - - it "sets the requested architecture to 32-bit if passed i386 as a string" do - @registry.architecture = :i386 - expect(@registry.architecture).to eq(:i386) - expect(@registry.registry_system_architecture).to eq(0x0200) - end - end - end - - describe "on 64-bit" do - before(:all) do - @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine] - @node.automatic_attrs[:kernel][:machine] = :x86_64 - end - - after(:all) do - @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine - end - - context "registry constructor" do - it "can correctly set the requested architecture to 32-bit" do - @r = Chef::Win32::Registry.new(@run_context, :i386) - expect(@r.architecture).to eq(:i386) - expect(@r.registry_system_architecture).to eq(0x0200) - end - - it "can correctly set the requested architecture to 64-bit" do - @r = Chef::Win32::Registry.new(@run_context, :x86_64) - expect(@r.architecture).to eq(:x86_64) - expect(@r.registry_system_architecture).to eq(0x0100) - end - - it "can correctly set the requested architecture to :machine" do - @r = Chef::Win32::Registry.new(@run_context, :machine) - expect(@r.architecture).to eq(:machine) - expect(@r.registry_system_architecture).to eq(0x0100) - end - end - - context "architecture setter" do - it "sets the requested architecture to 64-bit if passed 64-bit" do - @registry.architecture = :x86_64 - expect(@registry.architecture).to eq(:x86_64) - expect(@registry.registry_system_architecture).to eq(0x0100) - end - - it "sets the requested architecture to :machine if passed :machine" do - @registry.architecture = :machine - expect(@registry.architecture).to eq(:machine) - expect(@registry.registry_system_architecture).to eq(0x0100) - end - - it "sets the requested architecture to 32-bit if passed 32-bit" do - @registry.architecture = :i386 - expect(@registry.architecture).to eq(:i386) - expect(@registry.registry_system_architecture).to eq(0x0200) - end - end - end - - describe "when running on an actual 64-bit server", :windows64_only do - before(:all) do - begin - ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| - reg.delete_key("Trunk", true) - end - rescue - end - begin - ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| - reg.delete_key("Trunk", true) - end - rescue - end - # 64-bit - ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Mauve", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) - ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Mauve', Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| - reg['Alert', Win32::Registry::REG_SZ] = 'Universal' - end - # 32-bit - ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Poosh", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) - ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Poosh', Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| - reg['Status', Win32::Registry::REG_SZ] = 'Lost' - end - end - - after(:all) do - ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| - reg.delete_key("Root", true) - end - ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| - reg.delete_key("Root", true) - end - end - - describe "key_exists?" do - it "does not find 64-bit keys in the 32-bit registry" do - @registry.architecture=:i386 - expect(@registry.key_exists?("HKLM\\Software\\Root\\Mauve")).to eq(false) - end - it "finds 32-bit keys in the 32-bit registry" do - @registry.architecture=:i386 - expect(@registry.key_exists?("HKLM\\Software\\Root\\Poosh")).to eq(true) - end - it "does not find 32-bit keys in the 64-bit registry" do - @registry.architecture=:x86_64 - expect(@registry.key_exists?("HKLM\\Software\\Root\\Mauve")).to eq(true) - end - it "finds 64-bit keys in the 64-bit registry" do - @registry.architecture=:x86_64 - expect(@registry.key_exists?("HKLM\\Software\\Root\\Poosh")).to eq(false) - end - end - - describe "value_exists?" do - it "does not find 64-bit values in the 32-bit registry" do - @registry.architecture=:i386 - expect{@registry.value_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - it "finds 32-bit values in the 32-bit registry" do - @registry.architecture=:i386 - expect(@registry.value_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status"})).to eq(true) - end - it "does not find 32-bit values in the 64-bit registry" do - @registry.architecture=:x86_64 - expect(@registry.value_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert"})).to eq(true) - end - it "finds 64-bit values in the 64-bit registry" do - @registry.architecture=:x86_64 - expect{@registry.value_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - end - - describe "data_exists?" do - it "does not find 64-bit keys in the 32-bit registry" do - @registry.architecture=:i386 - expect{@registry.data_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert", :type=>:string, :data=>"Universal"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - it "finds 32-bit keys in the 32-bit registry" do - @registry.architecture=:i386 - expect(@registry.data_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status", :type=>:string, :data=>"Lost"})).to eq(true) - end - it "does not find 32-bit keys in the 64-bit registry" do - @registry.architecture=:x86_64 - expect(@registry.data_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert", :type=>:string, :data=>"Universal"})).to eq(true) - end - it "finds 64-bit keys in the 64-bit registry" do - @registry.architecture=:x86_64 - expect{@registry.data_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status", :type=>:string, :data=>"Lost"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - end - - describe "create_key" do - it "can create a 32-bit only registry key" do - @registry.architecture = :i386 - expect(@registry.create_key("HKLM\\Software\\Root\\Trunk\\Red", true)).to eq(true) - expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red")).to eq(true) - @registry.architecture = :x86_64 - expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red")).to eq(false) - end - - it "can create a 64-bit only registry key" do - @registry.architecture = :x86_64 - expect(@registry.create_key("HKLM\\Software\\Root\\Trunk\\Blue", true)).to eq(true) - expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue")).to eq(true) - @registry.architecture = :i386 - expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue")).to eq(false) - end - end - - end - end -end diff --git a/spec/functional/win32/registry_spec.rb b/spec/functional/win32/registry_spec.rb new file mode 100644 index 0000000000..dcfc49e2b3 --- /dev/null +++ b/spec/functional/win32/registry_spec.rb @@ -0,0 +1,625 @@ +# +# Author:: Prajakta Purohit () +# Author:: Lamont Granquist () +# Copyright:: Copyright (c) 2012 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/win32/registry' + +describe 'Chef::Win32::Registry', :windows_only do + + before(:all) do + #Create a registry item + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root" + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch" + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Flower" + ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root', Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg['RootType1', Win32::Registry::REG_SZ] = 'fibrous' + reg.write('Roots', Win32::Registry::REG_MULTI_SZ, ["strong roots", "healthy tree"]) + end + ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch', Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg['Strong', Win32::Registry::REG_SZ] = 'bird nest' + end + ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Flower', Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg['Petals', Win32::Registry::REG_MULTI_SZ] = ["Pink", "Delicate"] + end + + #Create the node with ohai data + events = Chef::EventDispatch::Dispatcher.new + @node = Chef::Node.new + ohai = Ohai::System.new + ohai.all_plugins + @node.consume_external_attrs(ohai.data,{}) + @run_context = Chef::RunContext.new(@node, {}, events) + + #Create a registry object that has access ot the node previously created + @registry = Chef::Win32::Registry.new(@run_context) + end + + #Delete what is left of the registry key-values previously created + after(:all) do + ::Win32::Registry::HKEY_CURRENT_USER.open("Software") do |reg| + reg.delete_key("Root", true) + end + end + + # Server Versions + # it "succeeds if server versiion is 2003R2, 2008, 2008R2, 2012" do + # end + # it "falis if the server versions are anything else" do + # end + + describe "hive_exists?" do + it "returns true if the hive exists" do + expect(@registry.hive_exists?("HKCU\\Software\\Root")).to eq(true) + end + + it "returns false if the hive does not exist" do + hive = expect(@registry.hive_exists?("LYRU\\Software\\Root")).to eq(false) + end + end + + describe "key_exists?" do + it "returns true if the key path exists" do + expect(@registry.key_exists?("HKCU\\Software\\Root\\Branch\\Flower")).to eq(true) + end + + it "returns false if the key path does not exist" do + expect(@registry.key_exists?("HKCU\\Software\\Branch\\Flower")).to eq(false) + end + + it "throws an exception if the hive does not exist" do + expect {@registry.key_exists?("JKLM\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + end + + describe "key_exists!" do + it "returns true if the key path exists" do + expect(@registry.key_exists!("HKCU\\Software\\Root\\Branch\\Flower")).to eq(true) + end + + it "throws an exception if the key path does not exist" do + expect {@registry.key_exists!("HKCU\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + + it "throws an exception if the hive does not exist" do + expect {@registry.key_exists!("JKLM\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + end + + describe "value_exists?" do + it "throws an exception if the hive does not exist" do + expect {@registry.value_exists?("JKLM\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + it "throws an exception if the key does not exist" do + expect {@registry.value_exists?("HKCU\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + it "returns true if the value exists" do + expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true) + end + it "returns true if the value exists with a case mismatch on the value name" do + expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true) + end + it "returns false if the value does not exist" do + expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})).to eq(false) + end + end + + describe "value_exists!" do + it "throws an exception if the hive does not exist" do + expect {@registry.value_exists!("JKLM\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + it "throws an exception if the key does not exist" do + expect {@registry.value_exists!("HKCU\\Software\\Branch\\Flower", {:name=>"Petals"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + it "returns true if the value exists" do + expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true) + end + it "returns true if the value exists with a case mismatch on the value name" do + expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true) + end + it "throws an exception if the value does not exist" do + expect {@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})}.to raise_error(Chef::Exceptions::Win32RegValueMissing) + end + end + + describe "data_exists?" do + it "throws an exception if the hive does not exist" do + expect {@registry.data_exists?("JKLM\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + it "throws an exception if the key does not exist" do + expect {@registry.data_exists?("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + it "returns true if all the data matches" do + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) + end + it "returns true if all the data matches with a case mismatch on the data name" do + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) + end + it "returns false if the name does not exist" do + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(false) + end + it "returns false if the types do not match" do + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Pink"})).to eq(false) + end + it "returns false if the data does not match" do + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Mauve", "Delicate"]})).to eq(false) + end + end + + describe "data_exists!" do + it "throws an exception if the hive does not exist" do + expect {@registry.data_exists!("JKLM\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + it "throws an exception if the key does not exist" do + expect {@registry.data_exists!("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + it "returns true if all the data matches" do + expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) + end + it "returns true if all the data matches with a case mismatch on the data name" do + expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true) + end + it "throws an exception if the name does not exist" do + expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegDataMissing) + end + it "throws an exception if the types do not match" do + expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Pink"})}.to raise_error(Chef::Exceptions::Win32RegDataMissing) + end + it "throws an exception if the data does not match" do + expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Mauve", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegDataMissing) + end + end + + describe "get_values" do + it "returns all values for a key if it exists" do + values = @registry.get_values("HKCU\\Software\\Root") + expect(values).to be_an_instance_of Array + expect(values).to eq([{:name=>"RootType1", :type=>:string, :data=>"fibrous"}, + {:name=>"Roots", :type=>:multi_string, :data=>["strong roots", "healthy tree"]}]) + end + + it "throws an exception if the key does not exist" do + expect {@registry.get_values("HKCU\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + + it "throws an exception if the hive does not exist" do + expect {@registry.get_values("JKLM\\Software\\Branch\\Flower")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + end + + describe "set_value" do + it "updates a value if the key, value exist and type matches and value different" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) + end + + it "updates a value if the type does match and the values are different" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Yellow"})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Yellow"})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(false) + end + + it "creates a value if key exists and value does not" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) + end + + it "does nothing if data,type and name parameters for the value are same" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(false) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})).to eq(true) + end + + it "throws an exception if the key does not exist" do + expect {@registry.set_value("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + + it "throws an exception if the hive does not exist" do + expect {@registry.set_value("JKLM\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + + # we are validating that the data gets .to_i called on it when type is a :dword + + it "casts an integer string given as a dword into an integer" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe32767", :type=>:dword, :data=>"32767"})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe32767", :type=>:dword, :data=>32767})).to eq(true) + end + + it "casts a nonsense string given as a dword into zero" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeZero", :type=>:dword, :data=>"whatdoesthisdo"})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeZero", :type=>:dword, :data=>0})).to eq(true) + end + + it "throws an exception when trying to cast an array to an int for a dword" do + expect {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldThrow", :type=>:dword, :data=>["one","two"]})}.to raise_error + end + + # we are validating that the data gets .to_s called on it when type is a :string + + it "stores the string representation of an array into a string if you pass it an array" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBePainful", :type=>:string, :data=>["one","two"]})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBePainful", :type=>:string, :data=>'["one", "two"]'})).to eq(true) + end + + it "stores the string representation of a number into a string if you pass it an number" do + expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe65535", :type=>:string, :data=>65535})).to eq(true) + expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe65535", :type=>:string, :data=>"65535"})).to eq(true) + end + + # we are validating that the data gets .to_a called on it when type is a :multi_string + + it "throws an exception when a multi-string is passed a number" do + expect {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldThrow", :type=>:multi_string, :data=>65535})}.to raise_error + end + + it "throws an exception when a multi-string is passed a string" do + expect {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeWat", :type=>:multi_string, :data=>"foo"})}.to raise_error + end + end + + describe "create_key" do + before(:all) do + ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root") do |reg| + begin + reg.delete_key("Trunk", true) + rescue + end + end + end + + it "throws an exception if the path has missing keys but recursive set to false" do + expect {@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) + expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(false) + end + + it "creates the key_path if the keys were missing but recursive was set to true" do + expect(@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", true)).to eq(true) + expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) + end + + it "does nothing if the key already exists" do + expect(@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)).to eq(true) + expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) + end + + it "throws an exception of the hive does not exist" do + expect {@registry.create_key("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + end + + describe "delete_value" do + before(:all) do + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker" + ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg['Peter', Win32::Registry::REG_SZ] = 'Tiny' + end + end + + it "deletes values if the value exists" do + expect(@registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(true) + expect(@registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(false) + end + + it "does nothing if value does not exist" do + expect(@registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(true) + expect(@registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})).to eq(false) + end + + it "throws an exception if the key does not exist?" do + expect {@registry.delete_value("HKCU\\Software\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + + it "throws an exception if the hive does not exist" do + expect {@registry.delete_value("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + end + + describe "delete_key" do + before (:all) do + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Fruit" + ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Fruit', Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg['Apple', Win32::Registry::REG_MULTI_SZ] = ["Red", "Juicy"] + end + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker" + ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg['Peter', Win32::Registry::REG_SZ] = 'Tiny' + end + end + + it "deletes a key if it has no subkeys" do + expect(@registry.delete_key("HKCU\\Software\\Root\\Branch\\Fruit", false)).to eq(true) + expect(@registry.key_exists?("HKCU\\Software\\Root\\Branch\\Fruit")).to eq(false) + end + + it "throws an exception if key to delete has subkeys and recursive is false" do + expect { @registry.delete_key("HKCU\\Software\\Root\\Trunk", false) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) + expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) + end + + it "deletes a key if it has subkeys and recursive true" do + expect(@registry.delete_key("HKCU\\Software\\Root\\Trunk", true)).to eq(true) + expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk")).to eq(false) + end + + it "does nothing if the key does not exist" do + expect(@registry.delete_key("HKCU\\Software\\Root\\Trunk", true)).to eq(true) + expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk")).to eq(false) + end + + it "throws an exception if the hive does not exist" do + expect {@registry.delete_key("JKLM\\Software\\Root\\Branch\\Flower", false)}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + end + + describe "has_subkeys?" do + before(:all) do + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk" + ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root\\Trunk") do |reg| + begin + reg.delete_key("Red", true) + rescue + end + end + end + + it "throws an exception if the hive was missing" do + expect {@registry.has_subkeys?("LMNO\\Software\\Root")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + + it "throws an exception if the key is missing" do + expect {@registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + + it "returns true if the key has subkeys" do + expect(@registry.has_subkeys?("HKCU\\Software\\Root")).to eq(true) + end + + it "returns false if the key has no subkeys" do + ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Red" + expect(@registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red")).to eq(false) + end + end + + describe "get_subkeys" do + it "throws an exception if the key is missing" do + expect {@registry.get_subkeys("HKCU\\Software\\Trunk\\Red")}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + it "throws an exception if the hive does not exist" do + expect {@registry.get_subkeys("JKLM\\Software\\Root")}.to raise_error(Chef::Exceptions::Win32RegHiveMissing) + end + it "returns the array of subkeys for a given key" do + subkeys = @registry.get_subkeys("HKCU\\Software\\Root") + reg_subkeys = [] + ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root", Win32::Registry::KEY_ALL_ACCESS) do |reg| + reg.each_key{|name| reg_subkeys << name} + end + expect(reg_subkeys).to eq(subkeys) + end + end + + describe "architecture" do + describe "on 32-bit" do + before(:all) do + @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine] + @node.automatic_attrs[:kernel][:machine] = :i386 + end + + after(:all) do + @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine + end + + context "registry constructor" do + it "throws an exception if requested architecture is 64bit but running on 32bit" do + expect {Chef::Win32::Registry.new(@run_context, :x86_64)}.to raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect) + end + + it "can correctly set the requested architecture to 32-bit" do + @r = Chef::Win32::Registry.new(@run_context, :i386) + expect(@r.architecture).to eq(:i386) + expect(@r.registry_system_architecture).to eq(0x0200) + end + + it "can correctly set the requested architecture to :machine" do + @r = Chef::Win32::Registry.new(@run_context, :machine) + expect(@r.architecture).to eq(:machine) + expect(@r.registry_system_architecture).to eq(0x0200) + end + end + + context "architecture setter" do + it "throws an exception if requested architecture is 64bit but running on 32bit" do + expect {@registry.architecture = :x86_64}.to raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect) + end + + it "sets the requested architecture to :machine if passed :machine" do + @registry.architecture = :machine + expect(@registry.architecture).to eq(:machine) + expect(@registry.registry_system_architecture).to eq(0x0200) + end + + it "sets the requested architecture to 32-bit if passed i386 as a string" do + @registry.architecture = :i386 + expect(@registry.architecture).to eq(:i386) + expect(@registry.registry_system_architecture).to eq(0x0200) + end + end + end + + describe "on 64-bit" do + before(:all) do + @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine] + @node.automatic_attrs[:kernel][:machine] = :x86_64 + end + + after(:all) do + @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine + end + + context "registry constructor" do + it "can correctly set the requested architecture to 32-bit" do + @r = Chef::Win32::Registry.new(@run_context, :i386) + expect(@r.architecture).to eq(:i386) + expect(@r.registry_system_architecture).to eq(0x0200) + end + + it "can correctly set the requested architecture to 64-bit" do + @r = Chef::Win32::Registry.new(@run_context, :x86_64) + expect(@r.architecture).to eq(:x86_64) + expect(@r.registry_system_architecture).to eq(0x0100) + end + + it "can correctly set the requested architecture to :machine" do + @r = Chef::Win32::Registry.new(@run_context, :machine) + expect(@r.architecture).to eq(:machine) + expect(@r.registry_system_architecture).to eq(0x0100) + end + end + + context "architecture setter" do + it "sets the requested architecture to 64-bit if passed 64-bit" do + @registry.architecture = :x86_64 + expect(@registry.architecture).to eq(:x86_64) + expect(@registry.registry_system_architecture).to eq(0x0100) + end + + it "sets the requested architecture to :machine if passed :machine" do + @registry.architecture = :machine + expect(@registry.architecture).to eq(:machine) + expect(@registry.registry_system_architecture).to eq(0x0100) + end + + it "sets the requested architecture to 32-bit if passed 32-bit" do + @registry.architecture = :i386 + expect(@registry.architecture).to eq(:i386) + expect(@registry.registry_system_architecture).to eq(0x0200) + end + end + end + + describe "when running on an actual 64-bit server", :windows64_only do + before(:all) do + begin + ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| + reg.delete_key("Trunk", true) + end + rescue + end + begin + ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| + reg.delete_key("Trunk", true) + end + rescue + end + # 64-bit + ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Mauve", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) + ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Mauve', Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| + reg['Alert', Win32::Registry::REG_SZ] = 'Universal' + end + # 32-bit + ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Poosh", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) + ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Poosh', Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| + reg['Status', Win32::Registry::REG_SZ] = 'Lost' + end + end + + after(:all) do + ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| + reg.delete_key("Root", true) + end + ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| + reg.delete_key("Root", true) + end + end + + describe "key_exists?" do + it "does not find 64-bit keys in the 32-bit registry" do + @registry.architecture=:i386 + expect(@registry.key_exists?("HKLM\\Software\\Root\\Mauve")).to eq(false) + end + it "finds 32-bit keys in the 32-bit registry" do + @registry.architecture=:i386 + expect(@registry.key_exists?("HKLM\\Software\\Root\\Poosh")).to eq(true) + end + it "does not find 32-bit keys in the 64-bit registry" do + @registry.architecture=:x86_64 + expect(@registry.key_exists?("HKLM\\Software\\Root\\Mauve")).to eq(true) + end + it "finds 64-bit keys in the 64-bit registry" do + @registry.architecture=:x86_64 + expect(@registry.key_exists?("HKLM\\Software\\Root\\Poosh")).to eq(false) + end + end + + describe "value_exists?" do + it "does not find 64-bit values in the 32-bit registry" do + @registry.architecture=:i386 + expect{@registry.value_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + it "finds 32-bit values in the 32-bit registry" do + @registry.architecture=:i386 + expect(@registry.value_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status"})).to eq(true) + end + it "does not find 32-bit values in the 64-bit registry" do + @registry.architecture=:x86_64 + expect(@registry.value_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert"})).to eq(true) + end + it "finds 64-bit values in the 64-bit registry" do + @registry.architecture=:x86_64 + expect{@registry.value_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + end + + describe "data_exists?" do + it "does not find 64-bit keys in the 32-bit registry" do + @registry.architecture=:i386 + expect{@registry.data_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert", :type=>:string, :data=>"Universal"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + it "finds 32-bit keys in the 32-bit registry" do + @registry.architecture=:i386 + expect(@registry.data_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status", :type=>:string, :data=>"Lost"})).to eq(true) + end + it "does not find 32-bit keys in the 64-bit registry" do + @registry.architecture=:x86_64 + expect(@registry.data_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert", :type=>:string, :data=>"Universal"})).to eq(true) + end + it "finds 64-bit keys in the 64-bit registry" do + @registry.architecture=:x86_64 + expect{@registry.data_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status", :type=>:string, :data=>"Lost"})}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + end + + describe "create_key" do + it "can create a 32-bit only registry key" do + @registry.architecture = :i386 + expect(@registry.create_key("HKLM\\Software\\Root\\Trunk\\Red", true)).to eq(true) + expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red")).to eq(true) + @registry.architecture = :x86_64 + expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red")).to eq(false) + end + + it "can create a 64-bit only registry key" do + @registry.architecture = :x86_64 + expect(@registry.create_key("HKLM\\Software\\Root\\Trunk\\Blue", true)).to eq(true) + expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue")).to eq(true) + @registry.architecture = :i386 + expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue")).to eq(false) + end + end + + end + end +end diff --git a/spec/unit/registry_helper_spec.rb b/spec/unit/registry_helper_spec.rb deleted file mode 100644 index a30608add7..0000000000 --- a/spec/unit/registry_helper_spec.rb +++ /dev/null @@ -1,387 +0,0 @@ -# -# Author:: Prajakta Purohit (prajakta@opscode.com) -# Copyright:: Copyright (c) 2012 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::Provider::RegistryKey do - - let(:value1) { { :name => "one", :type => :string, :data => "1" } } - let(:value1_upcase_name) { {:name => "ONE", :type => :string, :data => "1"} } - let(:key_path) { 'HKCU\Software\OpscodeNumbers' } - let(:key) { 'Software\OpscodeNumbers' } - let(:key_parent) { 'Software' } - let(:key_to_delete) { 'OpscodeNumbers' } - let(:sub_key) {'OpscodePrimes'} - let(:missing_key_path) {'HKCU\Software'} - - before(:each) do - allow_any_instance_of(Chef::Win32::Registry).to receive(:machine_architecture).and_return(:x86_64) - @registry = Chef::Win32::Registry.new() - - #Making the values for registry constants available on unix - Object.send(:remove_const, 'Win32') if defined?(Win32) - Win32 = Module.new - Win32::Registry = Class.new - Win32::Registry::KEY_SET_VALUE = 0x0002 - Win32::Registry::KEY_QUERY_VALUE = 0x0001 - Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004 - Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010 - - Win32::Registry::Error = Class.new(RuntimeError) - - @hive_mock = double("::Win32::Registry::HKEY_CURRENT_USER") - @reg_mock = double("reg") - end - - describe "get_values" do - it "gets all values for a key if the key exists" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:map) - @registry.get_values(key_path) - end - - it "throws an exception if key does not exist" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect{@registry.get_values(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - end - - describe "set_value" do - it "does nothing if key and hive and value exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true) - @registry.set_value(key_path, value1) - end - it "does nothing if case insensitive key and hive and value exist" do - expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true) - @registry.set_value(key_path.downcase, value1) - end - it "does nothing if key and hive and value with a case insensitive name exist" do - expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) - @registry.set_value(key_path.downcase, value1_upcase_name) - end - it "updates value if key and hive and value exist, but data is different" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false) - expect(@hive_mock).to receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:write).with("one", 1, "1") - @registry.set_value(key_path, value1) - end - - it "creates value if the key exists and the value does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:write).with("one", 1, "1") - @registry.set_value(key_path, value1) - end - - it "should raise an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - end - - describe "delete_value" do - it "deletes value if value exists" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:delete_value).with("one").and_return(true) - @registry.delete_value(key_path, value1) - end - - it "raises an exception if the key does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - @registry.delete_value(key_path, value1) - end - - it "does nothing if the value does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - @registry.delete_value(key_path, value1) - end - end - - describe "create_key" do - it "creates key if intermediate keys are missing and recursive is set to true" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) - expect(@registry).to receive(:create_missing).with(key_path) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) - @registry.create_key(key_path, true) - end - - it "raises an exception if intermediate keys are missing and recursive is set to false" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) - expect{@registry.create_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) - end - - it "does nothing if the key exists" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) - expect(@registry).to receive(:create_missing).with(key_path) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - @registry.create_key(key_path, true) - end - - it "create key if intermediate keys not missing and recursive is set to false" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) - @registry.create_key(key_path, false) - end - - it "create key if intermediate keys not missing and recursive is set to true" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) - @registry.create_key(key_path, true) - end - end - - describe "delete_key", :windows_only do - it "deletes key if it has subkeys and recursive is set to true" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) - @registry.delete_key(key_path, true) - end - - it "raises an exception if it has subkeys but recursive is set to false" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) - expect{@registry.delete_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) - end - - it "deletes key if the key exists and has no subkeys" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) - @registry.delete_key(key_path, true) - end - end - - describe "key_exists?" do - it "returns true if key_exists" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry.key_exists?(key_path)).to eq(true) - end - - it "returns false if key does not exist" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_raise(::Win32::Registry::Error) - expect(@registry.key_exists?(key_path)).to eq(false) - end - end - - describe "key_exists!" do - it "throws an exception if the key_parent does not exist" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect{@registry.key_exists!(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - end - - describe "hive_exists?" do - it "returns true if the hive exists" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - @registry.hive_exists?(key_path) == true - end - - it "returns false if the hive does not exist" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing) - @registry.hive_exists?(key_path) == false - end - end - - describe "has_subkeys?" do - it "returns true if the key has subkeys" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each_key).and_yield(key) - @registry.has_subkeys?(key_path) == true - end - - it "returns false if the key does not have subkeys" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each_key).and_return(no_args()) - expect(@registry.has_subkeys?(key_path)).to eq(false) - end - - it "throws an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - end - - describe "get_subkeys" do - it "returns the subkeys if they exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each_key).and_yield(sub_key) - @registry.get_subkeys(key_path) - end - end - - describe "value_exists?" do - it "throws an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.value_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - - it "returns true if the value exists" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:any?).and_yield("one") - @registry.value_exists?(key_path, value1) == true - end - - it "returns false if the value does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:any?).and_yield(no_args()) - @registry.value_exists?(key_path, value1) == false - end - end - - describe "data_exists?" do - it "throws an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.data_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) - end - - it "returns true if the data exists" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "1") - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry.data_exists?(key_path, value1)).to eq(true) - end - - it "returns false if the data does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "2") - expect(@registry.data_exists?(key_path, value1)).to eq(false) - end - end - - describe "value_exists!" do - it "does nothing if the value exists" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - @registry.value_exists!(key_path, value1) - end - - it "throws an exception if the value does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - expect{@registry.value_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) - end - end - - describe "data_exists!" do - it "does nothing if the data exists" do - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true) - @registry.data_exists!(key_path, value1) - end - - it "throws an exception if the data does not exist" do - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false) - expect{@registry.data_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegDataMissing) - end - end - - describe "type_matches?" do - it "returns true if type matches" do - expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:each).and_yield("one", 1) - expect(@registry.type_matches?(key_path, value1)).to eq(true) - end - - it "returns false if type does not match" do - expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each).and_yield("two", 2) - expect(@registry.type_matches?(key_path, value1)).to eq(false) - end - - it "throws an exception if value does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - expect{@registry.type_matches?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) - end - end - - describe "type_matches!" do - it "does nothing if the type_matches" do - expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(true) - @registry.type_matches!(key_path, value1) - end - - it "throws an exception if the type does not match" do - expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(false) - expect{@registry.type_matches!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegTypesMismatch) - end - end - - describe "keys_missing?" do - it "returns true if the keys are missing" do - expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(false) - expect(@registry.keys_missing?(key_path)).to eq(true) - end - - it "returns false if no keys in the path are missing" do - expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(true) - expect(@registry.keys_missing?(key_path)).to eq(false) - end - end -end diff --git a/spec/unit/win32/registry_spec.rb b/spec/unit/win32/registry_spec.rb new file mode 100644 index 0000000000..fdd3e85a8c --- /dev/null +++ b/spec/unit/win32/registry_spec.rb @@ -0,0 +1,387 @@ +# +# Author:: Prajakta Purohit (prajakta@opscode.com) +# Copyright:: Copyright (c) 2012 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::Win32::Registry do + + let(:value1) { { :name => "one", :type => :string, :data => "1" } } + let(:value1_upcase_name) { {:name => "ONE", :type => :string, :data => "1"} } + let(:key_path) { 'HKCU\Software\OpscodeNumbers' } + let(:key) { 'Software\OpscodeNumbers' } + let(:key_parent) { 'Software' } + let(:key_to_delete) { 'OpscodeNumbers' } + let(:sub_key) {'OpscodePrimes'} + let(:missing_key_path) {'HKCU\Software'} + + before(:each) do + allow_any_instance_of(Chef::Win32::Registry).to receive(:machine_architecture).and_return(:x86_64) + @registry = Chef::Win32::Registry.new() + + #Making the values for registry constants available on unix + Object.send(:remove_const, 'Win32') if defined?(Win32) + Win32 = Module.new + Win32::Registry = Class.new + Win32::Registry::KEY_SET_VALUE = 0x0002 + Win32::Registry::KEY_QUERY_VALUE = 0x0001 + Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004 + Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010 + + Win32::Registry::Error = Class.new(RuntimeError) + + @hive_mock = double("::Win32::Registry::HKEY_CURRENT_USER") + @reg_mock = double("reg") + end + + describe "get_values" do + it "gets all values for a key if the key exists" do + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:map) + @registry.get_values(key_path) + end + + it "throws an exception if key does not exist" do + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect{@registry.get_values(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + end + + describe "set_value" do + it "does nothing if key and hive and value exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true) + @registry.set_value(key_path, value1) + end + it "does nothing if case insensitive key and hive and value exist" do + expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) + expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true) + expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true) + @registry.set_value(key_path.downcase, value1) + end + it "does nothing if key and hive and value with a case insensitive name exist" do + expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) + expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) + expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) + @registry.set_value(key_path.downcase, value1_upcase_name) + end + it "updates value if key and hive and value exist, but data is different" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false) + expect(@hive_mock).to receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(@reg_mock).to receive(:write).with("one", 1, "1") + @registry.set_value(key_path, value1) + end + + it "creates value if the key exists and the value does not exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(@reg_mock).to receive(:write).with("one", 1, "1") + @registry.set_value(key_path, value1) + end + + it "should raise an exception if the key does not exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + end + + describe "delete_value" do + it "deletes value if value exists" do + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:delete_value).with("one").and_return(true) + @registry.delete_value(key_path, value1) + end + + it "raises an exception if the key does not exist" do + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + @registry.delete_value(key_path, value1) + end + + it "does nothing if the value does not exist" do + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + @registry.delete_value(key_path, value1) + end + end + + describe "create_key" do + it "creates key if intermediate keys are missing and recursive is set to true" do + expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) + expect(@registry).to receive(:create_missing).with(key_path) + expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) + @registry.create_key(key_path, true) + end + + it "raises an exception if intermediate keys are missing and recursive is set to false" do + expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) + expect{@registry.create_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) + end + + it "does nothing if the key exists" do + expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) + expect(@registry).to receive(:create_missing).with(key_path) + expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) + @registry.create_key(key_path, true) + end + + it "create key if intermediate keys not missing and recursive is set to false" do + expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false) + expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) + @registry.create_key(key_path, false) + end + + it "create key if intermediate keys not missing and recursive is set to true" do + expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false) + expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) + @registry.create_key(key_path, true) + end + end + + describe "delete_key", :windows_only do + it "deletes key if it has subkeys and recursive is set to true" do + expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) + expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) + @registry.delete_key(key_path, true) + end + + it "raises an exception if it has subkeys but recursive is set to false" do + expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) + expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) + expect{@registry.delete_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) + end + + it "deletes key if the key exists and has no subkeys" do + expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) + expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(false) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) + @registry.delete_key(key_path, true) + end + end + + describe "key_exists?" do + it "returns true if key_exists" do + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@registry.key_exists?(key_path)).to eq(true) + end + + it "returns false if key does not exist" do + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_raise(::Win32::Registry::Error) + expect(@registry.key_exists?(key_path)).to eq(false) + end + end + + describe "key_exists!" do + it "throws an exception if the key_parent does not exist" do + expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) + expect{@registry.key_exists!(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + end + + describe "hive_exists?" do + it "returns true if the hive exists" do + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + @registry.hive_exists?(key_path) == true + end + + it "returns false if the hive does not exist" do + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing) + @registry.hive_exists?(key_path) == false + end + end + + describe "has_subkeys?" do + it "returns true if the key has subkeys" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:each_key).and_yield(key) + @registry.has_subkeys?(key_path) == true + end + + it "returns false if the key does not have subkeys" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:each_key).and_return(no_args()) + expect(@registry.has_subkeys?(key_path)).to eq(false) + end + + it "throws an exception if the key does not exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + end + + describe "get_subkeys" do + it "returns the subkeys if they exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:each_key).and_yield(sub_key) + @registry.get_subkeys(key_path) + end + end + + describe "value_exists?" do + it "throws an exception if the key does not exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {@registry.value_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + + it "returns true if the value exists" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:any?).and_yield("one") + @registry.value_exists?(key_path, value1) == true + end + + it "returns false if the value does not exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:any?).and_yield(no_args()) + @registry.value_exists?(key_path, value1) == false + end + end + + describe "data_exists?" do + it "throws an exception if the key does not exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {@registry.data_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + end + + it "returns true if the data exists" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "1") + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@registry.data_exists?(key_path, value1)).to eq(true) + end + + it "returns false if the data does not exist" do + expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "2") + expect(@registry.data_exists?(key_path, value1)).to eq(false) + end + end + + describe "value_exists!" do + it "does nothing if the value exists" do + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + @registry.value_exists!(key_path, value1) + end + + it "throws an exception if the value does not exist" do + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + expect{@registry.value_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) + end + end + + describe "data_exists!" do + it "does nothing if the data exists" do + expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true) + @registry.data_exists!(key_path, value1) + end + + it "throws an exception if the data does not exist" do + expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false) + expect{@registry.data_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegDataMissing) + end + end + + describe "type_matches?" do + it "returns true if type matches" do + expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(@reg_mock).to receive(:each).and_yield("one", 1) + expect(@registry.type_matches?(key_path, value1)).to eq(true) + end + + it "returns false if type does not match" do + expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true) + expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) + expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) + expect(@reg_mock).to receive(:each).and_yield("two", 2) + expect(@registry.type_matches?(key_path, value1)).to eq(false) + end + + it "throws an exception if value does not exist" do + expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + expect{@registry.type_matches?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) + end + end + + describe "type_matches!" do + it "does nothing if the type_matches" do + expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(true) + @registry.type_matches!(key_path, value1) + end + + it "throws an exception if the type does not match" do + expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(false) + expect{@registry.type_matches!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegTypesMismatch) + end + end + + describe "keys_missing?" do + it "returns true if the keys are missing" do + expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(false) + expect(@registry.keys_missing?(key_path)).to eq(true) + end + + it "returns false if no keys in the path are missing" do + expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(true) + expect(@registry.keys_missing?(key_path)).to eq(false) + end + end +end -- cgit v1.2.1 From e44155b53c67ef9f580bca14a7e0cb153b9f041c Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Fri, 4 Sep 2015 11:25:27 -0700 Subject: Update for Windows registry work. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f66a1400ac..6806a76fd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ of partial templates. * [**Joel Handwell**](https://github.com/joelhandwell): [pr#3821](https://github.com/chef/chef/pull/3821) Human friendly elapsed time in log +* [pr#3875](https://github.com/chef/chef/pull/3875) Patch Win32::Registry#delete_key, #delete_value to use wide (W) APIs +* [pr#3850](https://github.com/chef/chef/pull/3850) Patch Win32::Registry#write to fix encoding errors * [pr#3837](https://github.com/chef/chef/pull/3837) refactor remote_directory provider for mem+perf improvement * [pr#3799](https://github.com/chef/chef/pull/3799) fix supports hash issues in service providers * [pr#3817](https://github.com/chef/chef/pull/3817) Remove now-useless forcing of ruby Garbage Collector run -- cgit v1.2.1 From aeab8eb02d49cc3331989fae6d241e3b18373fda Mon Sep 17 00:00:00 2001 From: Salim Alam Date: Fri, 4 Sep 2015 14:24:37 -0700 Subject: Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6806a76fd4..5f9f556c42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,9 @@ of partial templates. * [pr#3850](https://github.com/chef/chef/pull/3850) Patch Win32::Registry#write to fix encoding errors * [pr#3837](https://github.com/chef/chef/pull/3837) refactor remote_directory provider for mem+perf improvement * [pr#3799](https://github.com/chef/chef/pull/3799) fix supports hash issues in service providers +* [pr#3797](https://github.com/chef/chef/pull/3797) Fix dsc_script spec failure on 64-bit Ruby * [pr#3817](https://github.com/chef/chef/pull/3817) Remove now-useless forcing of ruby Garbage Collector run +* [pr#3775](https://github.com/chef/chef/pull/3775) Enable 64-bit support for Powershell and Batch scripts * [pr#3774](https://github.com/chef/chef/pull/3774) Add support for yum-deprecated in yum provider * [pr#3793](https://github.com/chef/chef/pull/3793) CHEF-5372: Support specific `run_levels` for RedHat service * [pr#2460](https://github.com/chef/chef/pull/2460) add privacy flag -- cgit v1.2.1 From d7ce09f91e298457448b212f7ccb547de2d1a5af Mon Sep 17 00:00:00 2001 From: Thom May Date: Mon, 7 Sep 2015 15:11:48 +0100 Subject: sync maintainers with github --- Gemfile | 10 ++- MAINTAINERS.md | 40 +++++++++++- MAINTAINERS.toml | 28 +++++++-- tasks/maintainers.rb | 169 ++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 226 insertions(+), 21 deletions(-) diff --git a/Gemfile b/Gemfile index 6aae2c5b9e..edbd853c47 100644 --- a/Gemfile +++ b/Gemfile @@ -10,11 +10,19 @@ gem 'chef-config', path: "chef-config" gem "chef-zero", "~> 4.2.3" group(:docgen) do - gem "tomlrb" gem "yard" end +group(:maintenance) do + gem "tomlrb" + + # To sync maintainers with github + gem "octokit" + gem "netrc" +end + group(:development, :test) do + gem "simplecov" gem 'rack', "~> 1.5.1" diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 3c777366f8..aeaebd251d 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -22,6 +22,8 @@ Handles the core parts of the Chef DSL, base resource and provider infrastructure, the Chef applications and [omnibus-chef](https://github.com/chef/omnibus-chef). Includes anything not covered by another component. +To mention the team, use @chef/client-core + ### Lieutenant * [Thom May](https://github.com/thommay) @@ -33,7 +35,7 @@ another component. * [Daniel DeLeo](https://github.com/danielsdeleo) * [AJ Christensen](https://github.com/fujin) * [Phil Dibowitz](https://github.com/jaymzh) -* [Jay Mundrawala](https://github.com/jdmundrawala) +* [Jay Mundrawala](https://github.com/jaym) * [Jon Cowie](https://github.com/jonlives) * [Lamont Granquist](https://github.com/lamont-granquist) * [Claire McQuin](https://github.com/mcquin) @@ -44,6 +46,8 @@ another component. ## Dev Tools Chef Zero, Knife, Chef Apply and Chef Shell. +To mention the team, use @chef/client-dev-tools + ### Maintainers * [Daniel DeLeo](https://github.com/danielsdeleo) @@ -54,6 +58,8 @@ Chef Zero, Knife, Chef Apply and Chef Shell. ## Test Tools ChefSpec +To mention the team, use @chef/client-test-tools + ### Lieutenant * [Seth Vargo](https://github.com/sethvargo) @@ -70,6 +76,8 @@ The specific components of Chef related to a given platform - including (but not ## Enterprise Linux +To mention the team, use @chef/client-enterprise-linux + ### Lieutenant * [Jon Cowie](https://github.com/jonlives) @@ -81,6 +89,8 @@ The specific components of Chef related to a given platform - including (but not ## Ubuntu +To mention the team, use @chef/client-ubuntu + ### Lieutenant * [Ranjib Dey](https://github.com/ranjib) @@ -92,19 +102,23 @@ The specific components of Chef related to a given platform - including (but not ## Windows +To mention the team, use @chef/client-windows + ### Lieutenant * [Bryan McLellan](https://github.com/btm) ### Maintainers -* [Jay Mundrawala](https://github.com/jdmundrawala) +* [Jay Mundrawala](https://github.com/jaym) * [Kartik Cating-Subramanian](https://github.com/ksubrama) * [Steven Murawski](https://github.com/smurawski) * [Salim Alam](https://github.com/chefsalim) ## Solaris +To mention the team, use @chef/client-solaris + ### Lieutenant * [Thom May](https://github.com/thommay) @@ -115,12 +129,16 @@ The specific components of Chef related to a given platform - including (but not ## AIX +To mention the team, use @chef/client-aix + ### Lieutenant * [Lamont Granquist](https://github.com/lamont-granquist) ## Mac OS X +To mention the team, use @chef/client-os-x + ### Lieutenant * [Joshua Timberman](https://github.com/jtimberman) @@ -131,6 +149,8 @@ The specific components of Chef related to a given platform - including (but not ## Debian +To mention the team, use @chef/client-debian + ### Lieutenant * [Thom May](https://github.com/thommay) @@ -141,24 +161,32 @@ The specific components of Chef related to a given platform - including (but not ## Fedora +To mention the team, use @chef/client-fedora + ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## openSUSE +To mention the team, use @chef/client-opensuse + ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## SUSE Enterprise Linux Server +To mention the team, use @chef/client-suse + ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## FreeBSD +To mention the team, use @chef/client-freebsd + ### Lieutenant * [Aaron Kalin](https://github.com/martinisoft) @@ -170,24 +198,32 @@ The specific components of Chef related to a given platform - including (but not ## OpenBSD +To mention the team, use @chef/client-openbsd + ### Lieutenant * [Joe Miller](https://github.com/joemiller) ## Gentoo +To mention the team, use @chef/client-gentoo + ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## OmniOS +To mention the team, use @chef/client-omnios + ### Maintainers * [Thom May](https://github.com/thommay) ## ArchLinux +To mention the team, use @chef/client-archlinux + ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml index 61c75c1a30..d72b9b316c 100644 --- a/MAINTAINERS.toml +++ b/MAINTAINERS.toml @@ -1,5 +1,7 @@ # # This file is structured to be consumed by both humans and computers. +# To update the generated Markdown, run `bundle exec rake maintainers:generate` +# To synchronize the maintainers with the github teams, run `bundle exec rake maintainers:synchronize` # It is a TOML document containing Markdown # [Preamble] @@ -24,6 +26,7 @@ a maintainer, lieutenant, or the project lead. [Org.Components.Core] title = "Chef Core" + team = "client-core" text = """ Handles the core parts of the Chef DSL, base resource and provider infrastructure, the Chef applications and [omnibus-chef](https://github.com/chef/omnibus-chef). Includes anything not covered by @@ -38,7 +41,7 @@ another component. "danielsdeleo", "fujin", "jaymzh", - "jdmundrawala", + "jaym", "jonlives", "lamont-granquist", "mcquin", @@ -49,6 +52,7 @@ another component. [Org.Components.DevTools] title = "Dev Tools" + team = "client-dev-tools" text = "Chef Zero, Knife, Chef Apply and Chef Shell." paths = [ @@ -68,6 +72,7 @@ another component. [Org.Components.TestTools] title = "Test Tools" + team = "client-test-tools" text = "ChefSpec" lieutenant = "sethvargo" @@ -86,6 +91,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems."Enterprise Linux"] title = "Enterprise Linux" + team = "client-enterprise-linux" lieutenant = "jonlives" @@ -96,6 +102,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.Ubuntu] title = "Ubuntu" + team = "client-ubuntu" lieutenant = "ranjib" @@ -106,10 +113,11 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.Windows] title = "Windows" + team = "client-windows" lieutenant = "btm" maintainers = [ - "jdmundrawala", + "jaym", "ksubrama", "smurawski", "chefsalim" @@ -117,6 +125,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.Solaris] title = "Solaris" + team = "client-solaris" lieutenant = "thommay" @@ -126,11 +135,13 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.AIX] title = "AIX" + team = "client-aix" lieutenant = "lamont-granquist" [Org.Components.Subsystems."Mac OS X"] title = "Mac OS X" + team = "client-os-x" lieutenant = "jtimberman" @@ -140,6 +151,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.Debian] title = "Debian" + team = "client-debian" lieutenant = "thommay" @@ -149,6 +161,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.Fedora] title = "Fedora" + team = "client-fedora" maintainers = [ "lamont-granquist" @@ -156,6 +169,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.openSUSE] title = "openSUSE" + team = "client-opensuse" maintainers = [ "lamont-granquist" @@ -163,6 +177,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems."SUSE Enterprise Linux"] title = "SUSE Enterprise Linux Server" + team = "client-suse" maintainers = [ "lamont-granquist" @@ -170,6 +185,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.FreeBSD] title = "FreeBSD" + team = "client-freebsd" lieutenant = "martinisoft" @@ -180,11 +196,13 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.OpenBSD] title = "OpenBSD" + team = "client-openbsd" lieutenant = "joemiller" [Org.Components.Subsystems.Gentoo] title = "Gentoo" + team = "client-gentoo" maintainers = [ "lamont-granquist" @@ -192,6 +210,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.OmniOS] title = "OmniOS" + team = "client-omnios" maintainers = [ "thommay" @@ -199,6 +218,7 @@ The specific components of Chef related to a given platform - including (but not [Org.Components.Subsystems.ArchLinux] title = "ArchLinux" + team = "client-archlinux" maintainers = [ "lamont-granquist", @@ -230,9 +250,9 @@ The specific components of Chef related to a given platform - including (but not Name = "Phil Dibowitz" GitHub = "jaymzh" - [people.jdmundrawala] + [people.jaym] Name = "Jay Mundrawala" - GitHub = "jdmundrawala" + GitHub = "jaym" [people.jonlives] Name = "Jon Cowie" diff --git a/tasks/maintainers.rb b/tasks/maintainers.rb index 5a2c8d9c2d..73a422fc61 100644 --- a/tasks/maintainers.rb +++ b/tasks/maintainers.rb @@ -20,50 +20,191 @@ require 'rake' SOURCE = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.toml") TARGET = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.md") +# The list of repositories that teams should own +REPOSITORIES = ["chef/chef", "chef/chef-census", "chef/chef-repo", + "chef/client-docs", "chef/ffi-yajl", "chef/libyajl2-gem", + "chef/mixlib-authentication", "chef/mixlib-cli", + "chef/mixlib-config", "chef/mixlib-install", "chef/mixlib-log", + "chef/mixlib-shellout", "chef/ohai", "chef/omnibus-chef"] + begin require 'tomlrb' + require 'octokit' + require 'pp' task :default => :generate namespace :maintainers do desc "Generate MarkDown version of MAINTAINERS file" task :generate do - maintainers = Tomlrb.load_file SOURCE out = "\n\n" - out << "# " + maintainers["Preamble"]["title"] + "\n\n" - out << maintainers["Preamble"]["text"] + "\n" - out << "# " + maintainers["Org"]["Lead"]["title"] + "\n\n" - out << person(maintainers["people"], maintainers["Org"]["Lead"]["person"]) + "\n\n" - out << components(maintainers["people"], maintainers["Org"]["Components"]) + out << "# " + source["Preamble"]["title"] + "\n\n" + out << source["Preamble"]["text"] + "\n" + + # The project lead is a special case + out << "# " + source["Org"]["Lead"]["title"] + "\n\n" + out << format_person(source["Org"]["Lead"]["person"]) + "\n\n" + + out << format_components(source["Org"]["Components"]) File.open(TARGET, "w") { |fn| fn.write out } end + + desc "Synchronize GitHub teams" + # there's a special @chef/client-maintainers team that's everyone + # and then there's a team per component + task :synchronize do + Octokit.auto_paginate = true + get_github_teams + prepare_teams(source["Org"]["Components"].dup) + sync_teams! + end + end + + def github + @github ||= Octokit::Client.new(:netrc => true) + end + + def source + @source ||= Tomlrb.load_file SOURCE end - def components(list, cmp) + def teams + @teams ||= {"client-maintainers" => {"title" => "Client Maintainers"}} + end + + def add_members(team, name) + teams["client-maintainers"]["members"] ||= [] + teams["client-maintainers"]["members"] << name + teams[team] ||= {} + teams[team]["members"] ||= [] + teams[team]["members"] << name + end + + def set_team_title(team, title) + teams[team] ||= {} + teams[team]["title"] = title + end + + def gh_teams + @gh_teams ||= {} + end + + # we have to resolve team names to ids. While we're at it, we can get the privacy + # setting, so we know whether we need to update it + def get_github_teams + github.org_teams("chef").each do |team| + gh_teams[team[:slug]] = {"id" => team[:id], "privacy" => team[:privacy]} + end + end + + def get_github_team(team) + github.team_members(gh_teams[team]["id"]).map do |member| + member[:login] + end.sort.uniq.map(&:downcase) + rescue + [] + end + + def create_team(team) + puts "creating new github team: #{team} with title: #{teams[team]["title"]} " + t = github.create_team("chef", name: team, description: teams[team]["title"], + privacy: "closed", repo_names: REPOSITORIES, + accept: "application/vnd.github.ironman-preview+json") + gh_teams[team] = { "id" => t[:id], "privacy" => t[:privacy] } + end + + def compare_teams(current, desired) + # additions are the subtraction of the current state from the desired state + # deletions are the subtraction of the desired state from the current state + [desired - current, current - desired] + end + + def prepare_teams(cmp) + %w(text paths).each { |k| cmp.delete(k) } + if cmp.key?("team") + team = cmp.delete("team") + add_members(team, cmp.delete("lieutenant")) if cmp.key?("lieutenant") + add_members(team, cmp.delete("maintainers")) if cmp.key?("maintainers") + set_team_title(team, cmp.delete("title")) + else + %w(maintainers lieutenant title).each { |k| cmp.delete(k) } + end + cmp.each { |_k, v| prepare_teams(v) } + end + + def update_team(team, additions, deletions) + create_team(team) unless gh_teams.key?(team) + update_team_privacy(team) + add_team_members(team, additions) + remove_team_members(team, deletions) + rescue + puts "failed for #{team}" + end + + def update_team_privacy(team) + return if gh_teams[team]["privacy"] == "closed" + puts "Setting #{team} privacy to closed from #{gh_teams[team]["privacy"]}" + github.update_team(gh_teams[team]["id"], privacy: "closed", + accept: "application/vnd.github.ironman-preview+json") + end + + def add_team_members(team, additions) + additions.each do |member| + puts "Adding #{member} to #{team}" + github.add_team_membership(gh_teams[team]["id"], member, role: "member", + accept: "application/vnd.github.ironman-preview+json") + end + end + + def remove_team_members(team, deletions) + deletions.each do |member| + puts "Removing #{member} from #{team}" + github.remove_team_membership(gh_teams[team]["id"], member, + accept: "application/vnd.github.ironman-preview+json") + end + end + + def sync_teams! + teams.each do |name, details| + current = get_github_team(name) + desired = details["members"].flatten.sort.uniq.map(&:downcase) + additions, deletions = compare_teams(current, desired) + update_team(name, additions, deletions) + end + end + + def get_person(person) + source["people"][person] + end + + def format_components(cmp) out = "## " + cmp.delete("title") + "\n\n" out << cmp.delete("text") + "\n" if cmp.has_key?("text") + out << "To mention the team, use @chef/#{cmp.delete("team")}\n\n" if cmp.has_key?("team") if cmp.has_key?("lieutenant") out << "### Lieutenant\n\n" - out << person(list, cmp.delete("lieutenant")) + "\n\n" + out << format_person(cmp.delete("lieutenant")) + "\n\n" end - out << maintainers(list, cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers") + out << format_maintainers(cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers") cmp.delete("paths") - cmp.each {|k,v| out << components(list, v) } + cmp.each {|k,v| out << format_components(v) } out end - def maintainers(list, people) + def format_maintainers(people) o = "### Maintainers\n\n" people.each do |p| - o << person(list, p) + "\n" + o << format_person(p) + "\n" end o end - def person(list, person) - "* [#{list[person]["Name"]}](https://github.com/#{list[person]["GitHub"]})" + def format_person(person) + mnt = get_person(person) + "* [#{mnt["Name"]}](https://github.com/#{mnt["GitHub"]})" end + rescue LoadError STDERR.puts "\n*** TomlRb not available.\n\n" end -- cgit v1.2.1 From 86ed4519c00f7487e3e60a91adbbd70789b11e63 Mon Sep 17 00:00:00 2001 From: Kartik Null Cating-Subramanian Date: Tue, 8 Sep 2015 11:06:27 -0400 Subject: Have appveyor display systeminfo before build Also rename os to Windows 2012 R2 to avoid confusion. --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 06448e2be2..b3cf5395f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: "master-{build}" -os: Windows Server 2012 +os: Windows Server 2012 R2 platform: - x64 @@ -21,6 +21,7 @@ cache: - C:\Ruby200\bin install: + - systeminfo - winrm quickconfig -q - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - echo %PATH% -- cgit v1.2.1 From 27f9af626d717f3293acc8f015ca8ca68981528f Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Tue, 8 Sep 2015 10:31:33 -0500 Subject: Add 64 bit tester --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 06448e2be2..faddbc072f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,7 @@ platform: environment: matrix: - ruby_version: "200" + - ruby_version: "200-x64" clone_folder: c:\projects\chef clone_depth: 1 -- cgit v1.2.1 From eac543b05437f6ee1d9f98cc2656e55a189c2934 Mon Sep 17 00:00:00 2001 From: Steven Murawski Date: Tue, 8 Sep 2015 13:15:12 -0500 Subject: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f9f556c42..4df36163fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ of partial templates. * [pr#3768](https://github.com/chef/chef/pull/3768) Make reboot\_pending? look for CBS RebootPending * [pr#3815](https://github.com/chef/chef/pull/3815) Fix `powershell_script` validation to use correct architecture * [pr#3772](https://github.com/chef/chef/pull/3772) Add `ps_credential` dsl method to `dsc_script` +* [pr#3462](https://github.com/chef/chef/pull/3462) Fix issue where `ps_credential` does not work over winrm ## 12.4.1 -- cgit v1.2.1 From 998e13adbda5e964d2f8d17ebd8f216f752244a6 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Tue, 8 Sep 2015 12:20:45 -0700 Subject: Skip tests unless RefreshMode is Disabled --- lib/chef/platform/query_helpers.rb | 7 +++++++ lib/chef/provider/dsc_resource.rb | 14 +++++--------- spec/functional/resource/dsc_resource_spec.rb | 2 ++ spec/unit/platform/query_helpers_spec.rb | 24 ++++++++++++++++++++++++ spec/unit/provider/dsc_resource_spec.rb | 13 +++---------- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index e64189fbd6..ea86ab227f 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -52,6 +52,13 @@ class Chef Gem::Version.new(node[:languages][:powershell][:version]) >= Gem::Version.new("5.0.10018.0") end + + def refresh_mode_disabled?(node) + require 'chef/util/powershell/cmdlet' + cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object) + metadata = cmdlet.run!.return_value + metadata['RefreshMode'] == 'Disabled' + end end end end diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb index 379369ba6e..31c6c078f4 100644 --- a/lib/chef/provider/dsc_resource.rb +++ b/lib/chef/provider/dsc_resource.rb @@ -59,9 +59,7 @@ class Chef a.block_action! end requirements.assert(:run) do |a| - a.assertion { - meta_configuration['RefreshMode'] == 'Disabled' - } + a.assertion { refresh_mode_disabled? } err = ["The LCM must have its RefreshMode set to Disabled. "] a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ') a.whyrun err + ["Assuming a previous resource sets the RefreshMode."] @@ -85,6 +83,10 @@ class Chef def supports_dsc_invoke_resource? run_context && Chef::Platform.supports_dsc_invoke_resource?(node) end + + def refresh_mode_disabled? + Chef::Platform.refresh_mode_disabled?(node) + end def generate_description @converge_description @@ -153,12 +155,6 @@ class Chef cmdlet.run! end - def meta_configuration - cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object) - result = cmdlet.run! - result.return_value - end - end end end diff --git a/spec/functional/resource/dsc_resource_spec.rb b/spec/functional/resource/dsc_resource_spec.rb index 6f453eeb9f..820e8fe123 100644 --- a/spec/functional/resource/dsc_resource_spec.rb +++ b/spec/functional/resource/dsc_resource_spec.rb @@ -43,6 +43,8 @@ describe Chef::Resource::DscResource, :windows_powershell_dsc_only do before do if !Chef::Platform.supports_dsc_invoke_resource?(node) skip 'Requires Powershell >= 5.0.10018.0' + elsif !Chef::Platform.refresh_mode_disabled?(node) + skip 'Requires LCM RefreshMode is Disabled' end end context 'with an invalid dsc resource' do diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 33d4c2c3b7..78f90748d3 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -75,3 +75,27 @@ describe 'Chef::Platform#supports_dsc_invoke_resource?' do end end +describe 'Chef::Platform#refresh_mode_disabled?' do + let(:node) { instance_double('Chef::Node') } + let(:cmdlet) { instance_double('Chef::Util::Powershell::Cmdlet') } + let(:cmdlet_result) { instance_double('Chef::Util::Powershell::CmdletResult')} + + it "returns true when RefreshMode is Disabled" do + expect(Chef::Util::Powershell::Cmdlet).to receive(:new). + with(node, "Get-DscLocalConfigurationManager", :object). + and_return(cmdlet) + expect(cmdlet).to receive(:run!).and_return(cmdlet_result) + expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'Disabled' }) + expect(Chef::Platform.refresh_mode_disabled?(node)).to be true + end + + it "returns false when RefreshMode is not Disabled" do + expect(Chef::Util::Powershell::Cmdlet).to receive(:new). + with(node, "Get-DscLocalConfigurationManager", :object). + and_return(cmdlet) + expect(cmdlet).to receive(:run!).and_return(cmdlet_result) + expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'LaLaLa' }) + expect(Chef::Platform.refresh_mode_disabled?(node)).to be false + end +end + diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb index 65c1c019f0..807a844346 100644 --- a/spec/unit/provider/dsc_resource_spec.rb +++ b/spec/unit/provider/dsc_resource_spec.rb @@ -50,30 +50,23 @@ describe Chef::Provider::DscResource do } context 'when RefreshMode is not set to Disabled' do - let (:meta_configuration) { {'RefreshMode' => 'AnythingElse'}} - it 'raises an exception' do - expect(provider).to receive(:meta_configuration).and_return( - meta_configuration) + expect(provider).to receive(:refresh_mode_disabled?).and_return(false) expect { provider.run_action(:run) }.to raise_error( Chef::Exceptions::ProviderNotFound, /Disabled/) end end context 'when RefreshMode is set to Disabled' do - let (:meta_configuration) { {'RefreshMode' => 'Disabled'}} - it 'does not update the resource if it is up to date' do - expect(provider).to receive(:meta_configuration).and_return( - meta_configuration) + expect(provider).to receive(:refresh_mode_disabled?).and_return(true) expect(provider).to receive(:test_resource).and_return(true) provider.run_action(:run) expect(resource).not_to be_updated end it 'converges the resource if it is not up to date' do - expect(provider).to receive(:meta_configuration).and_return( - meta_configuration) + expect(provider).to receive(:refresh_mode_disabled?).and_return(true) expect(provider).to receive(:test_resource).and_return(false) expect(provider).to receive(:set_resource) provider.run_action(:run) -- cgit v1.2.1 From 2517cae8ac1b52c9a5eca1347a63c072a97e2c4c Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Tue, 8 Sep 2015 14:57:10 -0700 Subject: Rename refresh_mode_disabled? to dsc_refresh_mode_disabled? --- lib/chef/platform/query_helpers.rb | 2 +- lib/chef/provider/dsc_resource.rb | 6 +++--- spec/functional/resource/dsc_resource_spec.rb | 2 +- spec/unit/platform/query_helpers_spec.rb | 6 +++--- spec/unit/provider/dsc_resource_spec.rb | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index ea86ab227f..2dd33ea190 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -53,7 +53,7 @@ class Chef Gem::Version.new("5.0.10018.0") end - def refresh_mode_disabled?(node) + def dsc_refresh_mode_disabled?(node) require 'chef/util/powershell/cmdlet' cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object) metadata = cmdlet.run!.return_value diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb index 31c6c078f4..65830131ab 100644 --- a/lib/chef/provider/dsc_resource.rb +++ b/lib/chef/provider/dsc_resource.rb @@ -59,7 +59,7 @@ class Chef a.block_action! end requirements.assert(:run) do |a| - a.assertion { refresh_mode_disabled? } + a.assertion { dsc_refresh_mode_disabled? } err = ["The LCM must have its RefreshMode set to Disabled. "] a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ') a.whyrun err + ["Assuming a previous resource sets the RefreshMode."] @@ -84,8 +84,8 @@ class Chef run_context && Chef::Platform.supports_dsc_invoke_resource?(node) end - def refresh_mode_disabled? - Chef::Platform.refresh_mode_disabled?(node) + def dsc_refresh_mode_disabled? + Chef::Platform.dsc_refresh_mode_disabled?(node) end def generate_description diff --git a/spec/functional/resource/dsc_resource_spec.rb b/spec/functional/resource/dsc_resource_spec.rb index 820e8fe123..24503f1ec7 100644 --- a/spec/functional/resource/dsc_resource_spec.rb +++ b/spec/functional/resource/dsc_resource_spec.rb @@ -43,7 +43,7 @@ describe Chef::Resource::DscResource, :windows_powershell_dsc_only do before do if !Chef::Platform.supports_dsc_invoke_resource?(node) skip 'Requires Powershell >= 5.0.10018.0' - elsif !Chef::Platform.refresh_mode_disabled?(node) + elsif !Chef::Platform.dsc_refresh_mode_disabled?(node) skip 'Requires LCM RefreshMode is Disabled' end end diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 78f90748d3..f33bfa128b 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -75,7 +75,7 @@ describe 'Chef::Platform#supports_dsc_invoke_resource?' do end end -describe 'Chef::Platform#refresh_mode_disabled?' do +describe 'Chef::Platform#dsc_refresh_mode_disabled?' do let(:node) { instance_double('Chef::Node') } let(:cmdlet) { instance_double('Chef::Util::Powershell::Cmdlet') } let(:cmdlet_result) { instance_double('Chef::Util::Powershell::CmdletResult')} @@ -86,7 +86,7 @@ describe 'Chef::Platform#refresh_mode_disabled?' do and_return(cmdlet) expect(cmdlet).to receive(:run!).and_return(cmdlet_result) expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'Disabled' }) - expect(Chef::Platform.refresh_mode_disabled?(node)).to be true + expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be true end it "returns false when RefreshMode is not Disabled" do @@ -95,7 +95,7 @@ describe 'Chef::Platform#refresh_mode_disabled?' do and_return(cmdlet) expect(cmdlet).to receive(:run!).and_return(cmdlet_result) expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'LaLaLa' }) - expect(Chef::Platform.refresh_mode_disabled?(node)).to be false + expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be false end end diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb index 807a844346..9946ab8410 100644 --- a/spec/unit/provider/dsc_resource_spec.rb +++ b/spec/unit/provider/dsc_resource_spec.rb @@ -51,7 +51,7 @@ describe Chef::Provider::DscResource do context 'when RefreshMode is not set to Disabled' do it 'raises an exception' do - expect(provider).to receive(:refresh_mode_disabled?).and_return(false) + expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(false) expect { provider.run_action(:run) }.to raise_error( Chef::Exceptions::ProviderNotFound, /Disabled/) end @@ -59,14 +59,14 @@ describe Chef::Provider::DscResource do context 'when RefreshMode is set to Disabled' do it 'does not update the resource if it is up to date' do - expect(provider).to receive(:refresh_mode_disabled?).and_return(true) + expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true) expect(provider).to receive(:test_resource).and_return(true) provider.run_action(:run) expect(resource).not_to be_updated end it 'converges the resource if it is not up to date' do - expect(provider).to receive(:refresh_mode_disabled?).and_return(true) + expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true) expect(provider).to receive(:test_resource).and_return(false) expect(provider).to receive(:set_resource) provider.run_action(:run) -- cgit v1.2.1 From bc0daaae67b2e6bf032e11edbc3057c7531dfc36 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Tue, 8 Sep 2015 18:18:09 -0500 Subject: Add monkey patch for webrick Ruby 2.1 introduces a regression on Windows in WEBrick. create_listeners does not throw an exception when we're already listening on a port. This seems to only be an issue on Windows. This patch reverts it back to what it was in Ruby 2.0 It seems the regression was introduced in https://github.com/ruby/ruby/commit/b1f493dcd1092fe17cccec998e175516ed5c6e47#diff-4b178393150b2b3a5ec9d77eced1f09e --- lib/chef/local_mode.rb | 5 ++++ lib/chef/monkey_patches/webrick-utils.rb | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 lib/chef/monkey_patches/webrick-utils.rb diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb index 79fb750dd8..fbb72cd6cb 100644 --- a/lib/chef/local_mode.rb +++ b/lib/chef/local_mode.rb @@ -15,6 +15,11 @@ # See the License for the specific language governing permissions and # limitations under the License. require 'chef/config' +if Chef::Platform.windows? + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.1') + require 'chef/monkey_patches/webrick-utils' + end +end class Chef module LocalMode diff --git a/lib/chef/monkey_patches/webrick-utils.rb b/lib/chef/monkey_patches/webrick-utils.rb new file mode 100644 index 0000000000..57f6db54ed --- /dev/null +++ b/lib/chef/monkey_patches/webrick-utils.rb @@ -0,0 +1,51 @@ +require 'webrick/utils' + +module WEBrick + module Utils + ## + # Creates TCP server sockets bound to +address+:+port+ and returns them. + # + # It will create IPV4 and IPV6 sockets on all interfaces. + # + # NOTE: We need to monkey patch this method because + # create_listeners on Windows with Ruby > 2.0.0 does not + # raise an error if we're already listening on a port. + # + def create_listeners(address, port, logger=nil) + # + # utils.rb -- Miscellaneous utilities + # + # Author: IPR -- Internet Programming with Ruby -- writers + # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou + # Copyright (c) 2002 Internet Programming with Ruby writers. All rights + # reserved. + # + # $IPR: utils.rb,v 1.10 2003/02/16 22:22:54 gotoyuzo Exp $ + unless port + raise ArgumentError, "must specify port" + end + res = Socket::getaddrinfo(address, port, + Socket::AF_UNSPEC, # address family + Socket::SOCK_STREAM, # socket type + 0, # protocol + Socket::AI_PASSIVE) # flag + last_error = nil + sockets = [] + res.each{|ai| + begin + logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger + sock = TCPServer.new(ai[3], port) + port = sock.addr[1] if port == 0 + Utils::set_close_on_exec(sock) + sockets << sock + rescue => ex + logger.warn("TCPServer Error: #{ex}") if logger + last_error = ex + end + } + raise last_error if sockets.empty? + return sockets + end + module_function :create_listeners + end +end -- cgit v1.2.1 From 0cc0ae46270da20b3bd9045550df2d4fd9ae145f Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Tue, 8 Sep 2015 17:16:40 -0700 Subject: Modify enforce_ownership_and_permissions_spec to be more unit-like --- .../mixin/enforce_ownership_and_permissions_spec.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb index aeef175ff9..408926293e 100644 --- a/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb +++ b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb @@ -49,12 +49,12 @@ describe Chef::Mixin::EnforceOwnershipAndPermissions do allow_any_instance_of(Chef::FileAccessControl).to receive(:uid_from_resource).and_return(0) allow_any_instance_of(Chef::FileAccessControl).to receive(:requires_changes?).and_return(false) allow_any_instance_of(Chef::FileAccessControl).to receive(:define_resource_requirements) + allow_any_instance_of(Chef::FileAccessControl).to receive(:describe_changes) + + passwd_struct = OpenStruct.new(:name => "root", :passwd => "x", + :uid => 0, :gid => 0, :dir => '/root', + :shell => '/bin/bash') - passwd_struct = if windows? - Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash") - else - Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash") - end group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0) allow(Etc).to receive(:getpwuid).and_return(passwd_struct) allow(Etc).to receive(:getgrgid).and_return(group_struct) @@ -73,12 +73,12 @@ describe Chef::Mixin::EnforceOwnershipAndPermissions do before do allow_any_instance_of(Chef::FileAccessControl).to receive(:requires_changes?).and_return(true) allow_any_instance_of(Chef::FileAccessControl).to receive(:uid_from_resource).and_return(0) + allow_any_instance_of(Chef::FileAccessControl).to receive(:describe_changes) + + passwd_struct = OpenStruct.new(:name => "root", :passwd => "x", + :uid => 0, :gid => 0, :dir => '/root', + :shell => '/bin/bash') - passwd_struct = if windows? - Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash") - else - Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash") - end group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0) allow(Etc).to receive(:getpwuid).and_return(passwd_struct) allow(Etc).to receive(:getgrgid).and_return(group_struct) -- cgit v1.2.1 From 7d62aa46d533f758fc5c3b6d00cb9ac4510253b7 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 10 Sep 2015 09:36:55 -0700 Subject: Allow windows_service_spec to only run in appveyor We thing that these tests may be causing instability on our jenkins slaves. --- spec/functional/resource/windows_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/functional/resource/windows_service_spec.rb b/spec/functional/resource/windows_service_spec.rb index 29d1fc42c3..90545429e5 100644 --- a/spec/functional/resource/windows_service_spec.rb +++ b/spec/functional/resource/windows_service_spec.rb @@ -18,7 +18,7 @@ require 'spec_helper' -describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only do +describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only, :appveyor_only do include_context "using Win32::Service" -- cgit v1.2.1 From 3751f8d4bca04834e4660be9319f0dd006b79454 Mon Sep 17 00:00:00 2001 From: Kartik Null Cating-Subramanian Date: Tue, 8 Sep 2015 15:17:08 -0400 Subject: Add ruby 2.1 and 2.2 to appveyor --- appveyor.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 26fa871923..b168c4deee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,6 +8,10 @@ environment: matrix: - ruby_version: "200" - ruby_version: "200-x64" + - ruby_version: "21" + - ruby_version: "21-x64" + - ruby_version: "22" + - ruby_version: "22-x64" clone_folder: c:\projects\chef clone_depth: 1 @@ -15,11 +19,20 @@ skip_tags: true branches: only: - master - - 12.4-stable cache: - C:\Ruby200\lib\ruby\gems\2.0.0 - C:\Ruby200\bin + - C:\Ruby200-x64\lib\ruby\gems\2.0.0 + - C:\Ruby200\bin + - C:\Ruby21\lib\ruby\gems\2.1.0 + - C:\Ruby21\bin + - C:\Ruby21-x64\lib\ruby\gems\2.1.0 + - C:\Ruby21\bin + - C:\Ruby22\lib\ruby\gems\2.2.0 + - C:\Ruby22\bin + - C:\Ruby22-x64\lib\ruby\gems\2.2.0 + - C:\Ruby22\bin install: - systeminfo -- cgit v1.2.1 From 24c3a6002138e8440e67953019de720d451c9ec8 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Thu, 10 Sep 2015 12:53:43 -0700 Subject: Remove unused things --- appveyor.yml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b168c4deee..7cc8fb631d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,9 +9,6 @@ environment: - ruby_version: "200" - ruby_version: "200-x64" - ruby_version: "21" - - ruby_version: "21-x64" - - ruby_version: "22" - - ruby_version: "22-x64" clone_folder: c:\projects\chef clone_depth: 1 @@ -20,20 +17,6 @@ branches: only: - master -cache: - - C:\Ruby200\lib\ruby\gems\2.0.0 - - C:\Ruby200\bin - - C:\Ruby200-x64\lib\ruby\gems\2.0.0 - - C:\Ruby200\bin - - C:\Ruby21\lib\ruby\gems\2.1.0 - - C:\Ruby21\bin - - C:\Ruby21-x64\lib\ruby\gems\2.1.0 - - C:\Ruby21\bin - - C:\Ruby22\lib\ruby\gems\2.2.0 - - C:\Ruby22\bin - - C:\Ruby22-x64\lib\ruby\gems\2.2.0 - - C:\Ruby22\bin - install: - systeminfo - winrm quickconfig -q -- cgit v1.2.1 From 716eb7679cd30cd0e371ea8699d5dbdb47a05169 Mon Sep 17 00:00:00 2001 From: Matt Wrock Date: Thu, 10 Sep 2015 22:08:51 -0700 Subject: remove pending reboot check for HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile if not on windows 2003 --- lib/chef/dsl/reboot_pending.rb | 3 +- spec/functional/dsl/reboot_pending_spec.rb | 76 +++++++++++++----------------- spec/unit/dsl/reboot_pending_spec.rb | 16 +++++-- 3 files changed, 46 insertions(+), 49 deletions(-) diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb index c577118dd4..3d84b29ec5 100644 --- a/lib/chef/dsl/reboot_pending.rb +++ b/lib/chef/dsl/reboot_pending.rb @@ -49,7 +49,8 @@ class Chef # The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates # http://support.microsoft.com/kb/832475 - (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') && + Chef::Platform.windows_server_2003? && + (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') && !registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0].nil? && [1,2,3].include?(registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0][:data])) elsif platform?("ubuntu") diff --git a/spec/functional/dsl/reboot_pending_spec.rb b/spec/functional/dsl/reboot_pending_spec.rb index 14dd9412d5..1d11f38dbc 100644 --- a/spec/functional/dsl/reboot_pending_spec.rb +++ b/spec/functional/dsl/reboot_pending_spec.rb @@ -30,13 +30,6 @@ describe Chef::DSL::RebootPending, :windows_only do ohai end - def registry_unsafe? - registry.value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) || - registry.key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') - registry.key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') || - registry.key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') - end - let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let!(:ohai) { run_ohai } # Ensure we have necessary node data @@ -45,76 +38,73 @@ describe Chef::DSL::RebootPending, :windows_only do let(:registry) { Chef::Win32::Registry.new(run_context) } describe "reboot_pending?" do + let(:reg_key) { nil } + let(:original_set) { false } - describe "when there is nothing to indicate a reboot is pending" do - it "should return false" do - skip "Found existing registry keys" if registry_unsafe? - expect(recipe.reboot_pending?).to be_falsey - end - end + before(:all) { @any_flag = Hash.new } + + after { @any_flag[reg_key] = original_set } describe 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations' do + let(:reg_key) { 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager' } + let(:original_set) { registry.value_exists?(reg_key, { :name => 'PendingFileRenameOperations' }) } + it "returns true if the registry value exists" do - skip "Found existing registry keys" if registry_unsafe? - registry.set_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', + skip 'found existing registry key' if original_set + registry.set_value(reg_key, { :name => 'PendingFileRenameOperations', :type => :multi_string, :data => ['\??\C:\foo.txt|\??\C:\bar.txt'] }) expect(recipe.reboot_pending?).to be_truthy end after do - unless registry_unsafe? - registry.delete_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) + unless original_set + registry.delete_value(reg_key, { :name => 'PendingFileRenameOperations' }) end end end - describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' do + describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' do + let(:reg_key) { 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' } + let(:original_set) { registry.key_exists?(reg_key) } + it "returns true if the registry key exists" do - skip "Found existing registry keys" if registry_unsafe? - registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false) + skip 'found existing registry key' if original_set + pending "Permissions are limited to 'TrustedInstaller' by default" + registry.create_key(reg_key, false) expect(recipe.reboot_pending?).to be_truthy end after do - unless registry_unsafe? - registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false) + unless original_set + registry.delete_key(reg_key, false) end end end - describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' do + describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' do + let(:reg_key) { 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' } + let(:original_set) { registry.key_exists?(reg_key) } + it "returns true if the registry key exists" do - pending "Permissions are limited to 'TrustedInstaller' by default" - skip "Found existing registry keys" if registry_unsafe? - registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false) + skip 'found existing registry key' if original_set + registry.create_key(reg_key, false) expect(recipe.reboot_pending?).to be_truthy end after do - unless registry_unsafe? - registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false) + unless original_set + registry.delete_key(reg_key, false) end end end - describe 'HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile\Flags' do - it "returns true if the registry key exists" do - skip "Found existing registry keys" if registry_unsafe? - registry.create_key('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', true) - registry.set_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', - { :name => 'Flags', :type => :dword, :data => 3 }) - - expect(recipe.reboot_pending?).to be_truthy - end - - after do - unless registry_unsafe? - registry.delete_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', { :name => 'Flags' }) - registry.delete_key('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', false) - end + describe "when there is nothing to indicate a reboot is pending" do + it "should return false" do + skip 'reboot pending' if @any_flag.any? { |_,v| v == true } + expect(recipe.reboot_pending?).to be_falsey end end end diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb index a55f91d5e6..6705820e17 100644 --- a/spec/unit/dsl/reboot_pending_spec.rb +++ b/spec/unit/dsl/reboot_pending_spec.rb @@ -50,11 +50,17 @@ describe Chef::DSL::RebootPending do expect(recipe.reboot_pending?).to be_truthy end - it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data' do - allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true) - allow(recipe).to receive(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return( - [{:name => "Flags", :type => :dword, :data => 3}]) - expect(recipe.reboot_pending?).to be_truthy + context "version is server 2003" do + before do + allow(Chef::Platform).to receive(:windows_server_2003?).and_return(true) + end + + it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data on 2k3' do + allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true) + allow(recipe).to receive(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return( + [{:name => "Flags", :type => :dword, :data => 3}]) + expect(recipe.reboot_pending?).to be_truthy + end end end -- cgit v1.2.1 From ab88167f33e1ee41cfcfbacfca6647113019b8a4 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Fri, 11 Sep 2015 13:22:55 -0700 Subject: Windows cookbook dependencies should be updated for Chef 12.5 --- RELEASE_NOTES.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cba5b9f415..d95c416a41 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,9 @@ # Chef Client Release Notes 12.5.0: * OSX 10.11 support (support for SIP and service changes) +* Windows cookbook <= 1.38.1 contains a library file which does not compile with +chef-client 12.5.0. This is resolved in version 1.38.2 of the Windows cookbook. +Cookbooks depending on the Windows cookbook should update the dependency version +to at least 1.38.2 to be compatible with chef-client 12.5.0. ## PSCredential support for the `dsc_script` resource @@ -59,7 +63,7 @@ In Fedora 22 yum has been deprecated in favor of DNF. Unfortunately, while DNF compatible with yum, the yum provider in Chef is not compatible with DNF. Until a proper `dnf_package` resource and associated provider is written and merged into core, 12.5.0 has been patched so that the `yum_package` resource takes a property named `yum_binary` which can be set to point at the yum binary -to run for all its commands. The `yum_binary` will also default to `yum-deprecated` if the +to run for all its commands. The `yum_binary` will also default to `yum-deprecated` if the `/usr/bin/yum-deprecated` command is found on the system. This means that Fedora 22 users can run something like this early in their chef-client run: @@ -73,4 +77,3 @@ end After which the yum-deprecated binary will exist, and the yum provider will find it and should operate normally and successfully. - -- cgit v1.2.1 From e24de71409a861f9bc6318755debb2c18d27f2a3 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Mon, 14 Sep 2015 07:54:57 -0700 Subject: Fix failing specs on Windows The verify tests were not correctly mocking things out. --- spec/support/shared/unit/provider/file.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb index 7de9698451..f552fe0a2a 100644 --- a/spec/support/shared/unit/provider/file.rb +++ b/spec/support/shared/unit/provider/file.rb @@ -466,10 +466,12 @@ shared_examples_for Chef::Provider::File do } let(:verification) { double("Verification") } + let(:verification_fail) { double("Verification Fail") } context "with user-supplied verifications" do it "calls #verify on each verification with tempfile path" do - allow(Chef::Resource::File::Verification).to receive(:new).and_return(verification) + allow(Chef::Resource::File::Verification).to( + receive(:new).with(anything(), "true", anything()).and_return(verification)) provider.new_resource.verify "true" provider.new_resource.verify "true" expect(verification).to receive(:verify).with(tempfile.path).twice.and_return(true) @@ -477,10 +479,14 @@ shared_examples_for Chef::Provider::File do end it "raises an exception if any verification fails" do + allow(Chef::Resource::File::Verification).to( + receive(:new).with(anything(), "true", anything()).and_return(verification)) + allow(Chef::Resource::File::Verification).to( + receive(:new).with(anything(), "false", anything()).and_return(verification_fail)) provider.new_resource.verify "true" provider.new_resource.verify "false" - allow(verification).to receive(:verify).with("true").and_return(true) - allow(verification).to receive(:verify).with("false").and_return(false) + expect(verification).to receive(:verify).with(tempfile.path).and_return(true) + expect(verification_fail).to receive(:verify).with(tempfile.path).and_return(false) expect{provider.send(:do_validate_content)}.to raise_error(Chef::Exceptions::ValidationFailed) end end -- cgit v1.2.1 From d5ec7382e5587e7f4fed61287a77cfa1e10747a4 Mon Sep 17 00:00:00 2001 From: Jay Mundrawala Date: Mon, 14 Sep 2015 09:41:56 -0700 Subject: Use instance_double instead of instance --- spec/support/shared/unit/provider/file.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb index f552fe0a2a..ff9e271a0a 100644 --- a/spec/support/shared/unit/provider/file.rb +++ b/spec/support/shared/unit/provider/file.rb @@ -465,8 +465,8 @@ shared_examples_for Chef::Provider::File do t } - let(:verification) { double("Verification") } - let(:verification_fail) { double("Verification Fail") } + let(:verification) { instance_double(Chef::Resource::File::Verification) } + let(:verification_fail) { instance_double(Chef::Resource::File::Verification) } context "with user-supplied verifications" do it "calls #verify on each verification with tempfile path" do -- cgit v1.2.1 From 72cb4af6451f67791469456899df5bd09db94d7b Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Wed, 2 Sep 2015 18:01:09 -0700 Subject: Remove the warning about hashes and arrays as default values - This isn't a bug to declare an LWRP default hash or array, its a bug to use the hash or array in such a way as to mutate it. A lot of LWRP authors will get this triggered on their code when none of their users are actually triggering the bug. - It would be better handled by freezing default values in Chef 13. - We could also just not try to solve this problem in code and treat it fundamentally as a documentation/education problem. --- lib/chef/resource.rb | 4 ---- spec/unit/property_spec.rb | 6 ------ 2 files changed, 10 deletions(-) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index ee75dec3b9..aec78b4f6d 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -808,10 +808,6 @@ class Chef property = property_type(**options) end - if !options[:default].frozen? && (options[:default].is_a?(Array) || options[:default].is_a?(Hash)) - Chef.log_deprecation("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.") - end - local_properties = properties(false) local_properties[name] = property diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 50764aa7a2..f758b5f403 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -480,12 +480,6 @@ describe "Chef::Resource.property" do end end - it "when a property is declared with default: {}, a warning is issued" do - expect(Chef::Log).to receive(:deprecation).with( /^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/, /property_spec\.rb/ ) - resource_class.class_eval("property :x, default: {}", __FILE__, __LINE__) - expect(resource.x).to eq({}) - end - with_property ':x, default: lazy { {} }' do it "when x is not set, it returns {}" do expect(resource.x).to eq({}) -- cgit v1.2.1 From 01bfabc9015f68af084d76a29ee46dc38285e535 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Mon, 14 Sep 2015 18:14:52 -0700 Subject: remove deprecation warnings test --- spec/integration/client/client_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index 1a030c130b..5b235e2720 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -350,8 +350,6 @@ EOM expect(run_complete).to be >= 0 # Make sure there is exactly one result for each, and that it occurs *after* the complete message. - expect(match_indices(/MyResource.x has an array or hash default/, result.stdout)).to match([ be > run_complete ]) - expect(match_indices(/MyResource.y has an array or hash default/, result.stdout)).to match([ be > run_complete ]) expect(match_indices(/nil currently does not overwrite the value of/, result.stdout)).to match([ be > run_complete ]) end end -- cgit v1.2.1 From fbd4375eb898b6a0e1a3d55dfe1a91e4c1c26d8a Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Thu, 17 Sep 2015 10:01:02 -0700 Subject: Rename Chef::Resource#current_resource -> Chef::Resource#current_value --- lib/chef/resource.rb | 2 +- spec/integration/recipes/resource_load_spec.rb | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index aec78b4f6d..5c230ad2f4 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -1406,7 +1406,7 @@ class Chef # @return A new copy of the resource, with values filled in from the actual # current value. # - def current_resource + def current_value provider = provider_for_action(Array(action).first) if provider.whyrun_mode? && !provider.whyrun_supported? raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run" diff --git a/spec/integration/recipes/resource_load_spec.rb b/spec/integration/recipes/resource_load_spec.rb index c29b877b59..556201efd8 100644 --- a/spec/integration/recipes/resource_load_spec.rb +++ b/spec/integration/recipes/resource_load_spec.rb @@ -63,17 +63,17 @@ describe "Resource.load_current_value" do end it "current_resource is passed name but not x" do - expect(resource.current_resource.x).to eq 'loaded 2 (name=blah)' + expect(resource.current_value.x).to eq 'loaded 2 (name=blah)' end - it "resource.current_resource returns a different resource" do - expect(resource.current_resource.x).to eq 'loaded 2 (name=blah)' + it "resource.current_value returns a different resource" do + expect(resource.current_value.x).to eq 'loaded 2 (name=blah)' expect(resource.x).to eq 'desired' end - it "resource.current_resource constructs the resource anew each time" do - expect(resource.current_resource.x).to eq 'loaded 2 (name=blah)' - expect(resource.current_resource.x).to eq 'loaded 3 (name=blah)' + it "resource.current_value constructs the resource anew each time" do + expect(resource.current_value.x).to eq 'loaded 2 (name=blah)' + expect(resource.current_value.x).to eq 'loaded 3 (name=blah)' end it "the provider accesses the current value of x" do @@ -94,7 +94,7 @@ describe "Resource.load_current_value" do } it "i, name and d are passed to load_current_value, but not x" do - expect(resource.current_resource.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)' + expect(resource.current_value.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)' end end @@ -112,7 +112,7 @@ describe "Resource.load_current_value" do } it "i, name and d are passed to load_current_value, but not x" do - expect(resource.current_resource.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)' + expect(resource.current_value.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)' end end end @@ -160,10 +160,10 @@ describe "Resource.load_current_value" do context "and a child resource class with no load_current_value" do it "the parent load_current_value is used" do - expect(subresource.current_resource.x).to eq 'loaded 2 (name=blah)' + expect(subresource.current_value.x).to eq 'loaded 2 (name=blah)' end it "load_current_value yields a copy of the child class" do - expect(subresource.current_resource).to be_kind_of(subresource_class) + expect(subresource.current_value).to be_kind_of(subresource_class) end end @@ -178,7 +178,7 @@ describe "Resource.load_current_value" do } it "the overridden load_current_value is used" do - current_resource = subresource.current_resource + current_resource = subresource.current_value expect(current_resource.x).to eq 'default 3' expect(current_resource.y).to eq 'loaded_y 2 (name=blah)' end @@ -196,7 +196,7 @@ describe "Resource.load_current_value" do } it "the original load_current_value is called as well as the child one" do - current_resource = subresource.current_resource + current_resource = subresource.current_value expect(current_resource.x).to eq 'loaded 3 (name=blah)' expect(current_resource.y).to eq 'loaded_y 4 (name=blah, x=loaded 3 (name=blah))' end -- cgit v1.2.1 From 01be9b1e91e515eb76de43a1e448e5476bd56574 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Wed, 9 Sep 2015 18:17:48 -0700 Subject: Add policy_name and policy_group attrs to Node class --- lib/chef/node.rb | 49 ++++++++++++++++++++++++++++++++++ spec/unit/node_spec.rb | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 22c7d5bd8e..65ed21a442 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -63,6 +63,8 @@ class Chef include Chef::Mixin::ParamsValidate + NULL_ARG = Object.new + # Create a new Chef::Node object. def initialize(chef_server_rest: nil) @chef_server_rest = chef_server_rest @@ -72,6 +74,9 @@ class Chef @primary_runlist = Chef::RunList.new @override_runlist = Chef::RunList.new + @policy_name = nil + @policy_group = nil + @attributes = Chef::Node::Attribute.new({}, {}, {}, {}) @run_state = {} @@ -132,6 +137,50 @@ class Chef alias :environment :chef_environment + # The `policy_name` for this node. Setting this to a non-nil value will + # enable policyfile mode when `chef-client` is run. If set in the config + # file or in node json, running `chef-client` will update this value. + # + # @see Chef::PolicyBuilder::Dynamic + # @see Chef::PolicyBuilder::Policyfile + # + # @param arg [String] the new policy_name value + # @return [String] the current policy_name, or the one you just set + def policy_name(arg=NULL_ARG) + return @policy_name if arg.equal?(NULL_ARG) + validate({policy_name: arg}, { policy_name: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } }) + @policy_name = arg + end + + # A "non-DSL-style" setter for `policy_name` + # + # @see #policy_name + def policy_name=(policy_name) + policy_name(policy_name) + end + + # The `policy_group` for this node. Setting this to a non-nil value will + # enable policyfile mode when `chef-client` is run. If set in the config + # file or in node json, running `chef-client` will update this value. + # + # @see Chef::PolicyBuilder::Dynamic + # @see Chef::PolicyBuilder::Policyfile + # + # @param arg [String] the new policy_group value + # @return [String] the current policy_group, or the one you just set + def policy_group(arg=NULL_ARG) + return @policy_group if arg.equal?(NULL_ARG) + validate({policy_group: arg}, { policy_group: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } }) + @policy_group = arg + end + + # A "non-DSL-style" setter for `policy_group` + # + # @see #policy_group + def policy_group=(policy_group) + policy_group(policy_group) + end + def attributes @attributes end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index b7752eb734..39a4e829c9 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -127,6 +127,78 @@ describe Chef::Node do end end + describe "policy_name" do + + it "defaults to nil" do + expect(node.policy_name).to be_nil + end + + it "sets policy_name with a regular setter" do + node.policy_name = "example-policy" + expect(node.policy_name).to eq("example-policy") + end + + it "allows policy_name with every valid character" do + expect { node.policy_name = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqurstuvwxyz0123456789-_:.' }.to_not raise_error + end + + it "sets policy_name when given an argument" do + node.policy_name("example-policy") + expect(node.policy_name).to eq("example-policy") + end + + it "sets policy_name to nil when given nil" do + node.policy_name = "example-policy" + node.policy_name = nil + expect(node.policy_name).to be_nil + end + + it "disallows non-strings" do + expect { node.policy_name(Hash.new) }.to raise_error(Chef::Exceptions::ValidationFailed) + expect { node.policy_name(42) }.to raise_error(Chef::Exceptions::ValidationFailed) + end + + it "cannot be blank" do + expect { node.policy_name("")}.to raise_error(Chef::Exceptions::ValidationFailed) + end + end + + describe "policy_group" do + + it "defaults to nil" do + expect(node.policy_group).to be_nil + end + + it "sets policy_group with a regular setter" do + node.policy_group = "staging" + expect(node.policy_group).to eq("staging") + end + + it "allows policy_group with every valid character" do + expect { node.policy_group = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqurstuvwxyz0123456789-_:.' }.to_not raise_error + end + + it "sets an environment with chef_environment(something)" do + node.policy_group("staging") + expect(node.policy_group).to eq("staging") + end + + it "sets policy_group to nil when given nil" do + node.policy_group = "staging" + node.policy_group = nil + expect(node.policy_group).to be_nil + end + + it "disallows non-strings" do + expect { node.policy_group(Hash.new) }.to raise_error(Chef::Exceptions::ValidationFailed) + expect { node.policy_group(42) }.to raise_error(Chef::Exceptions::ValidationFailed) + end + + it "cannot be blank" do + expect { node.policy_group("")}.to raise_error(Chef::Exceptions::ValidationFailed) + end + end + describe "attributes" do it "should have attributes" do expect(node.attribute).to be_a_kind_of(Hash) -- cgit v1.2.1 From 69c7fa5f63e01c64f8ccc198ddc00c836c24914e Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Wed, 9 Sep 2015 18:33:15 -0700 Subject: Add Dynamic PolicyBuilder to switch on policyfile mode Now we need to switch PolicyBuilder implementations based on several factors instead of just a single config settings, including content of the node fetched from the Chef Server. --- lib/chef/policy_builder.rb | 1 + lib/chef/policy_builder/dynamic.rb | 136 ++++++++++++++++++++ spec/unit/policy_builder/dynamic_spec.rb | 211 +++++++++++++++++++++++++++++++ 3 files changed, 348 insertions(+) create mode 100644 lib/chef/policy_builder/dynamic.rb create mode 100644 spec/unit/policy_builder/dynamic_spec.rb diff --git a/lib/chef/policy_builder.rb b/lib/chef/policy_builder.rb index 136b2853b0..036d8bc051 100644 --- a/lib/chef/policy_builder.rb +++ b/lib/chef/policy_builder.rb @@ -18,6 +18,7 @@ require 'chef/policy_builder/expand_node_object' require 'chef/policy_builder/policyfile' +require 'chef/policy_builder/dynamic' class Chef diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb new file mode 100644 index 0000000000..e976ddd87f --- /dev/null +++ b/lib/chef/policy_builder/dynamic.rb @@ -0,0 +1,136 @@ +# +# Author:: Daniel DeLeo () +# Copyright:: Copyright 2015 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/log' +require 'chef/rest' +require 'chef/run_context' +require 'chef/config' +require 'chef/node' + +class Chef + module PolicyBuilder + + # PolicyBuilder that selects either a Policyfile or non-Policyfile + # implementation based on the content of the node object. + class Dynamic + + attr_reader :node + attr_reader :node_name + attr_reader :ohai_data + attr_reader :json_attribs + attr_reader :override_runlist + attr_reader :events + + def initialize(node_name, ohai_data, json_attribs, override_runlist, events) + @implementation = nil + + @node_name = node_name + @ohai_data = ohai_data + @json_attribs = json_attribs + @override_runlist = override_runlist + @events = events + + @node = nil + end + + ## PolicyBuilder API ## + + # Loads the node state from the server, then picks the correct + # implementation class based on the node and json_attribs. + def load_node + events.node_load_start(node_name, config) + Chef::Log.debug("Building node object for #{node_name}") + + node = Chef::Node.find_or_create(node_name) + select_implementation(node) + implementation.finish_load_node(node) + node + rescue Exception => e + events.node_load_failed(node_name, e, config) + raise + end + + ## Delegated Methods ## + + def original_runlist + implementation.original_runlist + end + + def run_context + implementation.run_context + end + + def run_list_expansion + implementation.run_list_expansion + end + + def build_node + implementation.build_node + end + + def setup_run_context(specific_recipes=nil) + implementation.setup_run_context(specific_recipes) + end + + def expand_run_list + implementation.expand_run_list + end + + def sync_cookbooks + implementation.sync_cookbooks + end + + def temporary_policy? + implementation.temporary_policy? + end + + ## Internal Public API ## + + def implementation + @implementation + end + + def select_implementation(node) + if policyfile_set_in_config? || policyfile_attribs_in_node_json? || node_has_policyfile_attrs?(node) + @implementation = Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events) + else + @implementation = ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events) + end + end + + def config + Chef::Config + end + + private + + def node_has_policyfile_attrs?(node) + node.policy_name || node.policy_group + end + + def policyfile_attribs_in_node_json? + json_attribs.key?("policy_name") || json_attribs.key?("policy_group") + end + + def policyfile_set_in_config? + config[:use_policyfile] || config[:policy_name] || config[:policy_group] + end + + end + end +end diff --git a/spec/unit/policy_builder/dynamic_spec.rb b/spec/unit/policy_builder/dynamic_spec.rb new file mode 100644 index 0000000000..74a0272f42 --- /dev/null +++ b/spec/unit/policy_builder/dynamic_spec.rb @@ -0,0 +1,211 @@ +# +# Author:: Daniel DeLeo () +# Copyright:: Copyright 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/policy_builder' + +describe Chef::PolicyBuilder::Dynamic do + + let(:node_name) { "joe_node" } + let(:ohai_data) { {"platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com"} } + let(:json_attribs) { {"custom_attr" => "custom_attr_value"} } + let(:override_runlist) { nil } + let(:events) { Chef::EventDispatch::Dispatcher.new } + + let(:err_namespace) { Chef::PolicyBuilder::Policyfile } + + let(:base_node) do + node = Chef::Node.new + node.name(node_name) + node + end + + let(:node) { base_node } + + subject(:policy_builder) { Chef::PolicyBuilder::Dynamic.new(node_name, ohai_data, json_attribs, override_runlist, events) } + + describe "loading policy data" do + + describe "delegating PolicyBuilder API to the correct implementation" do + + let(:implementation) { instance_double("Chef::PolicyBuilder::Policyfile") } + + before do + allow(policy_builder).to receive(:implementation).and_return(implementation) + end + + # Dynamic should load_node, figure out the correct backend, then forward + # messages to it after. That behavior is tested below. + it "responds to #load_node" do + expect(policy_builder).to respond_to(:load_node) + end + + it "forwards #original_runlist" do + expect(implementation).to receive(:original_runlist) + policy_builder.original_runlist + end + + it "forwards #run_context" do + expect(implementation).to receive(:run_context) + policy_builder.run_context + end + + it "forwards #run_list_expansion" do + expect(implementation).to receive(:run_list_expansion) + policy_builder.run_list_expansion + end + + it "forwards #build_node to the implementation object" do + expect(implementation).to receive(:build_node) + policy_builder.build_node + end + + it "forwards #setup_run_context to the implementation object" do + expect(implementation).to receive(:setup_run_context) + policy_builder.setup_run_context + + arg = Object.new + + expect(implementation).to receive(:setup_run_context).with(arg) + policy_builder.setup_run_context(arg) + end + + it "forwards #expand_run_list to the implementation object" do + expect(implementation).to receive(:expand_run_list) + policy_builder.expand_run_list + end + + it "forwards #sync_cookbooks to the implementation object" do + expect(implementation).to receive(:sync_cookbooks) + policy_builder.sync_cookbooks + end + + it "forwards #temporary_policy? to the implementation object" do + expect(implementation).to receive(:temporary_policy?) + policy_builder.temporary_policy? + end + + end + + describe "selecting a backend implementation" do + + let(:implementation) do + policy_builder.select_implementation(node) + policy_builder.implementation + end + + context "when no policyfile attributes are present on the node" do + + context "and json_attribs are not given" do + + let(:json_attribs) { {} } + + it "uses the ExpandNodeObject implementation" do + expect(implementation).to be_a(Chef::PolicyBuilder::ExpandNodeObject) + end + + end + + context "and no policyfile attributes are present in json_attribs" do + + let(:json_attribs) { {"foo" => "bar"} } + + it "uses the ExpandNodeObject implementation" do + expect(implementation).to be_a(Chef::PolicyBuilder::ExpandNodeObject) + end + + end + + context "and :use_policyfile is set in Chef::Config" do + + before do + Chef::Config[:use_policyfile] = true + end + + it "uses the Policyfile implementation" do + expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) + end + + end + + context "and policy_name and policy_group are set on Chef::Config" do + + before do + Chef::Config[:policy_name] = "example-policy" + Chef::Config[:policy_group] = "testing" + end + + it "uses the Policyfile implementation" do + expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) + end + + end + + context "and policyfile attributes are present in json_attribs" do + + let(:json_attribs) { {"policy_name" => "example-policy", "policy_group" => "testing"} } + + it "uses the Policyfile implementation" do + expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) + end + + end + + end + + context "when policyfile attributes are present on the node" do + + let(:node) do + base_node.policy_name = "example-policy" + base_node.policy_group = "staging" + base_node + end + + it "uses the Policyfile implementation" do + expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) + end + + end + + end + + describe "loading a node" do + + let(:implementation) { instance_double("Chef::PolicyBuilder::Policyfile") } + + before do + allow(policy_builder).to receive(:implementation).and_return(implementation) + + expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) + expect(policy_builder).to receive(:select_implementation).with(node) + expect(implementation).to receive(:finish_load_node).with(node) + end + + context "when successful" do + + it "selects the backend implementation and continues node loading", :pending do + policy_builder.load_node + end + + end + + end + + end + +end -- cgit v1.2.1 From 6f65a2ea7771a319e4315d68dd5234aeedc7dfd9 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Fri, 11 Sep 2015 17:18:22 -0700 Subject: Use the dynamic policy builder everywhere --- lib/chef/client.rb | 2 +- lib/chef/exceptions.rb | 2 + lib/chef/policy_builder.rb | 8 --- lib/chef/policy_builder/dynamic.rb | 10 +++- lib/chef/policy_builder/expand_node_object.rb | 20 +------ lib/chef/policy_builder/policyfile.rb | 12 +---- spec/unit/client_spec.rb | 4 ++ spec/unit/policy_builder/dynamic_spec.rb | 61 ++++++++++++++++++++-- .../unit/policy_builder/expand_node_object_spec.rb | 50 ++++-------------- spec/unit/policy_builder/policyfile_spec.rb | 33 +++--------- 10 files changed, 91 insertions(+), 111 deletions(-) diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 621ce3d489..7d5d463242 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -496,7 +496,7 @@ class Chef # @api private # def policy_builder - @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, override_runlist, events) + @policy_builder ||= Chef::PolicyBuilder::Dynamic.new(node_name, ohai.data, json_attribs, override_runlist, events) end # diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index e3649c068b..7862d9c160 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -116,6 +116,8 @@ class Chef end end + class InvalidPolicybuilderCall < ArgumentError; end + class InvalidResourceSpecification < ArgumentError; end class SolrConnectionError < RuntimeError; end class IllegalChecksumRevert < RuntimeError; end diff --git a/lib/chef/policy_builder.rb b/lib/chef/policy_builder.rb index 036d8bc051..56415dbbd0 100644 --- a/lib/chef/policy_builder.rb +++ b/lib/chef/policy_builder.rb @@ -38,13 +38,5 @@ class Chef # * cookbook_hash is stored in run_context module PolicyBuilder - def self.strategy - if Chef::Config[:use_policyfile] - Policyfile - else - ExpandNodeObject - end - end - end end diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb index e976ddd87f..d2849dd4f1 100644 --- a/lib/chef/policy_builder/dynamic.rb +++ b/lib/chef/policy_builder/dynamic.rb @@ -21,6 +21,7 @@ require 'chef/rest' require 'chef/run_context' require 'chef/config' require 'chef/node' +require 'chef/exceptions' class Chef module PolicyBuilder @@ -56,7 +57,12 @@ class Chef events.node_load_start(node_name, config) Chef::Log.debug("Building node object for #{node_name}") - node = Chef::Node.find_or_create(node_name) + @node = + if Chef::Config[:solo] + Chef::Node.build(node_name) + else + Chef::Node.find_or_create(node_name) + end select_implementation(node) implementation.finish_load_node(node) node @@ -102,7 +108,7 @@ class Chef ## Internal Public API ## def implementation - @implementation + @implementation or raise Exceptions::InvalidPolicybuilderCall, "#load_node must be called before other policy builder methods" end def select_implementation(node) diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb index 524bdd95b1..543d6a0a7b 100644 --- a/lib/chef/policy_builder/expand_node_object.rb +++ b/lib/chef/policy_builder/expand_node_object.rb @@ -93,26 +93,10 @@ class Chef run_context end - - # In client-server operation, loads the node state from the server. In - # chef-solo operation, builds a new node object. - def load_node - events.node_load_start(node_name, Chef::Config) - Chef::Log.debug("Building node object for #{node_name}") - - if Chef::Config[:solo] - @node = Chef::Node.build(node_name) - else - @node = Chef::Node.find_or_create(node_name) - end - rescue Exception => e - # TODO: wrap this exception so useful error info can be given to the - # user. - events.node_load_failed(node_name, e, Chef::Config) - raise + def finish_load_node(node) + @node = node end - # Applies environment, external JSON attributes, and override run list to # the node, Then expands the run_list. # diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb index 5991e3ce10..5bcdd5f52f 100644 --- a/lib/chef/policy_builder/policyfile.rb +++ b/lib/chef/policy_builder/policyfile.rb @@ -112,18 +112,10 @@ class Chef ## PolicyBuilder API ## - # Loads the node state from the server. - def load_node - events.node_load_start(node_name, Chef::Config) - Chef::Log.debug("Building node object for #{node_name}") - - @node = Chef::Node.find_or_create(node_name) + def finish_load_node(node) + @node = node validate_policyfile events.policyfile_loaded(policy) - node - rescue Exception => e - events.node_load_failed(node_name, e, Chef::Config) - raise end # Applies environment, external JSON attributes, and override run list to diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index 8146774764..f736c38859 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -364,6 +364,8 @@ describe Chef::Client do expect(node[:expanded_run_list]).to be_nil allow(client.policy_builder).to receive(:node).and_return(node) + client.policy_builder.select_implementation(node) + allow(client.policy_builder.implementation).to receive(:node).and_return(node) # chefspec and possibly others use the return value of this method expect(client.build_node).to eq(node) @@ -391,6 +393,8 @@ describe Chef::Client do expect(mock_chef_rest).to receive(:get_rest).with("environments/A").and_return(test_env) expect(Chef::REST).to receive(:new).and_return(mock_chef_rest) allow(client.policy_builder).to receive(:node).and_return(node) + client.policy_builder.select_implementation(node) + allow(client.policy_builder.implementation).to receive(:node).and_return(node) expect(client.build_node).to eq(node) expect(node.chef_environment).to eq("A") diff --git a/spec/unit/policy_builder/dynamic_spec.rb b/spec/unit/policy_builder/dynamic_spec.rb index 74a0272f42..105a34390e 100644 --- a/spec/unit/policy_builder/dynamic_spec.rb +++ b/spec/unit/policy_builder/dynamic_spec.rb @@ -190,15 +190,66 @@ describe Chef::PolicyBuilder::Dynamic do before do allow(policy_builder).to receive(:implementation).and_return(implementation) + end + + context "when not running chef solo" do + + + context "when successful" do + + before do + expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) + expect(policy_builder).to receive(:select_implementation).with(node) + expect(implementation).to receive(:finish_load_node).with(node) + end + + it "selects the backend implementation and continues node loading" do + policy_builder.load_node + end + + end + + context "when an error occurs finding the node" do + + before do + expect(Chef::Node).to receive(:find_or_create).with(node_name).and_raise("oops") + end + + it "sends a node_load_failed event and re-raises" do + expect(events).to receive(:node_load_failed) + expect { policy_builder.load_node }.to raise_error("oops") + end + + end + + context "when an error occurs in the implementation's finish_load_node call" do + + before do + expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) + expect(policy_builder).to receive(:select_implementation).with(node) + expect(implementation).to receive(:finish_load_node).and_raise("oops") + end + + + it "sends a node_load_failed event and re-raises" do + expect(events).to receive(:node_load_failed) + expect { policy_builder.load_node }.to raise_error("oops") + end + + end - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - expect(policy_builder).to receive(:select_implementation).with(node) - expect(implementation).to receive(:finish_load_node).with(node) end - context "when successful" do + context "when running chef solo" do + + before do + Chef::Config[:solo] = true + expect(Chef::Node).to receive(:build).with(node_name).and_return(node) + expect(policy_builder).to receive(:select_implementation).with(node) + expect(implementation).to receive(:finish_load_node).with(node) + end - it "selects the backend implementation and continues node loading", :pending do + it "selects the backend implementation and continues node loading" do policy_builder.load_node end diff --git a/spec/unit/policy_builder/expand_node_object_spec.rb b/spec/unit/policy_builder/expand_node_object_spec.rb index 8e9fdc305e..815926ffb7 100644 --- a/spec/unit/policy_builder/expand_node_object_spec.rb +++ b/spec/unit/policy_builder/expand_node_object_spec.rb @@ -34,8 +34,8 @@ describe Chef::PolicyBuilder::ExpandNodeObject do expect(policy_builder).to respond_to(:node) end - it "implements a load_node method" do - expect(policy_builder).to respond_to(:load_node) + it "implements a finish_load_node method" do + expect(policy_builder).to respond_to(:finish_load_node) end it "implements a build_node method" do @@ -63,39 +63,13 @@ describe Chef::PolicyBuilder::ExpandNodeObject do expect(policy_builder).to respond_to(:temporary_policy?) end - describe "loading the node" do + describe "finishing loading the node" do - context "on chef-solo" do - - before do - Chef::Config[:solo] = true - end - - it "creates a new in-memory node object with the given name" do - policy_builder.load_node - expect(policy_builder.node.name).to eq(node_name) - end + let(:node) { Chef::Node.new.tap { |n| n.name(node_name) } } - end - - context "on chef-client" do - - let(:node) { Chef::Node.new.tap { |n| n.name(node_name) } } - - it "loads or creates a node on the server" do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - policy_builder.load_node - expect(policy_builder.node).to eq(node) - end - - end - end - - describe "building the node" do - - # XXX: Chef::Client just needs to be able to call this, it doesn't depend on the return value. - it "builds the node and returns the updated node object" do - skip + it "stores the node" do + policy_builder.finish_load_node(node) + expect(policy_builder.node).to eq(node) end end @@ -133,8 +107,7 @@ describe Chef::PolicyBuilder::ExpandNodeObject do end before do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - policy_builder.load_node + policy_builder.finish_load_node(node) end it "expands the run_list" do @@ -167,8 +140,7 @@ describe Chef::PolicyBuilder::ExpandNodeObject do before do Chef::Config[:environment] = configured_environment - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - policy_builder.load_node + policy_builder.finish_load_node(node) policy_builder.build_node end @@ -302,11 +274,9 @@ describe Chef::PolicyBuilder::ExpandNodeObject do let(:cookbook_synchronizer) { double("CookbookSynchronizer") } before do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - allow(policy_builder).to receive(:api_service).and_return(chef_http) - policy_builder.load_node + policy_builder.finish_load_node(node) policy_builder.build_node run_list_expansion = policy_builder.run_list_expansion diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb index 5fa00d8f2b..82ca1a2bb3 100644 --- a/spec/unit/policy_builder/policyfile_spec.rb +++ b/spec/unit/policy_builder/policyfile_spec.rb @@ -181,19 +181,13 @@ describe Chef::PolicyBuilder::Policyfile do let(:error404) { Net::HTTPServerException.new("404 message", :body) } before do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) expect(http_api).to receive(:get). with("data/policyfiles/example-policy-stage"). and_raise(error404) end it "raises an error" do - expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError) - end - - it "sends error message to the event system" do - expect(events).to receive(:node_load_failed).with(node_name, an_instance_of(err_namespace::ConfigurationError), Chef::Config) - expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError) + expect { policy_builder.finish_load_node(node) }.to raise_error(err_namespace::ConfigurationError) end end @@ -201,20 +195,12 @@ describe Chef::PolicyBuilder::Policyfile do context "when the deployment_group is not configured" do before do Chef::Config[:deployment_group] = nil - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) end it "errors while loading the node" do - expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError) + expect { policy_builder.finish_load_node(node) }.to raise_error(err_namespace::ConfigurationError) end - - it "passes error information to the event system" do - # TODO: also make sure something acceptable happens with the error formatters - err_class = err_namespace::ConfigurationError - expect(events).to receive(:node_load_failed).with(node_name, an_instance_of(err_class), Chef::Config) - expect { policy_builder.load_node }.to raise_error(err_class) - end end context "when deployment_group is correctly configured" do @@ -307,8 +293,7 @@ describe Chef::PolicyBuilder::Policyfile do end it "implements #expand_run_list in a manner compatible with ExpandNodeObject" do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - policy_builder.load_node + policy_builder.finish_load_node(node) expect(policy_builder.expand_run_list).to respond_to(:recipes) expect(policy_builder.expand_run_list.recipes).to eq(["example1::default", "example2::server"]) expect(policy_builder.expand_run_list.roles).to eq([]) @@ -346,9 +331,7 @@ describe Chef::PolicyBuilder::Policyfile do describe "building the node object" do before do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - - policy_builder.load_node + policy_builder.finish_load_node(node) policy_builder.build_node end @@ -414,9 +397,7 @@ describe Chef::PolicyBuilder::Policyfile do let(:error404) { Net::HTTPServerException.new("404 message", :body) } before do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - - policy_builder.load_node + policy_builder.finish_load_node(node) policy_builder.build_node expect(http_api).to receive(:get).with(cookbook1_url). @@ -433,9 +414,7 @@ describe Chef::PolicyBuilder::Policyfile do shared_examples_for "fetching cookbooks when they exist" do context "and the cookbooks can be fetched" do before do - expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) - - policy_builder.load_node + policy_builder.finish_load_node(node) policy_builder.build_node allow(Chef::CookbookSynchronizer).to receive(:new). -- cgit v1.2.1 From 3d7ec975024e85f24ef8e4782a00eeb178d379e8 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Mon, 14 Sep 2015 11:29:32 -0700 Subject: Add policyfile attrs to node JSON when present --- lib/chef/node.rb | 8 ++++++++ spec/unit/node_spec.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 65ed21a442..34a1edf835 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -510,6 +510,14 @@ class Chef #Render correctly for run_list items so malformed json does not result "run_list" => @primary_runlist.run_list.map { |item| item.to_s } } + # Chef Server rejects node JSON with extra keys; prior to 12.3, + # "policy_name" and "policy_group" are unknown; after 12.3 they are + # optional, therefore only including them in the JSON if present + # maximizes compatibility for most people. + unless policy_group.nil? && policy_name.nil? + result["policy_name"] = policy_name + result["policy_group"] = policy_group + end result end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 39a4e829c9..fcf5f56655 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -1185,6 +1185,35 @@ describe Chef::Node do expect(serialized_node.run_list).to eq(node.run_list) end + context "when policyfile attributes are not present" do + + it "does not have a policy_name key in the json" do + expect(node.for_json.keys).to_not include("policy_name") + end + + it "does not have a policy_group key in the json" do + expect(node.for_json.keys).to_not include("policy_name") + end + end + + context "when policyfile attributes are present" do + + before do + node.policy_name = "my-application" + node.policy_group = "staging" + end + + it "includes policy_name key in the json" do + expect(node.for_json).to have_key("policy_name") + expect(node.for_json["policy_name"]).to eq("my-application") + end + + it "includes a policy_group key in the json" do + expect(node.for_json).to have_key("policy_group") + expect(node.for_json["policy_group"]).to eq("staging") + end + end + include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) @@ -1380,6 +1409,7 @@ describe Chef::Node do node.save end end + end end -- cgit v1.2.1 From 2890a01b06d7b341d8483f946eee788063baecc9 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Mon, 14 Sep 2015 16:13:16 -0700 Subject: Add policyfile attrs to node create/save --- lib/chef/node.rb | 37 +++++++++++++++++- spec/unit/node_spec.rb | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 34a1edf835..3c3286c548 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -610,8 +610,16 @@ class Chef chef_server_rest.put_rest("nodes/#{name}", data_for_save) end rescue Net::HTTPServerException => e - raise e unless e.response.code == "404" - chef_server_rest.post_rest("nodes", data_for_save) + if e.response.code == "404" + chef_server_rest.post_rest("nodes", data_for_save) + # Chef Server before 12.3 rejects node JSON with 'policy_name' or + # 'policy_group' keys, but 'policy_name' will be detected first. + # Backcompat can be removed in 13.0 + elsif e.response.code == "400" && e.response.body.include?("Invalid key policy_name") + save_without_policyfile_attrs + else + raise + end end self end @@ -620,6 +628,15 @@ class Chef def create chef_server_rest.post_rest("nodes", data_for_save) self + rescue Net::HTTPServerException => e + # Chef Server before 12.3 rejects node JSON with 'policy_name' or + # 'policy_group' keys, but 'policy_name' will be detected first. + # Backcompat can be removed in 13.0 + if e.response.code == "400" && e.response.body.include?("Invalid key policy_name") + chef_server_rest.post_rest("nodes", data_for_save_without_policyfile_attrs) + else + raise + end end def to_s @@ -632,6 +649,22 @@ class Chef private + def save_without_policyfile_attrs + trimmed_data = data_for_save_without_policyfile_attrs + + chef_server_rest.put_rest("nodes/#{name}", trimmed_data) + rescue Net::HTTPServerException => e + raise e unless e.response.code == "404" + chef_server_rest.post_rest("nodes", trimmed_data) + end + + def data_for_save_without_policyfile_attrs + data_for_save.tap do |trimmed_data| + trimmed_data.delete("policy_name") + trimmed_data.delete("policy_group") + end + end + def data_for_save data = for_json ["automatic", "default", "normal", "override"].each do |level| diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index fcf5f56655..91177233a8 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -1410,6 +1410,109 @@ describe Chef::Node do end end + context "when policyfile attributes are present" do + + before do + node.name("example-node") + node.policy_name = "my-application" + node.policy_group = "staging" + end + + context "and the server supports policyfile attributes in node JSON" do + + it "creates the object normally" do + expect(@rest).to receive(:post_rest).with("nodes", node.for_json) + node.create + end + + it "saves the node object normally" do + expect(@rest).to receive(:put_rest).with("nodes/example-node", node.for_json) + node.save + end + end + + # Chef Server before 12.3 + context "and the Chef Server does not support policyfile attributes in node JSON" do + + let(:response_body) { %q[{"error":["Invalid key policy_name in request body"]}] } + + let(:response) do + Net::HTTPResponse.send(:response_class, "400").new("1.0", "400", "Bad Request").tap do |r| + allow(r).to receive(:body).and_return(response_body) + end + end + + let(:http_exception) do + begin + response.error! + rescue => e + e + end + end + + let(:trimmed_node) do + node.for_json.tap do |j| + j.delete("policy_name") + j.delete("policy_group") + end + + end + + context "on Chef Client 13 and later" do + + # Though we normally attempt to provide compatibility with chef + # server one major version back, policyfiles were beta when we + # added the policyfile attributes to the node JSON, therefore + # policyfile users need to be on 12.3 minimum when upgrading Chef + # Client to 13+ + it "lets the 400 pass through", :chef_gte_13_only do + expect { node.save }.to raise_error(http_exception) + end + + end + + context "when the node exists" do + + it "falls back to saving without policyfile attributes" do + expect(@rest).to receive(:put_rest).with("nodes/example-node", node.for_json).and_raise(http_exception) + expect(@rest).to receive(:put_rest).with("nodes/example-node", trimmed_node).and_return(@node) + expect { node.save }.to_not raise_error + end + + end + + context "when the node doesn't exist" do + + let(:response_404) do + Net::HTTPResponse.send(:response_class, "404").new("1.0", "404", "Not Found") + end + + let(:http_exception_404) do + begin + response_404.error! + rescue => e + e + end + end + + it "falls back to saving without policyfile attributes" do + expect(@rest).to receive(:put_rest).with("nodes/example-node", node.for_json).and_raise(http_exception) + expect(@rest).to receive(:put_rest).with("nodes/example-node", trimmed_node).and_raise(http_exception_404) + expect(@rest).to receive(:post_rest).with("nodes", trimmed_node).and_return(@node) + node.save + end + + it "creates the node without policyfile attributes" do + expect(@rest).to receive(:post_rest).with("nodes", node.for_json).and_raise(http_exception) + expect(@rest).to receive(:post_rest).with("nodes", trimmed_node).and_return(@node) + node.create + end + end + + end + + end + end end -- cgit v1.2.1 From e441d37d36eedbd73b37166f982536b8702ff4fb Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Mon, 14 Sep 2015 17:25:56 -0700 Subject: Set the precedence of policyfile attrs and propagate them everywhere --- lib/chef/policy_builder/policyfile.rb | 43 ++++++++ spec/unit/policy_builder/policyfile_spec.rb | 158 ++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb index 5bcdd5f52f..ef3b7838cb 100644 --- a/lib/chef/policy_builder/policyfile.rb +++ b/lib/chef/policy_builder/policyfile.rb @@ -114,6 +114,7 @@ class Chef def finish_load_node(node) @node = node + select_policy_name_and_group validate_policyfile events.policyfile_loaded(policy) end @@ -295,6 +296,48 @@ class Chef Chef::Config[:policy_name] end + def select_policy_name_and_group + policy_name_to_set = + policy_name_from_json_attribs || + policy_name_from_config || + policy_name_from_node + + policy_group_to_set = + policy_group_from_json_attribs || + policy_group_from_config || + policy_group_from_node + + node.policy_name = policy_name_to_set + node.policy_group = policy_group_to_set + + Chef::Config[:policy_name] = policy_name_to_set + Chef::Config[:policy_group] = policy_group_to_set + end + + def policy_group_from_json_attribs + json_attribs["policy_group"] + end + + def policy_name_from_json_attribs + json_attribs["policy_name"] + end + + def policy_group_from_config + Chef::Config[:policy_group] + end + + def policy_name_from_config + Chef::Config[:policy_name] + end + + def policy_group_from_node + node.policy_group + end + + def policy_name_from_node + node.policy_name + end + # Builds a 'cookbook_hash' map of the form # "COOKBOOK_NAME" => "IDENTIFIER" # diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb index 82ca1a2bb3..da14222339 100644 --- a/spec/unit/policy_builder/policyfile_spec.rb +++ b/spec/unit/policy_builder/policyfile_spec.rb @@ -330,11 +330,169 @@ describe Chef::PolicyBuilder::Policyfile do describe "building the node object" do + let(:extra_chef_config) { {} } + before do + # must be set before #build_node is called to have the proper effect + extra_chef_config.each do |key, value| + Chef::Config[key] = value + end + policy_builder.finish_load_node(node) policy_builder.build_node end + # it sets policy_name and policy_group in the following priority order: + # -j JSON > config file > node object + + describe "selecting policy_name and policy_group from the various sources" do + + context "when only set in node JSON" do + + let(:json_attribs) do + { + "policy_name" => "policy_name_from_node_json", + "policy_group" => "policy_group_from_node_json" + } + end + + it "sets policy_name and policy_group on Chef::Config" do + expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json") + expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json") + end + + it "sets policy_name and policy_group on the node object" do + expect(node.policy_name).to eq("policy_name_from_node_json") + expect(node.policy_group).to eq("policy_group_from_node_json") + end + + end + + context "when only set in Chef::Config" do + + let(:extra_chef_config) do + { + policy_name: "policy_name_from_config", + policy_group: "policy_group_from_config" + } + end + + it "sets policy_name and policy_group on the node object" do + expect(node.policy_name).to eq("policy_name_from_config") + expect(node.policy_group).to eq("policy_group_from_config") + end + + end + + context "when only set on the node" do + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.policy_name = "policy_name_from_node" + node.policy_group = "policy_group_from_node" + node + end + + it "sets policy_name and policy_group on Chef::Config" do + expect(Chef::Config[:policy_name]).to eq("policy_name_from_node") + expect(Chef::Config[:policy_group]).to eq("policy_group_from_node") + end + + end + + context "when set in Chef::Config and the fetched node" do + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.policy_name = "policy_name_from_node" + node.policy_group = "policy_group_from_node" + node + end + + let(:extra_chef_config) do + { + policy_name: "policy_name_from_config", + policy_group: "policy_group_from_config" + } + end + + it "prefers the policy_name and policy_group from Chef::Config" do + expect(node.policy_name).to eq("policy_name_from_config") + expect(node.policy_group).to eq("policy_group_from_config") + end + + end + + context "when set in node json and the fetched node" do + + let(:json_attribs) do + { + "policy_name" => "policy_name_from_node_json", + "policy_group" => "policy_group_from_node_json" + } + end + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.policy_name = "policy_name_from_node" + node.policy_group = "policy_group_from_node" + node + end + + + it "prefers the policy_name and policy_group from the node json" do + expect(policy_builder.policy_name).to eq("policy_name_from_node_json") + expect(policy_builder.policy_group).to eq("policy_group_from_node_json") + + expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json") + expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json") + expect(node.policy_name).to eq("policy_name_from_node_json") + expect(node.policy_group).to eq("policy_group_from_node_json") + end + + end + + context "when set in all sources" do + + let(:json_attribs) do + { + "policy_name" => "policy_name_from_node_json", + "policy_group" => "policy_group_from_node_json" + } + end + + let(:node) do + node = Chef::Node.new + node.name(node_name) + node.policy_name = "policy_name_from_node" + node.policy_group = "policy_group_from_node" + node + end + + let(:extra_chef_config) do + { + policy_name: "policy_name_from_config", + policy_group: "policy_group_from_config" + } + end + + it "prefers the policy_name and group from node json" do + expect(policy_builder.policy_name).to eq("policy_name_from_node_json") + expect(policy_builder.policy_group).to eq("policy_group_from_node_json") + + expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json") + expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json") + expect(node.policy_name).to eq("policy_name_from_node_json") + expect(node.policy_group).to eq("policy_group_from_node_json") + end + + end + + end + it "resets default and override data" do expect(node["default_key"]).to be_nil expect(node["override_key"]).to be_nil -- cgit v1.2.1 From f21715037c1fc9956738ba069760f3c7615987ce Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Mon, 14 Sep 2015 19:15:41 -0700 Subject: Populate node policyfile attrs from JSON --- lib/chef/node.rb | 4 ++++ spec/unit/node_spec.rb | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 3c3286c548..668ddbdc35 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -549,6 +549,10 @@ class Chef else o["recipes"].each { |r| node.recipes << r } end + + node.policy_name = o["policy_name"] if o.has_key?("policy_name") + node.policy_group = o["policy_group"] if o.has_key?("policy_group") + node end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 91177233a8..5f3bed2833 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -1212,6 +1212,14 @@ describe Chef::Node do expect(node.for_json).to have_key("policy_group") expect(node.for_json["policy_group"]).to eq("staging") end + + it "parses policyfile attributes from JSON" do + round_tripped_node = Chef::Node.json_create(node.for_json) + + expect(round_tripped_node.policy_name).to eq("my-application") + expect(round_tripped_node.policy_group).to eq("staging") + end + end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do -- cgit v1.2.1 From 3ca6231a7908e2c54c679c187a46ae10dfe21396 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Tue, 15 Sep 2015 09:57:32 -0700 Subject: Show policyfile attributes in node presenter --- lib/chef/knife/core/node_presenter.rb | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/chef/knife/core/node_presenter.rb b/lib/chef/knife/core/node_presenter.rb index d1aab592ef..d9ea8c7669 100644 --- a/lib/chef/knife/core/node_presenter.rb +++ b/lib/chef/knife/core/node_presenter.rb @@ -67,7 +67,12 @@ class Chef result = {} result["name"] = node.name - result["chef_environment"] = node.chef_environment + if node.policy_name.nil? && node.policy_group.nil? + result["chef_environment"] = node.chef_environment + else + result["policy_name"] = node.policy_name + result["policy_group"] = node.policy_group + end result["run_list"] = node.run_list result["normal"] = node.normal_attrs @@ -95,11 +100,29 @@ class Chef summarized=<<-SUMMARY #{ui.color('Node Name:', :bold)} #{ui.color(node.name, :bold)} +SUMMARY + show_policy = !(node.policy_name.nil? && node.policy_group.nil?) + if show_policy + summarized << <<-POLICY +#{key('Policy Name:')} #{node.policy_name} +#{key('Policy Group:')} #{node.policy_group} +POLICY + else + summarized << <<-ENV #{key('Environment:')} #{node.chef_environment} +ENV + end + summarized << <<-SUMMARY #{key('FQDN:')} #{node[:fqdn]} #{key('IP:')} #{ip} #{key('Run List:')} #{node.run_list} +SUMMARY + unless show_policy + summarized << <<-ROLES #{key('Roles:')} #{Array(node[:roles]).join(', ')} +ROLES + end + summarized << <<-SUMMARY #{key('Recipes:')} #{Array(node[:recipes]).join(', ')} #{key('Platform:')} #{node[:platform]} #{node[:platform_version]} #{key('Tags:')} #{Array(node[:tags]).join(', ')} -- cgit v1.2.1 From 460a7ec5347891d6615b7f88944cbd76998769b1 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Tue, 15 Sep 2015 11:19:00 -0700 Subject: Add default policyfile settings to config --- chef-config/lib/chef-config/config.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb index a3f06e9b23..76ee589ff9 100644 --- a/chef-config/lib/chef-config/config.rb +++ b/chef-config/lib/chef-config/config.rb @@ -339,10 +339,27 @@ module ChefConfig # most of our testing scenarios) default :minimal_ohai, false + ### + # Policyfile Settings + # # Policyfile is a feature where a node gets its run list and cookbook # version set from a single document on the server instead of expanding the # run list and having the server compute the cookbook version set based on # environment constraints. + # + # Policyfiles are auto-versioned. The user groups nodes by `policy_name`, + # which generally describes a hosts's functional role, and `policy_group`, + # which generally groups nodes by deployment phase (a.k.a., "environment"). + # The Chef Server maps a given set of `policy_name` plus `policy_group` to + # a particular revision of a policy. + + default :policy_name, nil + default :policy_group, nil + + # During initial development, users were required to set `use_policyfile true` + # in `client.rb` to opt-in to policyfile use. Chef Client now examines + # configuration, node json, and the stored node to determine if policyfile + # usage is desired. This flag is still honored if set, but is unnecessary. default :use_policyfile, false # Policyfiles can be used in a native mode (default) or compatibility mode. @@ -356,6 +373,16 @@ module ChefConfig # policyfiles with servers that don't yet support the native endpoints. default :policy_document_native_api, true + # When policyfiles are used in compatibility mode, `policy_name` and + # `policy_group` are instead specified using a combined configuration + # setting, `deployment_group`. For example, if policy_name should be + # "webserver" and policy_group should be "staging", then `deployment_group` + # should be set to "webserver-staging", which is the name of the data bag + # item that the policy will be stored as. NOTE: this setting only has an + # effect if `policy_document_native_api` is set to `false`. + default :deployment_group, nil + + # Set these to enable SSL authentication / mutual-authentication # with the server -- cgit v1.2.1 From 90075c9d83c2df2916fd1a2d441401e1c7cf5362 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Tue, 15 Sep 2015 11:23:06 -0700 Subject: Detect when user wants policyfile compat mode --- lib/chef/policy_builder/dynamic.rb | 9 ++++++++- spec/unit/policy_builder/dynamic_spec.rb | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb index d2849dd4f1..0f266a25fb 100644 --- a/lib/chef/policy_builder/dynamic.rb +++ b/lib/chef/policy_builder/dynamic.rb @@ -112,7 +112,10 @@ class Chef end def select_implementation(node) - if policyfile_set_in_config? || policyfile_attribs_in_node_json? || node_has_policyfile_attrs?(node) + if policyfile_set_in_config? || + policyfile_attribs_in_node_json? || + node_has_policyfile_attrs?(node) || + policyfile_compat_mode_config? @implementation = Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events) else @implementation = ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events) @@ -137,6 +140,10 @@ class Chef config[:use_policyfile] || config[:policy_name] || config[:policy_group] end + def policyfile_compat_mode_config? + config[:deployment_group] && !config[:policy_document_native_api] + end + end end end diff --git a/spec/unit/policy_builder/dynamic_spec.rb b/spec/unit/policy_builder/dynamic_spec.rb index 105a34390e..aff19f4d11 100644 --- a/spec/unit/policy_builder/dynamic_spec.rb +++ b/spec/unit/policy_builder/dynamic_spec.rb @@ -156,6 +156,19 @@ describe Chef::PolicyBuilder::Dynamic do end + context "and deployment_group and policy_document_native_api are set on Chef::Config" do + + before do + Chef::Config[:deployment_group] = "example-policy-staging" + Chef::Config[:policy_document_native_api] = false + end + + it "uses the Policyfile implementation" do + expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) + end + + end + context "and policyfile attributes are present in json_attribs" do let(:json_attribs) { {"policy_name" => "example-policy", "policy_group" => "testing"} } -- cgit v1.2.1 From 41372f5a6b5608625a6d89953d24c60373d4c39b Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Tue, 15 Sep 2015 11:28:35 -0700 Subject: Add policyfile attrs to knife "fuzzy" search --- lib/chef/knife/search.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb index 9319d30e7d..2b0c79ff6c 100644 --- a/lib/chef/knife/search.rb +++ b/lib/chef/knife/search.rb @@ -160,7 +160,7 @@ class Chef def fuzzify_query if @query !~ /:/ - @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*" + @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}* OR policy_name:*#{@query}* OR policy_group:*#{@query}*" end end -- cgit v1.2.1 From 6c01a178e1c701f2e4c5f85d0c9fa93431bb5b69 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Wed, 16 Sep 2015 16:53:25 -0700 Subject: Use Forwardable to delegate in PolicyBuilder::Dynamic --- lib/chef/policy_builder/dynamic.rb | 45 +++++++++++--------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb index 0f266a25fb..deefd1e855 100644 --- a/lib/chef/policy_builder/dynamic.rb +++ b/lib/chef/policy_builder/dynamic.rb @@ -16,6 +16,8 @@ # limitations under the License. # +require 'forwardable' + require 'chef/log' require 'chef/rest' require 'chef/run_context' @@ -30,6 +32,8 @@ class Chef # implementation based on the content of the node object. class Dynamic + extend Forwardable + attr_reader :node attr_reader :node_name attr_reader :ohai_data @@ -71,39 +75,16 @@ class Chef raise end - ## Delegated Methods ## + ## Delegated Public API Methods ## - def original_runlist - implementation.original_runlist - end - - def run_context - implementation.run_context - end - - def run_list_expansion - implementation.run_list_expansion - end - - def build_node - implementation.build_node - end - - def setup_run_context(specific_recipes=nil) - implementation.setup_run_context(specific_recipes) - end - - def expand_run_list - implementation.expand_run_list - end - - def sync_cookbooks - implementation.sync_cookbooks - end - - def temporary_policy? - implementation.temporary_policy? - end + def_delegator :implementation, :original_runlist + def_delegator :implementation, :run_context + def_delegator :implementation, :run_list_expansion + def_delegator :implementation, :build_node + def_delegator :implementation, :setup_run_context + def_delegator :implementation, :expand_run_list + def_delegator :implementation, :sync_cookbooks + def_delegator :implementation, :temporary_policy? ## Internal Public API ## -- cgit v1.2.1 From d2f5ff985403d32fd452b82a3dab1f09ccf4fae4 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 17 Sep 2015 09:15:48 -0700 Subject: Document policy builder API on the dynamic builder --- lib/chef/policy_builder/dynamic.rb | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb index deefd1e855..c9842ba532 100644 --- a/lib/chef/policy_builder/dynamic.rb +++ b/lib/chef/policy_builder/dynamic.rb @@ -57,6 +57,11 @@ class Chef # Loads the node state from the server, then picks the correct # implementation class based on the node and json_attribs. + # + # Calls #finish_load_node on the implementation object to complete the + # loading process. All subsequent lifecycle calls are delegated. + # + # @return [Chef::Node] the loaded node. def load_node events.node_load_start(node_name, config) Chef::Log.debug("Building node object for #{node_name}") @@ -77,21 +82,72 @@ class Chef ## Delegated Public API Methods ## + ### Accessors ### + def_delegator :implementation, :original_runlist def_delegator :implementation, :run_context def_delegator :implementation, :run_list_expansion + + ### Lifecycle Methods ### + + # @!method build_node + # + # Applies external attributes (e.g., from JSON file, environment, + # policyfile, etc.) and determines the correct expanded run list for the + # run. + # + # @return [Chef::Node] def_delegator :implementation, :build_node + + # @!method setup_run_context + # + # Synchronizes cookbooks and initializes the run context object for the + # run. + # + # @return [Chef::RunContext] def_delegator :implementation, :setup_run_context + + # @!method expanded_run_list + # + # Resolves the run list to a form containing only recipes and sets the + # `roles` and `recipes` automatic attributes on the node. + # + # @return [#recipes, #roles] A RunListExpansion or duck-type. def_delegator :implementation, :expand_run_list + + # @!method sync_cookbooks + # + # Synchronizes cookbooks. In a normal chef-client run, this is handled by + # #setup_run_context, but may be called directly in some circumstances. + # + # @return [Hash{String => Chef::CookbookManifest}] A map of + # CookbookManifest objects by cookbook name. def_delegator :implementation, :sync_cookbooks + + # @!method temporary_policy? + # + # Indicates whether the policy is temporary, which means an + # override_runlist was provided. Chef::Client uses this to decide whether + # to do the final node save at the end of the run or not. + # + # @return [true,false] def_delegator :implementation, :temporary_policy? ## Internal Public API ## + # Returns the selected implementation, or raises if not set. The + # implementation is set when #load_node is called. + # + # @return [PolicyBuilder::Policyfile, PolicyBuilder::ExpandNodeObject] def implementation @implementation or raise Exceptions::InvalidPolicybuilderCall, "#load_node must be called before other policy builder methods" end + # @api private + # + # Sets the implementation based on the content of the node, node JSON + # (i.e., the `-j JSON_FILE` data), and config. This is only public for + # testing purposes; production code should call #load_node instead. def select_implementation(node) if policyfile_set_in_config? || policyfile_attribs_in_node_json? || -- cgit v1.2.1 From 6883743831ff798d3d704ee62c78796ff9b4df16 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 17 Sep 2015 09:26:32 -0700 Subject: Update code comment to reflect reality --- lib/chef/policy_builder/expand_node_object.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb index 543d6a0a7b..caedcb4035 100644 --- a/lib/chef/policy_builder/expand_node_object.rb +++ b/lib/chef/policy_builder/expand_node_object.rb @@ -55,9 +55,7 @@ class Chef @run_list_expansion = nil end - # This method injects the run_context and provider and resource priority - # maps into the Chef class. The run_context has to be injected here, the provider and - # resource maps could be moved if a better place can be found to do this work. + # This method injects the run_context and into the Chef class. # # @param run_context [Chef::RunContext] the run_context to inject def setup_chef_class(run_context) -- cgit v1.2.1 From 2cadf9cd40e4c19741121bf2df180941918107bb Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 17 Sep 2015 09:48:31 -0700 Subject: Add `setup_chef_class` to Policyfile policy builder Also, mark internal public API as @private --- lib/chef/policy_builder/expand_node_object.rb | 3 ++ lib/chef/policy_builder/policyfile.rb | 72 ++++++++++++++++++++++++++- spec/unit/policy_builder/policyfile_spec.rb | 13 +++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb index caedcb4035..26b98afa52 100644 --- a/lib/chef/policy_builder/expand_node_object.rb +++ b/lib/chef/policy_builder/expand_node_object.rb @@ -57,6 +57,9 @@ class Chef # This method injects the run_context and into the Chef class. # + # NOTE: This is duplicated with the Policyfile implementation. If + # it gets any more complicated, it needs to be moved elsewhere. + # # @param run_context [Chef::RunContext] the run_context to inject def setup_chef_class(run_context) Chef.set_run_context(run_context) diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb index ef3b7838cb..69ef485e14 100644 --- a/lib/chef/policy_builder/policyfile.rb +++ b/lib/chef/policy_builder/policyfile.rb @@ -147,17 +147,28 @@ class Chef raise end + # Synchronizes cookbooks and initializes the run context object for the + # run. + # + # @return [Chef::RunContext] def setup_run_context(specific_recipes=nil) Chef::Cookbook::FileVendor.fetch_from_remote(http_api) sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync) run_context = Chef::RunContext.new(node, cookbook_collection, events) + setup_chef_class(run_context) + run_context.load(run_list_expansion_ish) + setup_chef_class(run_context) run_context end + # Sets `run_list` on the node from the policy, sets `roles` and `recipes` + # attributes on the node accordingly. + # + # @return [RunListExpansionIsh] A RunListExpansion duck-type. def expand_run_list node.run_list(run_list) node.automatic_attrs[:roles] = [] @@ -165,7 +176,11 @@ class Chef run_list_expansion_ish end - + # Synchronizes cookbooks. In a normal chef-client run, this is handled by + # #setup_run_context, but may be called directly in some circumstances. + # + # @return [Hash{String => Chef::CookbookManifest}] A map of + # CookbookManifest objects by cookbook name. def sync_cookbooks Chef::Log.debug("Synchronizing cookbooks") synchronizer = Chef::CookbookSynchronizer.new(cookbooks_to_sync, events) @@ -179,12 +194,18 @@ class Chef # Whether or not this is a temporary policy. Since PolicyBuilder doesn't # support override_runlist, this is always false. + # + # @return [false] def temporary_policy? false end ## Internal Public API ## + # @api private + # + # Generates an array of strings with recipe names including version and + # identifier info. def run_list_with_versions_for_display run_list.map do |recipe_spec| cookbook, recipe = parse_recipe_spec(recipe_spec) @@ -194,6 +215,11 @@ class Chef end end + # @api private + # + # Sets up a RunListExpansionIsh object so that it can be used in place of + # a RunListExpansion object, to satisfy the API contract of + # #expand_run_list def run_list_expansion_ish recipes = run_list.map do |recipe_spec| cookbook, recipe = parse_recipe_spec(recipe_spec) @@ -202,11 +228,15 @@ class Chef RunListExpansionIsh.new(recipes, []) end + # @api private + # + # Sets attributes from the policyfile on the node, using the role priority. def apply_policyfile_attributes node.attributes.role_default = policy["default_attributes"] node.attributes.role_override = policy["override_attributes"] end + # @api private def parse_recipe_spec(recipe_spec) rmatch = recipe_spec.match(/recipe\[([^:]+)::([^:]+)\]/) if rmatch.nil? @@ -216,20 +246,24 @@ class Chef end end + # @api private def cookbook_lock_for(cookbook_name) cookbook_locks[cookbook_name] end + # @api private def run_list policy["run_list"] end + # @api private def policy @policy ||= http_api.get(policyfile_location) rescue Net::HTTPServerException => e raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}" end + # @api private def policyfile_location if Chef::Config[:policy_document_native_api] validate_policy_config! @@ -266,6 +300,7 @@ class Chef end end + # @api private def validate_recipe_spec(recipe_spec) parse_recipe_spec(recipe_spec) nil @@ -275,11 +310,13 @@ class Chef class ConfigurationError < StandardError; end + # @api private def deployment_group Chef::Config[:deployment_group] or raise ConfigurationError, "Setting `deployment_group` is not configured." end + # @api private def validate_policy_config! policy_group or raise ConfigurationError, "Setting `policy_group` is not configured." @@ -288,14 +325,26 @@ class Chef raise ConfigurationError, "Setting `policy_name` is not configured." end + # @api private def policy_group Chef::Config[:policy_group] end + # @api private def policy_name Chef::Config[:policy_name] end + # @api private + # + # Selects the `policy_name` and `policy_group` from the following sources + # in priority order: + # + # 1. JSON attribs (i.e., `-j JSON_FILE`) + # 2. `Chef::Config` + # 3. The node object + # + # The selected values are then copied to `Chef::Config` and the node. def select_policy_name_and_group policy_name_to_set = policy_name_from_json_attribs || @@ -314,30 +363,37 @@ class Chef Chef::Config[:policy_group] = policy_group_to_set end + # @api private def policy_group_from_json_attribs json_attribs["policy_group"] end + # @api private def policy_name_from_json_attribs json_attribs["policy_name"] end + # @api private def policy_group_from_config Chef::Config[:policy_group] end + # @api private def policy_name_from_config Chef::Config[:policy_name] end + # @api private def policy_group_from_node node.policy_group end + # @api private def policy_name_from_node node.policy_name end + # @api private # Builds a 'cookbook_hash' map of the form # "COOKBOOK_NAME" => "IDENTIFIER" # @@ -365,6 +421,7 @@ class Chef raise end + # @api private # Fetches the CookbookVersion object for the given name and identifer # specified in the lock_data. # TODO: This only implements Chef 11 compatibility mode, which means that @@ -378,20 +435,33 @@ class Chef end end + # @api private def cookbook_locks policy["cookbook_locks"] end + # @api private def http_api @api_service ||= Chef::REST.new(config[:chef_server_url]) end + # @api private def config Chef::Config end private + # This method injects the run_context and into the Chef class. + # + # NOTE: This is duplicated with the ExpandNodeObject implementation. If + # it gets any more complicated, it needs to be moved elsewhere. + # + # @param run_context [Chef::RunContext] the run_context to inject + def setup_chef_class(run_context) + Chef.set_run_context(run_context) + end + def compat_mode_manifest_for(cookbook_name, lock_data) xyz_version = lock_data["dotted_decimal_identifier"] rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}" diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb index da14222339..9a39161648 100644 --- a/spec/unit/policy_builder/policyfile_spec.rb +++ b/spec/unit/policy_builder/policyfile_spec.rb @@ -572,6 +572,8 @@ describe Chef::PolicyBuilder::Policyfile do shared_examples_for "fetching cookbooks when they exist" do context "and the cookbooks can be fetched" do before do + Chef.reset! + policy_builder.finish_load_node(node) policy_builder.build_node @@ -580,6 +582,10 @@ describe Chef::PolicyBuilder::Policyfile do and_return(cookbook_synchronizer) end + after do + Chef.reset! + end + it "builds a Hash of the form 'cookbook_name' => Chef::CookbookVersion" do expect(policy_builder.cookbooks_to_sync).to eq(expected_cookbook_hash) end @@ -597,6 +603,13 @@ describe Chef::PolicyBuilder::Policyfile do expect(run_context.cookbook_collection.keys).to match_array(["example1", "example2"]) end + it "makes the run context available via static method on Chef" do + expect(cookbook_synchronizer).to receive(:sync_cookbooks) + expect_any_instance_of(Chef::RunContext).to receive(:load).with(policy_builder.run_list_expansion_ish) + run_context = policy_builder.setup_run_context + expect(Chef.run_context).to eq(run_context) + end + end end # shared_examples_for "fetching cookbooks" -- cgit v1.2.1 From 9703782bad3ddfad90b21ca5a225d4bd2e393f0d Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Thu, 17 Sep 2015 09:47:08 -0700 Subject: Don't add win_evt logger when on nano. --- lib/chef/config.rb | 3 +- lib/chef/platform/query_helpers.rb | 24 +++++++++- spec/unit/platform/query_helpers_spec.rb | 75 +++++++++++++++++++++++++++++--- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 6382af14c2..a43985f691 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -55,7 +55,8 @@ class Chef default :event_loggers do evt_loggers = [] - if ChefConfig.windows? and not Chef::Platform.windows_server_2003? + if ChefConfig.windows? && !(Chef::Platform.windows_server_2003? || + Chef::Platform.windows_nano_server?) evt_loggers << :win_evt end evt_loggers diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index 2dd33ea190..9ba8d2261d 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -36,6 +36,28 @@ class Chef is_server_2003 end + def windows_nano_server? + return false unless windows? + require 'win32/registry' + + # This method may be called before ohai runs (e.g., it may be used to + # determine settings in config.rb). Chef::Win32::Registry.new uses + # node attributes to verify the machine architecture which aren't + # accessible before ohai runs. + nano = nil + key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels" + access = ::Win32::Registry::KEY_QUERY_VALUE | 0x0100 # nano is 64-bit only + begin + ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg| + nano = reg["NanoServer"] + end + rescue ::Win32::Registry::Error + # If accessing the registry key failed, then we're probably not on + # nano. Fail through. + end + return nano == 1 + end + def supports_powershell_execution_bypass?(node) node[:languages] && node[:languages][:powershell] && node[:languages][:powershell][:version].to_i >= 3 @@ -52,7 +74,7 @@ class Chef Gem::Version.new(node[:languages][:powershell][:version]) >= Gem::Version.new("5.0.10018.0") end - + def dsc_refresh_mode_disabled?(node) require 'chef/util/powershell/cmdlet' cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object) diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index f33bfa128b..2843f80c60 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -21,7 +21,7 @@ require 'spec_helper' describe "Chef::Platform#windows_server_2003?" do it "returns false early when not on windows" do allow(ChefConfig).to receive(:windows?).and_return(false) - expect(Chef::Platform).not_to receive(:require) + expect(Chef::Platform).not_to receive(:require) expect(Chef::Platform.windows_server_2003?).to be_falsey end @@ -31,7 +31,69 @@ describe "Chef::Platform#windows_server_2003?" do end end -describe 'Chef::Platform#supports_dsc?' do +describe "Chef::Platform#windows_nano_server?" do + let(:key) { "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels" } + let(:access) { 0x0001 | 0x0100 } + let(:hive) { double("Win32::Registry::HKEY_LOCAL_MACHINE") } + let(:registry) { double("Win32::Registry") } + + before do + Object.send(:remove_const, 'Win32') if defined?(Win32) + Win32 = Module.new + Win32::Registry = Class.new + Win32::Registry::Error = Class.new(RuntimeError) + Win32::Registry::HKEY_LOCAL_MACHINE = hive + Win32::Registry::KEY_QUERY_VALUE = 0x0001 + end + + it "returns false early when not on windows" do + allow(ChefConfig).to receive(:windows?).and_return(false) + expect(Chef::Platform).to_not receive(:require) + expect(Chef::Platform.windows_nano_server?).to be false + end + + it "returns true when the registry value is 1" do + allow(ChefConfig).to receive(:windows?).and_return(true) + allow(Chef::Platform).to receive(:require).with('win32/registry') + expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). + with(key, access). + and_yield(registry) + expect(registry).to receive(:[]).with("NanoServer").and_return(1) + expect(Chef::Platform.windows_nano_server?).to be true + end + + it "returns false when the registry value is not 1" do + allow(ChefConfig).to receive(:windows?).and_return(true) + allow(Chef::Platform).to receive(:require).with('win32/registry') + expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). + with(key, access). + and_yield(registry) + expect(registry).to receive(:[]).with("NanoServer").and_return(0) + expect(Chef::Platform.windows_nano_server?).to be false + end + + it "returns false when the registry value does not exist" do + allow(ChefConfig).to receive(:windows?).and_return(true) + allow(Chef::Platform).to receive(:require).with('win32/registry') + expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). + with(key, access). + and_yield(registry) + expect(registry).to receive(:[]).with("NanoServer"). + and_raise(Win32::Registry::Error, "The system cannot find the file specified.") + expect(Chef::Platform.windows_nano_server?).to be false + end + + it "returns false when the registry key does not exist" do + allow(ChefConfig).to receive(:windows?).and_return(true) + allow(Chef::Platform).to receive(:require).with('win32/registry') + expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). + with(key, access). + and_raise(Win32::Registry::Error, "The system cannot find the file specified.") + expect(Chef::Platform.windows_nano_server?).to be false + end +end + +describe 'Chef::Platform#supports_dsc?' do it 'returns false if powershell is not present' do node = Chef::Node.new expect(Chef::Platform.supports_dsc?(node)).to be_falsey @@ -54,7 +116,7 @@ describe 'Chef::Platform#supports_dsc?' do end end -describe 'Chef::Platform#supports_dsc_invoke_resource?' do +describe 'Chef::Platform#supports_dsc_invoke_resource?' do it 'returns false if powershell is not present' do node = Chef::Node.new expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey @@ -79,7 +141,7 @@ describe 'Chef::Platform#dsc_refresh_mode_disabled?' do let(:node) { instance_double('Chef::Node') } let(:cmdlet) { instance_double('Chef::Util::Powershell::Cmdlet') } let(:cmdlet_result) { instance_double('Chef::Util::Powershell::CmdletResult')} - + it "returns true when RefreshMode is Disabled" do expect(Chef::Util::Powershell::Cmdlet).to receive(:new). with(node, "Get-DscLocalConfigurationManager", :object). @@ -88,14 +150,13 @@ describe 'Chef::Platform#dsc_refresh_mode_disabled?' do expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'Disabled' }) expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be true end - + it "returns false when RefreshMode is not Disabled" do expect(Chef::Util::Powershell::Cmdlet).to receive(:new). with(node, "Get-DscLocalConfigurationManager", :object). and_return(cmdlet) expect(cmdlet).to receive(:run!).and_return(cmdlet_result) expect(cmdlet_result).to receive(:return_value).and_return({ 'RefreshMode' => 'LaLaLa' }) - expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be false + expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be false end end - -- cgit v1.2.1 From 246e49bf9c4a55c1984be5dca4ff8b08b1adca87 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Thu, 17 Sep 2015 13:41:11 -0700 Subject: Remove Win32 after tests --- spec/unit/platform/query_helpers_spec.rb | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 2843f80c60..7f1b3984d3 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -33,17 +33,38 @@ end describe "Chef::Platform#windows_nano_server?" do let(:key) { "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels" } - let(:access) { 0x0001 | 0x0100 } + let(:key_query_value) { 0x0001 } + let(:access) { key_query_value | 0x0100 } let(:hive) { double("Win32::Registry::HKEY_LOCAL_MACHINE") } let(:registry) { double("Win32::Registry") } - before do - Object.send(:remove_const, 'Win32') if defined?(Win32) + before(:all) do + @original_win32 = if defined?(Win32) + win32 = Object.send(:const_get, 'Win32') + Object.send(:remove_const, 'Win32') + win32 + else + nil + end Win32 = Module.new Win32::Registry = Class.new Win32::Registry::Error = Class.new(RuntimeError) + + end + + before do Win32::Registry::HKEY_LOCAL_MACHINE = hive - Win32::Registry::KEY_QUERY_VALUE = 0x0001 + Win32::Registry::KEY_QUERY_VALUE = key_query_value + end + + after do + Win32::Registry.send(:remove_const, 'HKEY_LOCAL_MACHINE') if defined?(Win32::Registry::HKEY_LOCAL_MACHINE) + Win32::Registry.send(:remove_const, 'KEY_QUERY_VALUE') if defined?(Win32::Registry::KEY_QUERY_VALUE) + end + + after(:all) do + Object.send(:remove_const, 'Win32') if defined?(Win32) + Object.send(:const_set, 'Win32', @original_win32) if @original_win32 end it "returns false early when not on windows" do -- cgit v1.2.1 From 3da8700c1c88a44488732bae47bcc5b1cb545ca2 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 18 Sep 2015 10:41:30 -0700 Subject: Remove dependency on master of ohai and friends --- pedant.gemfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pedant.gemfile b/pedant.gemfile index 888e6b83f3..3302bccfe1 100644 --- a/pedant.gemfile +++ b/pedant.gemfile @@ -1,14 +1,9 @@ source "https://rubygems.org" gemspec :name => "chef" -gem 'rest-client', :github => 'opscode/rest-client', :branch => 'lcg/1.6.7-version-lying' - # TODO figure out how to grab this stuff from the main Gemfile gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" -gem "mixlib-shellout", github: "opscode/mixlib-shellout", branch: "master" -gem "ohai", github: "opscode/ohai", branch: "master" - # We are pinning chef-zero to 4.2.x until ChefFS can deal # with V1 api calls or chef-zero supports both v0 and v1 gem "chef-zero", "~> 4.2.3" -- cgit v1.2.1 From 3bffbc968d4dc76cd6992c603f849cf46fd759dc Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Wed, 16 Sep 2015 16:17:00 -0700 Subject: Apply a named_run_list in policy builder via configuration --- lib/chef/policy_builder/policyfile.rb | 36 +++++++++- spec/unit/policy_builder/policyfile_spec.rb | 106 ++++++++++++++++++++++------ 2 files changed, 120 insertions(+), 22 deletions(-) diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb index 69ef485e14..bb9593eb36 100644 --- a/lib/chef/policy_builder/policyfile.rb +++ b/lib/chef/policy_builder/policyfile.rb @@ -170,6 +170,8 @@ class Chef # # @return [RunListExpansionIsh] A RunListExpansion duck-type. def expand_run_list + CookbookCacheCleaner.instance.skip_removal = true if named_run_list_requested? + node.run_list(run_list) node.automatic_attrs[:roles] = [] node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes @@ -253,7 +255,14 @@ class Chef # @api private def run_list - policy["run_list"] + if named_run_list_requested? + named_run_list or + raise ConfigurationError, + "Policy '#{retrieved_policy_name}' revision '#{revision_id}' does not have named_run_list '#{named_run_list_name}'" + + "(available named_run_lists: [#{available_named_run_lists.join(', ')}])" + else + policy["run_list"] + end end # @api private @@ -440,6 +449,11 @@ class Chef policy["cookbook_locks"] end + # @api private + def revision_id + policy["revision_id"] + end + # @api private def http_api @api_service ||= Chef::REST.new(config[:chef_server_url]) @@ -462,6 +476,26 @@ class Chef Chef.set_run_context(run_context) end + def retrieved_policy_name + policy["name"] + end + + def named_run_list + policy["named_run_lists"] && policy["named_run_lists"][named_run_list_name] + end + + def available_named_run_lists + (policy["named_run_lists"] || {}).keys + end + + def named_run_list_requested? + !!Chef::Config[:named_run_list] + end + + def named_run_list_name + Chef::Config[:named_run_list] + end + def compat_mode_manifest_for(cookbook_name, lock_data) xyz_version = lock_data["dotted_decimal_identifier"] rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}" diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb index 9a39161648..b656a66ec3 100644 --- a/spec/unit/policy_builder/policyfile_spec.rb +++ b/spec/unit/policy_builder/policyfile_spec.rb @@ -76,8 +76,11 @@ describe Chef::PolicyBuilder::Policyfile do let(:policyfile_run_list) { ["recipe[example1::default]", "recipe[example2::server]"] } - let(:parsed_policyfile_json) do + let(:basic_valid_policy_data) do { + "name" => "example-policy", + "revision_id" => "123abc", + "run_list" => policyfile_run_list, "cookbook_locks" => { @@ -90,6 +93,8 @@ describe Chef::PolicyBuilder::Policyfile do } end + let(:parsed_policyfile_json) { basic_valid_policy_data } + let(:err_namespace) { Chef::PolicyBuilder::Policyfile } it "configures a Chef HTTP API client" do @@ -498,34 +503,93 @@ describe Chef::PolicyBuilder::Policyfile do expect(node["override_key"]).to be_nil end - it "applies ohai data" do - expect(ohai_data).to_not be_empty # ensure test is testing something - ohai_data.each do |key, value| - expect(node.automatic_attrs[key]).to eq(value) + describe "setting attribute values" do + + before do + policy_builder.build_node end - end - it "applies attributes from json file" do - expect(node["custom_attr"]).to eq("custom_attr_value") - end + it "resets default and override data" do + expect(node["default_key"]).to be_nil + expect(node["override_key"]).to be_nil + end - it "applies attributes from the policyfile" do - expect(node["policyfile_default_attr"]).to eq("policyfile_default_value") - expect(node["policyfile_override_attr"]).to eq("policyfile_override_value") - end + it "applies ohai data" do + expect(ohai_data).to_not be_empty # ensure test is testing something + ohai_data.each do |key, value| + expect(node.automatic_attrs[key]).to eq(value) + end + end - it "sets the policyfile's run_list on the node object" do - expect(node.run_list).to eq(policyfile_run_list) - end + it "applies attributes from json file" do + expect(node["custom_attr"]).to eq("custom_attr_value") + end - it "creates node.automatic_attrs[:roles]" do - expect(node.automatic_attrs[:roles]).to eq([]) - end + it "applies attributes from the policyfile" do + expect(node["policyfile_default_attr"]).to eq("policyfile_default_value") + expect(node["policyfile_override_attr"]).to eq("policyfile_override_value") + end + + it "sets the policyfile's run_list on the node object" do + expect(node.run_list).to eq(policyfile_run_list) + end - it "create node.automatic_attrs[:recipes]" do - expect(node.automatic_attrs[:recipes]).to eq(["example1::default", "example2::server"]) + it "creates node.automatic_attrs[:roles]" do + expect(node.automatic_attrs[:roles]).to eq([]) + end + + it "create node.automatic_attrs[:recipes]" do + expect(node.automatic_attrs[:recipes]).to eq(["example1::default", "example2::server"]) + end end + context "when a named run_list is given" do + + before do + Chef::Config[:named_run_list] = "deploy-app" + end + + context "and the named run_list is not present in the policy" do + + it "raises a ConfigurationError" do + err_class = Chef::PolicyBuilder::Policyfile::ConfigurationError + err_text = "Policy 'example-policy' revision '123abc' does not have named_run_list 'deploy-app'(available named_run_lists: [])" + expect { policy_builder.build_node }.to raise_error(err_class, err_text) + end + + end + + context "and the named run_list is present in the policy" do + + let(:parsed_policyfile_json) do + basic_valid_policy_data.dup.tap do |p| + p["named_run_lists"] = { + "deploy-app" => [ "recipe[example1::default]" ] + } + end + end + + before do + policy_builder.build_node + end + + it "sets the run list to the desired named run list" do + expect(policy_builder.run_list).to eq([ "recipe[example1::default]" ]) + expected_expansion = Chef::PolicyBuilder::Policyfile::RunListExpansionIsh.new([ "example1::default" ], []) + expect(policy_builder.run_list_expansion).to eq(expected_expansion) + expect(policy_builder.run_list_with_versions_for_display).to eq(["example1::default@2.3.5 (168d210)"]) + expect(node.run_list).to eq([ Chef::RunList::RunListItem.new("recipe[example1::default]") ]) + expect(node[:roles]).to eq( [] ) + expect(node[:recipes]).to eq( ["example1::default"] ) + end + + it "disables the cookbook cache cleaner" do + expect(Chef::CookbookCacheCleaner.instance.skip_removal).to be(true) + end + + end + + end end -- cgit v1.2.1 From 6a3ff229c9db3c9811ada59aed2ead80efe699b4 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 17 Sep 2015 16:14:03 -0700 Subject: Configure named run list via command line or config file --- chef-config/lib/chef-config/config.rb | 6 ++++++ lib/chef/application/client.rb | 6 ++++++ spec/unit/application/client_spec.rb | 13 +++++++++++++ 3 files changed, 25 insertions(+) diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb index 76ee589ff9..32058f283a 100644 --- a/chef-config/lib/chef-config/config.rb +++ b/chef-config/lib/chef-config/config.rb @@ -356,6 +356,12 @@ module ChefConfig default :policy_name, nil default :policy_group, nil + # Policyfiles can have multiple run lists, via the named run list feature. + # Generally this will be set by a CLI option via Chef::Application::Client, + # but it could be set in client.rb if desired. + + default :named_run_list, nil + # During initial development, users were required to set `use_policyfile true` # in `client.rb` to opt-in to policyfile use. Chef Client now examines # configuration, node json, and the stored node to determine if policyfile diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index 73eda81343..148257ab89 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -160,6 +160,12 @@ class Chef::Application::Client < Chef::Application :description => "Set the client key file location", :proc => nil + option :named_run_list, + :short => "-n NAMED_RUN_LIST", + :long => "--named-run-list NAMED_RUN_LIST", + :description => "Use a policyfile's named run list instead of the default run list", + :default => nil + option :environment, :short => '-E ENVIRONMENT', :long => '--environment ENVIRONMENT', diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index 64a6bcc9d2..49da62c719 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -47,6 +47,19 @@ describe Chef::Application::Client, "reconfigure" do expect(app).to receive(:set_specific_recipes).and_return(true) app.reconfigure end + + context "when given a named_run_list" do + + before do + ARGV.replace( %w[ --named-run-list arglebargle-example ] ) + app.reconfigure + end + + it "sets named_run_list in Chef::Config" do + expect(Chef::Config[:named_run_list]).to eq("arglebargle-example") + end + + end end describe "when configured to not fork the client process" do -- cgit v1.2.1 From cc612fce9456a2ad626535d0464944d28c18d12a Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 17 Sep 2015 16:15:55 -0700 Subject: Fix false positive in test, make test more strict. --- spec/unit/application/client_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb index 49da62c719..831a3fc19e 100644 --- a/spec/unit/application/client_spec.rb +++ b/spec/unit/application/client_spec.rb @@ -250,7 +250,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config end it "should throw an exception" do - expect { @app.reconfigure }.to raise_error + expect { app.reconfigure }.to raise_error(Chef::Exceptions::PIDFileLockfileMatch) end end end -- cgit v1.2.1 From 12461128b1dc82b56568fe8ac6d5d9a8ffb0f3f2 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Mon, 21 Sep 2015 09:13:11 -0700 Subject: Safely clean up Win32 namespace after specs --- spec/support/shared/context/win32.rb | 34 ++++++++++++++++++++++++++++++ spec/unit/platform/query_helpers_spec.rb | 15 ++----------- spec/unit/provider/service/windows_spec.rb | 17 ++++++++++++--- spec/unit/win32/registry_spec.rb | 18 +++++++++++----- 4 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 spec/support/shared/context/win32.rb diff --git a/spec/support/shared/context/win32.rb b/spec/support/shared/context/win32.rb new file mode 100644 index 0000000000..3dbe876114 --- /dev/null +++ b/spec/support/shared/context/win32.rb @@ -0,0 +1,34 @@ +# +# Copyright:: Copyright (c) 2015 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. +# + +RSpec.shared_context "Win32" do + before(:all) do + @original_win32 = if defined?(Win32) + win32 = Object.send(:const_get, 'Win32') + Object.send(:remove_const, 'Win32') + win32 + else + nil + end + Win32 = Module.new + end + + after(:all) do + Object.send(:remove_const, 'Win32') if defined?(Win32) + Object.send(:const_set, 'Win32', @original_win32) if @original_win32 + end +end diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 7f1b3984d3..c220018d09 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -32,6 +32,8 @@ describe "Chef::Platform#windows_server_2003?" do end describe "Chef::Platform#windows_nano_server?" do + include_context "Win32" + let(:key) { "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels" } let(:key_query_value) { 0x0001 } let(:access) { key_query_value | 0x0100 } @@ -39,14 +41,6 @@ describe "Chef::Platform#windows_nano_server?" do let(:registry) { double("Win32::Registry") } before(:all) do - @original_win32 = if defined?(Win32) - win32 = Object.send(:const_get, 'Win32') - Object.send(:remove_const, 'Win32') - win32 - else - nil - end - Win32 = Module.new Win32::Registry = Class.new Win32::Registry::Error = Class.new(RuntimeError) @@ -62,11 +56,6 @@ describe "Chef::Platform#windows_nano_server?" do Win32::Registry.send(:remove_const, 'KEY_QUERY_VALUE') if defined?(Win32::Registry::KEY_QUERY_VALUE) end - after(:all) do - Object.send(:remove_const, 'Win32') if defined?(Win32) - Object.send(:const_set, 'Win32', @original_win32) if @original_win32 - end - it "returns false early when not on windows" do allow(ChefConfig).to receive(:windows?).and_return(false) expect(Chef::Platform).to_not receive(:require) diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb index 784a2232b2..d27cc54ec1 100644 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -21,6 +21,12 @@ require 'spec_helper' require 'mixlib/shellout' describe Chef::Provider::Service::Windows, "load_current_resource" do + include_context "Win32" + + before(:all) do + Win32::Service = Class.new + end + before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @@ -28,12 +34,11 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do @new_resource = Chef::Resource::WindowsService.new("chef") @provider = Chef::Provider::Service::Windows.new(@new_resource, @run_context) @provider.current_resource = Chef::Resource::WindowsService.new("current-chef") - Object.send(:remove_const, 'Win32') if defined?(Win32) - Win32 = Module.new - Win32::Service = Class.new + Win32::Service::AUTO_START = 0x00000002 Win32::Service::DEMAND_START = 0x00000003 Win32::Service::DISABLED = 0x00000004 + allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( @@ -42,6 +47,12 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do allow(Win32::Service).to receive(:configure).and_return(Win32::Service) end + after(:each) do + Win32::Service.send(:remove_const, 'AUTO_START') if defined?(Win32::Service::AUTO_START) + Win32::Service.send(:remove_const, 'DEMAND_START') if defined?(Win32::Service::DEMAND_START) + Win32::Service.send(:remove_const, 'DISABLED') if defined?(Win32::Service::DISABLED) + end + it "should set the current resources service name to the new resources service name" do @provider.load_current_resource expect(@provider.current_resource.service_name).to eq('chef') diff --git a/spec/unit/win32/registry_spec.rb b/spec/unit/win32/registry_spec.rb index fdd3e85a8c..3ad26d967e 100644 --- a/spec/unit/win32/registry_spec.rb +++ b/spec/unit/win32/registry_spec.rb @@ -19,6 +19,7 @@ require 'spec_helper' describe Chef::Win32::Registry do + include_context "Win32" let(:value1) { { :name => "one", :type => :string, :data => "1" } } let(:value1_upcase_name) { {:name => "ONE", :type => :string, :data => "1"} } @@ -29,25 +30,32 @@ describe Chef::Win32::Registry do let(:sub_key) {'OpscodePrimes'} let(:missing_key_path) {'HKCU\Software'} + before(:all) do + Win32::Registry = Class.new + Win32::Registry::Error = Class.new(RuntimeError) + end + before(:each) do allow_any_instance_of(Chef::Win32::Registry).to receive(:machine_architecture).and_return(:x86_64) @registry = Chef::Win32::Registry.new() #Making the values for registry constants available on unix - Object.send(:remove_const, 'Win32') if defined?(Win32) - Win32 = Module.new - Win32::Registry = Class.new Win32::Registry::KEY_SET_VALUE = 0x0002 Win32::Registry::KEY_QUERY_VALUE = 0x0001 Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004 Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010 - Win32::Registry::Error = Class.new(RuntimeError) - @hive_mock = double("::Win32::Registry::HKEY_CURRENT_USER") @reg_mock = double("reg") end + after(:each) do + Win32::Registry.send(:remove_const, 'KEY_SET_VALUE') if defined?(Win32::Registry::KEY_SET_VALUE) + Win32::Registry.send(:remove_const, 'KEY_QUERY_VALUE') if defined?(Win32::Registry::KEY_QUERY_VALUE) + Win32::Registry.send(:remove_const, 'KEY_READ') if defined?(Win32::Registry::KEY_READ) + Win32::Registry.send(:remove_const, 'KEY_WRITE') if defined?(Win32::Registry::KEY_WRITE) + end + describe "get_values" do it "gets all values for a key if the key exists" do expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) -- cgit v1.2.1 From ec3dee0c5c62949ba0d907676a779af046c8a552 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Mon, 21 Sep 2015 09:15:57 -0700 Subject: Use let --- spec/unit/provider/service/windows_spec.rb | 339 ++++++++++++++-------------- spec/unit/win32/registry_spec.rb | 351 ++++++++++++++--------------- 2 files changed, 345 insertions(+), 345 deletions(-) diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb index d27cc54ec1..f14cad8501 100644 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -23,25 +23,26 @@ require 'mixlib/shellout' describe Chef::Provider::Service::Windows, "load_current_resource" do include_context "Win32" + let(:new_resource) { Chef::Resource::WindowsService.new("chef") } + let(:provider) do + prvdr = Chef::Provider::Service::Windows.new(new_resource, + Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)) + prvdr.current_resource = Chef::Resource::WindowsService.new("current-chef") + prvdr + end + before(:all) do Win32::Service = Class.new end before(:each) do - @node = Chef::Node.new - @events = Chef::EventDispatch::Dispatcher.new - @run_context = Chef::RunContext.new(@node, {}, @events) - @new_resource = Chef::Resource::WindowsService.new("chef") - @provider = Chef::Provider::Service::Windows.new(@new_resource, @run_context) - @provider.current_resource = Chef::Resource::WindowsService.new("current-chef") - Win32::Service::AUTO_START = 0x00000002 Win32::Service::DEMAND_START = 0x00000003 Win32::Service::DISABLED = 0x00000004 - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) allow(Win32::Service).to receive(:exists?).and_return(true) allow(Win32::Service).to receive(:configure).and_return(Win32::Service) @@ -54,114 +55,114 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do end it "should set the current resources service name to the new resources service name" do - @provider.load_current_resource - expect(@provider.current_resource.service_name).to eq('chef') + provider.load_current_resource + expect(provider.current_resource.service_name).to eq('chef') end it "should return the current resource" do - expect(@provider.load_current_resource).to equal(@provider.current_resource) + expect(provider.load_current_resource).to equal(provider.current_resource) end it "should set the current resources status" do - @provider.load_current_resource - expect(@provider.current_resource.running).to be_truthy + provider.load_current_resource + expect(provider.current_resource.running).to be_truthy end it "should set the current resources start type" do - @provider.load_current_resource - expect(@provider.current_resource.enabled).to be_truthy + provider.load_current_resource + expect(provider.current_resource.enabled).to be_truthy end it "does not set the current resources start type if it is neither AUTO START or DISABLED" do - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "manual")) - @provider.load_current_resource - expect(@provider.current_resource.enabled).to be_nil + provider.load_current_resource + expect(provider.current_resource.enabled).to be_nil end describe Chef::Provider::Service::Windows, "start_service" do before(:each) do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "running")) end it "should call the start command if one is specified" do - @new_resource.start_command "sc start chef" - expect(@provider).to receive(:shell_out!).with("#{@new_resource.start_command}").and_return("Starting custom service") - @provider.start_service - expect(@new_resource.updated_by_last_action?).to be_truthy + new_resource.start_command "sc start chef" + expect(provider).to receive(:shell_out!).with("#{new_resource.start_command}").and_return("Starting custom service") + provider.start_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should use the built-in command if no start command is specified" do - expect(Win32::Service).to receive(:start).with(@new_resource.service_name) - @provider.start_service - expect(@new_resource.updated_by_last_action?).to be_truthy + expect(Win32::Service).to receive(:start).with(new_resource.service_name) + provider.start_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should do nothing if the service does not exist" do - allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false) - expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name) - @provider.start_service - expect(@new_resource.updated_by_last_action?).to be_falsey + allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) + expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) + provider.start_service + expect(new_resource.updated_by_last_action?).to be_falsey end it "should do nothing if the service is running" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name) - @provider.start_service - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) + provider.start_service + expect(new_resource.updated_by_last_action?).to be_falsey end it "should raise an error if the service is paused" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "paused")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name) - expect { @provider.start_service }.to raise_error( Chef::Exceptions::Service ) - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) + expect { provider.start_service }.to raise_error( Chef::Exceptions::Service ) + expect(new_resource.updated_by_last_action?).to be_falsey end it "should wait and continue if the service is in start_pending" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "start pending"), double("StatusStruct", :current_state => "start pending"), double("StatusStruct", :current_state => "running")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name) - @provider.start_service - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) + provider.start_service + expect(new_resource.updated_by_last_action?).to be_falsey end it "should fail if the service is in stop_pending" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stop pending")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name) - expect { @provider.start_service }.to raise_error( Chef::Exceptions::Service ) - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) + expect { provider.start_service }.to raise_error( Chef::Exceptions::Service ) + expect(new_resource.updated_by_last_action?).to be_falsey end describe "running as a different account" do - let(:old_run_as_user) { @new_resource.run_as_user } - let(:old_run_as_password) { @new_resource.run_as_password } + let(:old_run_as_user) { new_resource.run_as_user } + let(:old_run_as_password) { new_resource.run_as_password } before { - @new_resource.run_as_user(".\\wallace") - @new_resource.run_as_password("Wensleydale") + new_resource.run_as_user(".\\wallace") + new_resource.run_as_password("Wensleydale") } after { - @new_resource.run_as_user(old_run_as_user) - @new_resource.run_as_password(old_run_as_password) + new_resource.run_as_user(old_run_as_user) + new_resource.run_as_password(old_run_as_password) } it "should call #grant_service_logon if the :run_as_user and :run_as_password attributes are present" do expect(Win32::Service).to receive(:start) - expect(@provider).to receive(:grant_service_logon).and_return(true) - @provider.start_service + expect(provider).to receive(:grant_service_logon).and_return(true) + provider.start_service end end end @@ -170,78 +171,78 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do describe Chef::Provider::Service::Windows, "stop_service" do before(:each) do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running"), double("StatusStruct", :current_state => "stopped")) end it "should call the stop command if one is specified" do - @new_resource.stop_command "sc stop chef" - expect(@provider).to receive(:shell_out!).with("#{@new_resource.stop_command}").and_return("Stopping custom service") - @provider.stop_service - expect(@new_resource.updated_by_last_action?).to be_truthy + new_resource.stop_command "sc stop chef" + expect(provider).to receive(:shell_out!).with("#{new_resource.stop_command}").and_return("Stopping custom service") + provider.stop_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should use the built-in command if no stop command is specified" do - expect(Win32::Service).to receive(:stop).with(@new_resource.service_name) - @provider.stop_service - expect(@new_resource.updated_by_last_action?).to be_truthy + expect(Win32::Service).to receive(:stop).with(new_resource.service_name) + provider.stop_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should do nothing if the service does not exist" do - allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false) - expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name) - @provider.stop_service - expect(@new_resource.updated_by_last_action?).to be_falsey + allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) + expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) + provider.stop_service + expect(new_resource.updated_by_last_action?).to be_falsey end it "should do nothing if the service is stopped" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name) - @provider.stop_service - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) + provider.stop_service + expect(new_resource.updated_by_last_action?).to be_falsey end it "should raise an error if the service is paused" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "paused")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name) - expect { @provider.stop_service }.to raise_error( Chef::Exceptions::Service ) - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) + expect { provider.stop_service }.to raise_error( Chef::Exceptions::Service ) + expect(new_resource.updated_by_last_action?).to be_falsey end it "should wait and continue if the service is in stop_pending" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stop pending"), double("StatusStruct", :current_state => "stop pending"), double("StatusStruct", :current_state => "stopped")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name) - @provider.stop_service - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) + provider.stop_service + expect(new_resource.updated_by_last_action?).to be_falsey end it "should fail if the service is in start_pending" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "start pending")) - @provider.load_current_resource - expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name) - expect { @provider.stop_service }.to raise_error( Chef::Exceptions::Service ) - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.load_current_resource + expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) + expect { provider.stop_service }.to raise_error( Chef::Exceptions::Service ) + expect(new_resource.updated_by_last_action?).to be_falsey end it "should pass custom timeout to the stop command if provided" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) - @new_resource.timeout 1 - expect(Win32::Service).to receive(:stop).with(@new_resource.service_name) + new_resource.timeout 1 + expect(Win32::Service).to receive(:stop).with(new_resource.service_name) Timeout.timeout(2) do - expect { @provider.stop_service }.to raise_error(Timeout::Error) + expect { provider.stop_service }.to raise_error(Timeout::Error) end - expect(@new_resource.updated_by_last_action?).to be_falsey + expect(new_resource.updated_by_last_action?).to be_falsey end end @@ -249,152 +250,152 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do describe Chef::Provider::Service::Windows, "restart_service" do it "should call the restart command if one is specified" do - @new_resource.restart_command "sc restart" - expect(@provider).to receive(:shell_out!).with("#{@new_resource.restart_command}") - @provider.restart_service - expect(@new_resource.updated_by_last_action?).to be_truthy + new_resource.restart_command "sc restart" + expect(provider).to receive(:shell_out!).with("#{new_resource.restart_command}") + provider.restart_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should stop then start the service if it is running" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running"), double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "running")) - expect(Win32::Service).to receive(:stop).with(@new_resource.service_name) - expect(Win32::Service).to receive(:start).with(@new_resource.service_name) - @provider.restart_service - expect(@new_resource.updated_by_last_action?).to be_truthy + expect(Win32::Service).to receive(:stop).with(new_resource.service_name) + expect(Win32::Service).to receive(:start).with(new_resource.service_name) + provider.restart_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should just start the service if it is stopped" do - allow(Win32::Service).to receive(:status).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "running")) - expect(Win32::Service).to receive(:start).with(@new_resource.service_name) - @provider.restart_service - expect(@new_resource.updated_by_last_action?).to be_truthy + expect(Win32::Service).to receive(:start).with(new_resource.service_name) + provider.restart_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should do nothing if the service does not exist" do - allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false) - expect(Win32::Service).not_to receive(:stop).with(@new_resource.service_name) - expect(Win32::Service).not_to receive(:start).with(@new_resource.service_name) - @provider.restart_service - expect(@new_resource.updated_by_last_action?).to be_falsey + allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) + expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) + expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) + provider.restart_service + expect(new_resource.updated_by_last_action?).to be_falsey end end describe Chef::Provider::Service::Windows, "enable_service" do before(:each) do - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "disabled")) end it "should enable service" do - expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::AUTO_START) - @provider.enable_service - expect(@new_resource.updated_by_last_action?).to be_truthy + expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::AUTO_START) + provider.enable_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should do nothing if the service does not exist" do - allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false) + allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:configure) - @provider.enable_service - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.enable_service + expect(new_resource.updated_by_last_action?).to be_falsey end end describe Chef::Provider::Service::Windows, "action_enable" do it "does nothing if the service is enabled" do - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) - expect(@provider).not_to receive(:enable_service) - @provider.action_enable + expect(provider).not_to receive(:enable_service) + provider.action_enable end it "enables the service if it is not set to automatic start" do - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "disabled")) - expect(@provider).to receive(:enable_service) - @provider.action_enable + expect(provider).to receive(:enable_service) + provider.action_enable end end describe Chef::Provider::Service::Windows, "action_disable" do it "does nothing if the service is disabled" do - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "disabled")) - expect(@provider).not_to receive(:disable_service) - @provider.action_disable + expect(provider).not_to receive(:disable_service) + provider.action_disable end it "disables the service if it is not set to disabled" do - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) - expect(@provider).to receive(:disable_service) - @provider.action_disable + expect(provider).to receive(:disable_service) + provider.action_disable end end describe Chef::Provider::Service::Windows, "disable_service" do before(:each) do - allow(Win32::Service).to receive(:config_info).with(@new_resource.service_name).and_return( + allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) end it "should disable service" do expect(Win32::Service).to receive(:configure) - @provider.disable_service - expect(@new_resource.updated_by_last_action?).to be_truthy + provider.disable_service + expect(new_resource.updated_by_last_action?).to be_truthy end it "should do nothing if the service does not exist" do - allow(Win32::Service).to receive(:exists?).with(@new_resource.service_name).and_return(false) + allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:configure) - @provider.disable_service - expect(@new_resource.updated_by_last_action?).to be_falsey + provider.disable_service + expect(new_resource.updated_by_last_action?).to be_falsey end end describe Chef::Provider::Service::Windows, "action_configure_startup" do { :automatic => "auto start", :manual => "demand start", :disabled => "disabled" }.each do |type,win32| it "sets the startup type to #{type} if it is something else" do - @new_resource.startup_type(type) - allow(@provider).to receive(:current_start_type).and_return("fire") - expect(@provider).to receive(:set_startup_type).with(type) - @provider.action_configure_startup + new_resource.startup_type(type) + allow(provider).to receive(:current_start_type).and_return("fire") + expect(provider).to receive(:set_startup_type).with(type) + provider.action_configure_startup end it "leaves the startup type as #{type} if it is already set" do - @new_resource.startup_type(type) - allow(@provider).to receive(:current_start_type).and_return(win32) - expect(@provider).not_to receive(:set_startup_type).with(type) - @provider.action_configure_startup + new_resource.startup_type(type) + allow(provider).to receive(:current_start_type).and_return(win32) + expect(provider).not_to receive(:set_startup_type).with(type) + provider.action_configure_startup end end end describe Chef::Provider::Service::Windows, "set_start_type" do it "when called with :automatic it calls Win32::Service#configure with Win32::Service::AUTO_START" do - expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::AUTO_START) - @provider.send(:set_startup_type, :automatic) + expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::AUTO_START) + provider.send(:set_startup_type, :automatic) end it "when called with :manual it calls Win32::Service#configure with Win32::Service::DEMAND_START" do - expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::DEMAND_START) - @provider.send(:set_startup_type, :manual) + expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::DEMAND_START) + provider.send(:set_startup_type, :manual) end it "when called with :disabled it calls Win32::Service#configure with Win32::Service::DISABLED" do - expect(Win32::Service).to receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::DISABLED) - @provider.send(:set_startup_type, :disabled) + expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::DISABLED) + provider.send(:set_startup_type, :disabled) end it "raises an exception when given an unknown start type" do - expect { @provider.send(:set_startup_type, :fire_truck) }.to raise_error(Chef::Exceptions::ConfigurationError) + expect { provider.send(:set_startup_type, :fire_truck) }.to raise_error(Chef::Exceptions::ConfigurationError) end end @@ -420,9 +421,9 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do let(:success_string) { "The task has completed successfully.\r\nSee logfile etc." } let(:failure_string) { "Look on my works, ye Mighty, and despair!" } let(:command) { - dbfile = @provider.grant_dbfile_name(username) - policyfile = @provider.grant_policyfile_name(username) - logfile = @provider.grant_logfile_name(username) + dbfile = provider.grant_dbfile_name(username) + policyfile = provider.grant_policyfile_name(username) + logfile = provider.grant_logfile_name(username) %Q{secedit.exe /configure /db "#{dbfile}" /cfg "#{policyfile}" /areas USER_RIGHTS SECURITYPOLICY SERVICES /log "#{logfile}"} } @@ -435,20 +436,20 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do after { # only needed for the second test. - ::File.delete(@provider.grant_policyfile_name(username)) rescue nil - ::File.delete(@provider.grant_logfile_name(username)) rescue nil - ::File.delete(@provider.grant_dbfile_name(username)) rescue nil + ::File.delete(provider.grant_policyfile_name(username)) rescue nil + ::File.delete(provider.grant_logfile_name(username)) rescue nil + ::File.delete(provider.grant_dbfile_name(username)) rescue nil } it "calls Mixlib::Shellout with the correct command string" do expect_any_instance_of(Mixlib::ShellOut).to receive(:exitstatus).and_return(0) - expect(@provider.grant_service_logon(username)).to equal true + expect(provider.grant_service_logon(username)).to equal true end it "raises an exception when the grant command fails" do expect_any_instance_of(Mixlib::ShellOut).to receive(:exitstatus).and_return(1) expect_any_instance_of(Mixlib::ShellOut).to receive(:stdout).and_return(failure_string) - expect { @provider.grant_service_logon(username) }.to raise_error(Chef::Exceptions::Service) + expect { provider.grant_service_logon(username) }.to raise_error(Chef::Exceptions::Service) end end @@ -456,17 +457,17 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do include_context "testing private methods" it "correctly reformats usernames to create valid filenames" do - expect(@provider.clean_username_for_path("\\\\problem username/oink.txt")).to eq("_problem_username_oink_txt") - expect(@provider.clean_username_for_path("boring_username")).to eq("boring_username") + expect(provider.clean_username_for_path("\\\\problem username/oink.txt")).to eq("_problem_username_oink_txt") + expect(provider.clean_username_for_path("boring_username")).to eq("boring_username") end it "correctly reformats usernames for the policy file" do - expect(@provider.canonicalize_username(".\\maryann")).to eq("maryann") - expect(@provider.canonicalize_username("maryann")).to eq("maryann") + expect(provider.canonicalize_username(".\\maryann")).to eq("maryann") + expect(provider.canonicalize_username("maryann")).to eq("maryann") - expect(@provider.canonicalize_username("\\\\maryann")).to eq("maryann") - expect(@provider.canonicalize_username("mydomain\\\\maryann")).to eq("mydomain\\\\maryann") - expect(@provider.canonicalize_username("\\\\mydomain\\\\maryann")).to eq("mydomain\\\\maryann") + expect(provider.canonicalize_username("\\\\maryann")).to eq("maryann") + expect(provider.canonicalize_username("mydomain\\\\maryann")).to eq("mydomain\\\\maryann") + expect(provider.canonicalize_username("\\\\mydomain\\\\maryann")).to eq("mydomain\\\\maryann") end end end diff --git a/spec/unit/win32/registry_spec.rb b/spec/unit/win32/registry_spec.rb index 3ad26d967e..56def30638 100644 --- a/spec/unit/win32/registry_spec.rb +++ b/spec/unit/win32/registry_spec.rb @@ -29,6 +29,9 @@ describe Chef::Win32::Registry do let(:key_to_delete) { 'OpscodeNumbers' } let(:sub_key) {'OpscodePrimes'} let(:missing_key_path) {'HKCU\Software'} + let(:registry) { Chef::Win32::Registry.new() } + let(:hive_mock) { double("::Win32::Registry::KHKEY_CURRENT_USER") } + let(:reg_mock) { double("reg") } before(:all) do Win32::Registry = Class.new @@ -37,16 +40,12 @@ describe Chef::Win32::Registry do before(:each) do allow_any_instance_of(Chef::Win32::Registry).to receive(:machine_architecture).and_return(:x86_64) - @registry = Chef::Win32::Registry.new() - + #Making the values for registry constants available on unix Win32::Registry::KEY_SET_VALUE = 0x0002 Win32::Registry::KEY_QUERY_VALUE = 0x0001 Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004 Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010 - - @hive_mock = double("::Win32::Registry::HKEY_CURRENT_USER") - @reg_mock = double("reg") end after(:each) do @@ -58,338 +57,338 @@ describe Chef::Win32::Registry do describe "get_values" do it "gets all values for a key if the key exists" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:map) - @registry.get_values(key_path) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:map) + registry.get_values(key_path) end it "throws an exception if key does not exist" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect{@registry.get_values(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect{registry.get_values(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "set_value" do it "does nothing if key and hive and value exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true) - @registry.set_value(key_path, value1) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(true) + registry.set_value(key_path, value1) end it "does nothing if case insensitive key and hive and value exist" do - expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true) - @registry.set_value(key_path.downcase, value1) + expect(registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([hive_mock, key]) + expect(registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true) + expect(registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true) + registry.set_value(key_path.downcase, value1) end it "does nothing if key and hive and value with a case insensitive name exist" do - expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) - @registry.set_value(key_path.downcase, value1_upcase_name) + expect(registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([hive_mock, key]) + expect(registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) + expect(registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) + registry.set_value(key_path.downcase, value1_upcase_name) end it "updates value if key and hive and value exist, but data is different" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false) - expect(@hive_mock).to receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:write).with("one", 1, "1") - @registry.set_value(key_path, value1) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(false) + expect(hive_mock).to receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry.registry_system_architecture).and_yield(reg_mock) + expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(reg_mock).to receive(:write).with("one", 1, "1") + registry.set_value(key_path, value1) end it "creates value if the key exists and the value does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:write).with("one", 1, "1") - @registry.set_value(key_path, value1) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry.registry_system_architecture).and_yield(reg_mock) + expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(reg_mock).to receive(:write).with("one", 1, "1") + registry.set_value(key_path, value1) end it "should raise an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "delete_value" do it "deletes value if value exists" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:delete_value).with("one").and_return(true) - @registry.delete_value(key_path, value1) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:delete_value).with("one").and_return(true) + registry.delete_value(key_path, value1) end it "raises an exception if the key does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - @registry.delete_value(key_path, value1) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + registry.delete_value(key_path, value1) end it "does nothing if the value does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - @registry.delete_value(key_path, value1) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + registry.delete_value(key_path, value1) end end describe "create_key" do it "creates key if intermediate keys are missing and recursive is set to true" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) - expect(@registry).to receive(:create_missing).with(key_path) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) - @registry.create_key(key_path, true) + expect(registry).to receive(:keys_missing?).with(key_path).and_return(true) + expect(registry).to receive(:create_missing).with(key_path) + expect(registry).to receive(:key_exists?).with(key_path).and_return(false) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture) + registry.create_key(key_path, true) end it "raises an exception if intermediate keys are missing and recursive is set to false" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) - expect{@registry.create_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) + expect(registry).to receive(:keys_missing?).with(key_path).and_return(true) + expect{registry.create_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "does nothing if the key exists" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(true) - expect(@registry).to receive(:create_missing).with(key_path) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - @registry.create_key(key_path, true) + expect(registry).to receive(:keys_missing?).with(key_path).and_return(true) + expect(registry).to receive(:create_missing).with(key_path) + expect(registry).to receive(:key_exists?).with(key_path).and_return(true) + registry.create_key(key_path, true) end it "create key if intermediate keys not missing and recursive is set to false" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) - @registry.create_key(key_path, false) + expect(registry).to receive(:keys_missing?).with(key_path).and_return(false) + expect(registry).to receive(:key_exists?).with(key_path).and_return(false) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture) + registry.create_key(key_path, false) end it "create key if intermediate keys not missing and recursive is set to true" do - expect(@registry).to receive(:keys_missing?).with(key_path).and_return(false) - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture) - @registry.create_key(key_path, true) + expect(registry).to receive(:keys_missing?).with(key_path).and_return(false) + expect(registry).to receive(:key_exists?).with(key_path).and_return(false) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture) + registry.create_key(key_path, true) end end describe "delete_key", :windows_only do it "deletes key if it has subkeys and recursive is set to true" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) - @registry.delete_key(key_path, true) + expect(registry).to receive(:key_exists?).with(key_path).and_return(true) + expect(registry).to receive(:has_subkeys?).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) + registry.delete_key(key_path, true) end it "raises an exception if it has subkeys but recursive is set to false" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(true) - expect{@registry.delete_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) + expect(registry).to receive(:key_exists?).with(key_path).and_return(true) + expect(registry).to receive(:has_subkeys?).with(key_path).and_return(true) + expect{registry.delete_key(key_path, false)}.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "deletes key if the key exists and has no subkeys" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(true) - expect(@registry).to receive(:has_subkeys?).with(key_path).and_return(false) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) - @registry.delete_key(key_path, true) + expect(registry).to receive(:key_exists?).with(key_path).and_return(true) + expect(registry).to receive(:has_subkeys?).with(key_path).and_return(false) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) + registry.delete_key(key_path, true) end end describe "key_exists?" do it "returns true if key_exists" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry.key_exists?(key_path)).to eq(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(registry.key_exists?(key_path)).to eq(true) end it "returns false if key does not exist" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_raise(::Win32::Registry::Error) - expect(@registry.key_exists?(key_path)).to eq(false) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_raise(::Win32::Registry::Error) + expect(registry.key_exists?(key_path)).to eq(false) end end describe "key_exists!" do it "throws an exception if the key_parent does not exist" do - expect(@registry).to receive(:key_exists?).with(key_path).and_return(false) - expect{@registry.key_exists!(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + expect(registry).to receive(:key_exists?).with(key_path).and_return(false) + expect{registry.key_exists!(key_path)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "hive_exists?" do it "returns true if the hive exists" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - @registry.hive_exists?(key_path) == true + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + registry.hive_exists?(key_path) == true end it "returns false if the hive does not exist" do - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing) - @registry.hive_exists?(key_path) == false + expect(registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing) + registry.hive_exists?(key_path) == false end end describe "has_subkeys?" do it "returns true if the key has subkeys" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each_key).and_yield(key) - @registry.has_subkeys?(key_path) == true + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:each_key).and_yield(key) + registry.has_subkeys?(key_path) == true end it "returns false if the key does not have subkeys" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each_key).and_return(no_args()) - expect(@registry.has_subkeys?(key_path)).to eq(false) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:each_key).and_return(no_args()) + expect(registry.has_subkeys?(key_path)).to eq(false) end it "throws an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {registry.set_value(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "get_subkeys" do it "returns the subkeys if they exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each_key).and_yield(sub_key) - @registry.get_subkeys(key_path) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:each_key).and_yield(sub_key) + registry.get_subkeys(key_path) end end describe "value_exists?" do it "throws an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.value_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {registry.value_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if the value exists" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:any?).and_yield("one") - @registry.value_exists?(key_path, value1) == true + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:any?).and_yield("one") + registry.value_exists?(key_path, value1) == true end it "returns false if the value does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:any?).and_yield(no_args()) - @registry.value_exists?(key_path, value1) == false + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:any?).and_yield(no_args()) + registry.value_exists?(key_path, value1) == false end end describe "data_exists?" do it "throws an exception if the key does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) - expect {@registry.data_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) + expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) + expect {registry.data_exists?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if the data exists" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "1") - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry.data_exists?(key_path, value1)).to eq(true) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "1") + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(registry.data_exists?(key_path, value1)).to eq(true) end it "returns false if the data does not exist" do - expect(@registry).to receive(:key_exists!).with(key_path).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "2") - expect(@registry.data_exists?(key_path, value1)).to eq(false) + expect(registry).to receive(:key_exists!).with(key_path).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "2") + expect(registry.data_exists?(key_path, value1)).to eq(false) end end describe "value_exists!" do it "does nothing if the value exists" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(true) - @registry.value_exists!(key_path, value1) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) + registry.value_exists!(key_path, value1) end it "throws an exception if the value does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - expect{@registry.value_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + expect{registry.value_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) end end describe "data_exists!" do it "does nothing if the data exists" do - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true) - @registry.data_exists!(key_path, value1) + expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(true) + registry.data_exists!(key_path, value1) end it "throws an exception if the data does not exist" do - expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(false) - expect{@registry.data_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegDataMissing) + expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(false) + expect{registry.data_exists!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegDataMissing) end end describe "type_matches?" do it "returns true if type matches" do - expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@registry).to receive(:get_type_from_name).with(:string).and_return(1) - expect(@reg_mock).to receive(:each).and_yield("one", 1) - expect(@registry.type_matches?(key_path, value1)).to eq(true) + expect(registry).to receive(:value_exists!).with(key_path, value1).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) + expect(reg_mock).to receive(:each).and_yield("one", 1) + expect(registry.type_matches?(key_path, value1)).to eq(true) end it "returns false if type does not match" do - expect(@registry).to receive(:value_exists!).with(key_path, value1).and_return(true) - expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key]) - expect(@hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock) - expect(@reg_mock).to receive(:each).and_yield("two", 2) - expect(@registry.type_matches?(key_path, value1)).to eq(false) + expect(registry).to receive(:value_exists!).with(key_path, value1).and_return(true) + expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) + expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) + expect(reg_mock).to receive(:each).and_yield("two", 2) + expect(registry.type_matches?(key_path, value1)).to eq(false) end it "throws an exception if value does not exist" do - expect(@registry).to receive(:value_exists?).with(key_path, value1).and_return(false) - expect{@registry.type_matches?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) + expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) + expect{registry.type_matches?(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegValueMissing) end end describe "type_matches!" do it "does nothing if the type_matches" do - expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(true) - @registry.type_matches!(key_path, value1) + expect(registry).to receive(:type_matches?).with(key_path, value1).and_return(true) + registry.type_matches!(key_path, value1) end it "throws an exception if the type does not match" do - expect(@registry).to receive(:type_matches?).with(key_path, value1).and_return(false) - expect{@registry.type_matches!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegTypesMismatch) + expect(registry).to receive(:type_matches?).with(key_path, value1).and_return(false) + expect{registry.type_matches!(key_path, value1)}.to raise_error(Chef::Exceptions::Win32RegTypesMismatch) end end describe "keys_missing?" do it "returns true if the keys are missing" do - expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(false) - expect(@registry.keys_missing?(key_path)).to eq(true) + expect(registry).to receive(:key_exists?).with(missing_key_path).and_return(false) + expect(registry.keys_missing?(key_path)).to eq(true) end it "returns false if no keys in the path are missing" do - expect(@registry).to receive(:key_exists?).with(missing_key_path).and_return(true) - expect(@registry.keys_missing?(key_path)).to eq(false) + expect(registry).to receive(:key_exists?).with(missing_key_path).and_return(true) + expect(registry.keys_missing?(key_path)).to eq(false) end end end -- cgit v1.2.1 From 3e94f9d11f19f833af09a47894e890ad2ef73f13 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Mon, 21 Sep 2015 09:32:14 -0700 Subject: Remove should from spec text --- spec/unit/provider/service/windows_spec.rb | 56 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb index f14cad8501..e2f610fb61 100644 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -54,21 +54,21 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do Win32::Service.send(:remove_const, 'DISABLED') if defined?(Win32::Service::DISABLED) end - it "should set the current resources service name to the new resources service name" do + it "sets the current resources service name to the new resources service name" do provider.load_current_resource expect(provider.current_resource.service_name).to eq('chef') end - it "should return the current resource" do + it "returns the current resource" do expect(provider.load_current_resource).to equal(provider.current_resource) end - it "should set the current resources status" do + it "sets the current resources status" do provider.load_current_resource expect(provider.current_resource.running).to be_truthy end - it "should set the current resources start type" do + it "sets the current resources start type" do provider.load_current_resource expect(provider.current_resource.enabled).to be_truthy end @@ -87,27 +87,27 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do double("StatusStruct", :current_state => "running")) end - it "should call the start command if one is specified" do + it "calls the start command if one is specified" do new_resource.start_command "sc start chef" expect(provider).to receive(:shell_out!).with("#{new_resource.start_command}").and_return("Starting custom service") provider.start_service expect(new_resource.updated_by_last_action?).to be_truthy end - it "should use the built-in command if no start command is specified" do + it "uses the built-in command if no start command is specified" do expect(Win32::Service).to receive(:start).with(new_resource.service_name) provider.start_service expect(new_resource.updated_by_last_action?).to be_truthy end - it "should do nothing if the service does not exist" do + it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) provider.start_service expect(new_resource.updated_by_last_action?).to be_falsey end - it "should do nothing if the service is running" do + it "does nothing if the service is running" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) provider.load_current_resource @@ -116,7 +116,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_falsey end - it "should raise an error if the service is paused" do + it "raises an error if the service is paused" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "paused")) provider.load_current_resource @@ -125,7 +125,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_falsey end - it "should wait and continue if the service is in start_pending" do + it "waits and continues if the service is in start_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "start pending"), double("StatusStruct", :current_state => "start pending"), @@ -136,7 +136,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_falsey end - it "should fail if the service is in stop_pending" do + it "fails if the service is in stop_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stop pending")) provider.load_current_resource @@ -159,7 +159,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do new_resource.run_as_password(old_run_as_password) } - it "should call #grant_service_logon if the :run_as_user and :run_as_password attributes are present" do + it "calls #grant_service_logon if the :run_as_user and :run_as_password attributes are present" do expect(Win32::Service).to receive(:start) expect(provider).to receive(:grant_service_logon).and_return(true) provider.start_service @@ -176,27 +176,27 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do double("StatusStruct", :current_state => "stopped")) end - it "should call the stop command if one is specified" do + it "calls the stop command if one is specified" do new_resource.stop_command "sc stop chef" expect(provider).to receive(:shell_out!).with("#{new_resource.stop_command}").and_return("Stopping custom service") provider.stop_service expect(new_resource.updated_by_last_action?).to be_truthy end - it "should use the built-in command if no stop command is specified" do + it "uses the built-in command if no stop command is specified" do expect(Win32::Service).to receive(:stop).with(new_resource.service_name) provider.stop_service expect(new_resource.updated_by_last_action?).to be_truthy end - it "should do nothing if the service does not exist" do + it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) provider.stop_service expect(new_resource.updated_by_last_action?).to be_falsey end - it "should do nothing if the service is stopped" do + it "does nothing if the service is stopped" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped")) provider.load_current_resource @@ -205,7 +205,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_falsey end - it "should raise an error if the service is paused" do + it "raises an error if the service is paused" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "paused")) provider.load_current_resource @@ -214,7 +214,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_falsey end - it "should wait and continue if the service is in stop_pending" do + it "waits and continue if the service is in stop_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stop pending"), double("StatusStruct", :current_state => "stop pending"), @@ -225,7 +225,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_falsey end - it "should fail if the service is in start_pending" do + it "fails if the service is in start_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "start pending")) provider.load_current_resource @@ -234,7 +234,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_falsey end - it "should pass custom timeout to the stop command if provided" do + it "passes custom timeout to the stop command if provided" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) new_resource.timeout 1 @@ -249,14 +249,14 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do describe Chef::Provider::Service::Windows, "restart_service" do - it "should call the restart command if one is specified" do + it "calls the restart command if one is specified" do new_resource.restart_command "sc restart" expect(provider).to receive(:shell_out!).with("#{new_resource.restart_command}") provider.restart_service expect(new_resource.updated_by_last_action?).to be_truthy end - it "should stop then start the service if it is running" do + it "stops then starts the service if it is running" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running"), double("StatusStruct", :current_state => "stopped"), @@ -268,7 +268,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_truthy end - it "should just start the service if it is stopped" do + it "just starts the service if it is stopped" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "stopped"), @@ -278,7 +278,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do expect(new_resource.updated_by_last_action?).to be_truthy end - it "should do nothing if the service does not exist" do + it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) @@ -294,13 +294,13 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do double("ConfigStruct", :start_type => "disabled")) end - it "should enable service" do + it "enables service" do expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::AUTO_START) provider.enable_service expect(new_resource.updated_by_last_action?).to be_truthy end - it "should do nothing if the service does not exist" do + it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:configure) provider.enable_service @@ -346,13 +346,13 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do double("ConfigStruct", :start_type => "auto start")) end - it "should disable service" do + it "disables service" do expect(Win32::Service).to receive(:configure) provider.disable_service expect(new_resource.updated_by_last_action?).to be_truthy end - it "should do nothing if the service does not exist" do + it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:configure) provider.disable_service -- cgit v1.2.1 From 61494f3a09de4d008e0d03076bd2ee8b77531625 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Mon, 21 Sep 2015 09:07:50 -0700 Subject: Remove pesky whitespace --- spec/unit/platform/query_helpers_spec.rb | 1 - spec/unit/provider/service/windows_spec.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index c220018d09..b5c56e8f9a 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -43,7 +43,6 @@ describe "Chef::Platform#windows_nano_server?" do before(:all) do Win32::Registry = Class.new Win32::Registry::Error = Class.new(RuntimeError) - end before do diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb index e2f610fb61..4c9f5b3377 100644 --- a/spec/unit/provider/service/windows_spec.rb +++ b/spec/unit/provider/service/windows_spec.rb @@ -167,7 +167,6 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do end end - describe Chef::Provider::Service::Windows, "stop_service" do before(:each) do -- cgit v1.2.1 From 538af753efff8493b71ee49f06f314701ce2523e Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Mon, 21 Sep 2015 10:36:35 -0700 Subject: Update docfiles for policyfile/node integration Also update for named run list support. --- CHANGELOG.md | 2 ++ DOC_CHANGES.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4df36163fd..7c83dbed88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ of partial templates. * [**Joel Handwell**](https://github.com/joelhandwell): [pr#3821](https://github.com/chef/chef/pull/3821) Human friendly elapsed time in log +* [pr#3928](https://github.com/chef/chef/pull/3928) Add named run list support when using policyfiles +* [pr#3913](https://github.com/chef/chef/pull/3913) Add `policy_name`and `policy_group` fields to the node object * [pr#3875](https://github.com/chef/chef/pull/3875) Patch Win32::Registry#delete_key, #delete_value to use wide (W) APIs * [pr#3850](https://github.com/chef/chef/pull/3850) Patch Win32::Registry#write to fix encoding errors * [pr#3837](https://github.com/chef/chef/pull/3837) refactor remote_directory provider for mem+perf improvement diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index 4c07dea872..0b467f2570 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -6,6 +6,69 @@ Example Doc Change: Description of the required change. --> +### client.rb named run list setting + +Policyfiles allow for multiple named run lists to be specified. To use +them in chef-client, one can either specify them on the command line +with: + +``` +chef-client --named-run-list NAME +``` + +or use the short option: + +``` +chef-client -n NAME +``` + +or specify the named run list in client.rb: + +```ruby +named_run_list "NAME" +``` + +NOTE: ChefDK has supported named run lists in policyfiles for a few +releases, but is affected by a bug where named run lists can be deleted +from a Policyfile.lock.json during the upload. The fix will likely be +included in ChefDK 0.8.0. See: https://github.com/chef/chef-dk/pull/520 + +### client.rb policyfile settings + +Chef client can be configured to run in policyfile mode by setting +`policy_name` and `policy_group` in client.rb. In order to use +policyfiles, _both_ settings should be set. Example: + +```ruby +policy_name "appserver" +policy_group "staging" +``` + +As of Chef Client 12.5, when used in conjunction with Chef Server 12.3, +these settings can instead be set directly on the node object. Setting +them via the node JSON as described below will result in Chef Client +creating the node object with these settings. + +### `chef-client -j JSON` + +Chef client node JSON can now be used to specify the policyfile settings +`policy_name` and `policy_group`, like so: + +```json +{ + "policy_name": "appserver", + "policy_group": "staging" +} +``` + +Doing so will cause `chef-client` to switch to policyfile mode +automatically (i.e., the `use_policy` flag in `client.rb` is not +required). + +Users who wish to take advantage of this functionality should upgrade +the Chef Server to at least 12.3, which is the first Chef Server release +capable of storing `policy_name` and `policy_group` in the node data. + ### PSCredential Support for `dsc_script` `dsc_script` now supports the use of `ps_credential` to create a PSCredential -- cgit v1.2.1 From 064e0a6b90d1e9a1e00e0c9ef4d18eb1ecc33a3e Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 10 Jul 2015 12:15:07 -0600 Subject: Rename action_provider_class to less-wordy action_class --- lib/chef/exceptions.rb | 2 +- lib/chef/resource.rb | 45 ++++++------ lib/chef/resource/action_class.rb | 83 ++++++++++++++++++++++ lib/chef/resource/action_provider.rb | 69 ------------------ spec/integration/recipes/resource_action_spec.rb | 26 +++---- .../recipes/resource_converge_if_changed_spec.rb | 2 +- 6 files changed, 121 insertions(+), 106 deletions(-) create mode 100644 lib/chef/resource/action_class.rb delete mode 100644 lib/chef/resource/action_provider.rb diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index 7862d9c160..6e7ff2e24a 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -105,7 +105,7 @@ class Chef class VerificationNotFound < RuntimeError; end class InvalidEventType < ArgumentError; end class MultipleIdentityError < RuntimeError; end - # Used in Resource::ActionProvider#load_current_resource to denote that + # Used in Resource::ActionClass#load_current_resource to denote that # the resource doesn't actually exist (for example, the file does not exist) class CurrentValueDoesNotExist < RuntimeError; end diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 5c230ad2f4..6c2f91ed8b 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -29,7 +29,7 @@ require 'chef/mixin/convert_to_class_name' require 'chef/guard_interpreter/resource_guard_interpreter' require 'chef/resource/conditional' require 'chef/resource/conditional_action_not_nothing' -require 'chef/resource/action_provider' +require 'chef/resource/action_class' require 'chef/resource_collection' require 'chef/node_map' require 'chef/node' @@ -688,7 +688,7 @@ class Chef # The provider class for this resource. # # If `action :x do ... end` has been declared on this resource or its - # superclasses, this will return the `action_provider_class`. + # superclasses, this will return the `action_class`. # # If this is not set, `provider_for_action` will dynamically determine the # provider. @@ -699,7 +699,7 @@ class Chef # # @return The provider class for this resource. # - # @see Chef::Resource.action_provider_class + # @see Chef::Resource.action_class # def provider(arg=nil) klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) @@ -708,7 +708,7 @@ class Chef arg end set_or_return(:provider, klass, kind_of: [ Class ]) || - self.class.action_provider_class + self.class.action_class end def provider=(arg) provider(arg) @@ -1372,7 +1372,8 @@ class Chef # def self.action(action, &recipe_block) action = action.to_sym - new_action_provider_class.action(action, &recipe_block) + declare_action_class + action_class.action(action, &recipe_block) self.allowed_actions += [ action ] default_action action if Array(default_action) == [:nothing] end @@ -1416,7 +1417,7 @@ class Chef end # - # The action provider class is an automatic `Provider` created to handle + # The action class is an automatic `Provider` created to handle # actions declared by `action :x do ... end`. # # This class will be returned by `resource.provider` if `resource.provider` @@ -1425,40 +1426,38 @@ class Chef # # If the user has not declared actions on this class or its superclasses # using `action :x do ... end`, then there is no need for this class and - # `action_provider_class` will be `nil`. + # `action_class` will be `nil`. # # @api private # - def self.action_provider_class - @action_provider_class || + def self.action_class + @action_class || # If the superclass needed one, then we need one as well. - if superclass.respond_to?(:action_provider_class) && superclass.action_provider_class - new_action_provider_class + if superclass.respond_to?(:action_class) && superclass.action_class + declare_action_class end end # - # Ensure the action provider class actually gets created. This is called + # Ensure the action class actually gets created. This is called # when the user does `action :x do ... end`. # + # If a block is passed, it is run inside the action_class. + # # @api private - def self.new_action_provider_class - return @action_provider_class if @action_provider_class + def self.declare_action_class + return @action_class if @action_class - if superclass.respond_to?(:action_provider_class) - base_provider = superclass.action_provider_class + if superclass.respond_to?(:action_class) + base_provider = superclass.action_class end base_provider ||= Chef::Provider resource_class = self - @action_provider_class = Class.new(base_provider) do - include ActionProvider - define_singleton_method(:to_s) { "#{resource_class} action provider" } - def self.inspect - to_s - end + @action_class = Class.new(base_provider) do + include ActionClass + self.resource_class = resource_class end - @action_provider_class end # diff --git a/lib/chef/resource/action_class.rb b/lib/chef/resource/action_class.rb new file mode 100644 index 0000000000..12211418e9 --- /dev/null +++ b/lib/chef/resource/action_class.rb @@ -0,0 +1,83 @@ +# +# Author:: John Keiser ( 0 + current_resource.load_current_value!(new_resource) + else + current_resource.load_current_value! + end + rescue Chef::Exceptions::CurrentValueDoesNotExist + current_resource = nil + end + end + + @current_resource = current_resource + end + + def self.included(other) + other.extend(ClassMethods) + other.use_inline_resources + other.include_resource_dsl true + end + + module ClassMethods + # + # The Chef::Resource class this ActionClass was declared against. + # + # @return [Class] The Chef::Resource class this ActionClass was declared against. + # + attr_accessor :resource_class + + def to_s + "#{resource_class} action provider" + end + + def inspect + to_s + end + end + end + end +end diff --git a/lib/chef/resource/action_provider.rb b/lib/chef/resource/action_provider.rb deleted file mode 100644 index d71b54ef4d..0000000000 --- a/lib/chef/resource/action_provider.rb +++ /dev/null @@ -1,69 +0,0 @@ -# -# Author:: John Keiser ( 0 - current_resource.load_current_value!(new_resource) - else - current_resource.load_current_value! - end - rescue Chef::Exceptions::CurrentValueDoesNotExist - current_resource = nil - end - end - - @current_resource = current_resource - end - - def self.included(other) - other.extend(ClassMethods) - other.use_inline_resources - other.include_resource_dsl true - end - - module ClassMethods - end - end - end -end diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb index 53611c144f..1c84b986cc 100644 --- a/spec/integration/recipes/resource_action_spec.rb +++ b/spec/integration/recipes/resource_action_spec.rb @@ -4,7 +4,7 @@ describe "Resource.action" do include IntegrationSupport shared_context "ActionJackson" do - it "The default action is the first declared action" do + it "the default action is the first declared action" do converge <<-EOM, __FILE__, __LINE__+1 #{resource_dsl} 'hi' do foo 'foo!' @@ -14,7 +14,7 @@ describe "Resource.action" do expect(ActionJackson.succeeded).to eq true end - it "The action can access recipe DSL" do + it "the action can access recipe DSL" do converge <<-EOM, __FILE__, __LINE__+1 #{resource_dsl} 'hi' do foo 'foo!' @@ -25,7 +25,7 @@ describe "Resource.action" do expect(ActionJackson.succeeded).to eq true end - it "The action can access attributes" do + it "the action can access attributes" do converge <<-EOM, __FILE__, __LINE__+1 #{resource_dsl} 'hi' do foo 'foo!' @@ -36,7 +36,7 @@ describe "Resource.action" do expect(ActionJackson.succeeded).to eq 'foo!' end - it "The action can access public methods" do + it "the action can access public methods" do converge <<-EOM, __FILE__, __LINE__+1 #{resource_dsl} 'hi' do foo 'foo!' @@ -47,7 +47,7 @@ describe "Resource.action" do expect(ActionJackson.succeeded).to eq 'foo_public!' end - it "The action can access protected methods" do + it "the action can access protected methods" do converge <<-EOM, __FILE__, __LINE__+1 #{resource_dsl} 'hi' do foo 'foo!' @@ -58,7 +58,7 @@ describe "Resource.action" do expect(ActionJackson.succeeded).to eq 'foo_protected!' end - it "The action cannot access private methods" do + it "the action cannot access private methods" do expect { converge(<<-EOM, __FILE__, __LINE__+1) #{resource_dsl} 'hi' do @@ -70,7 +70,7 @@ describe "Resource.action" do expect(ActionJackson.ran_action).to eq :access_private_method end - it "The action cannot access resource instance variables" do + it "the action cannot access resource instance variables" do converge <<-EOM, __FILE__, __LINE__+1 #{resource_dsl} 'hi' do foo 'foo!' @@ -81,7 +81,7 @@ describe "Resource.action" do expect(ActionJackson.succeeded).to be_nil end - it "The action does not compile until the prior resource has converged" do + it "the action does not compile until the prior resource has converged" do converge <<-EOM, __FILE__, __LINE__+1 ruby_block 'wow' do block do @@ -98,7 +98,7 @@ describe "Resource.action" do expect(ActionJackson.succeeded).to eq 'ruby_block_converged!' end - it "The action's resources converge before the next resource converges" do + it "the action's resources converge before the next resource converges" do converge <<-EOM, __FILE__, __LINE__+1 #{resource_dsl} 'hi' do foo 'foo!' @@ -214,7 +214,7 @@ describe "Resource.action" do end end - context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute and action" do + context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute, action and custom method" do before(:context) { class ActionJackalope < ActionJackson use_automatic_resource_name @@ -228,6 +228,7 @@ describe "Resource.action" do @bar end class < Date: Thu, 17 Sep 2015 18:53:09 -0700 Subject: Lazy load MSI provider, add check for MSI support --- lib/chef/platform/query_helpers.rb | 16 +++++++++++ lib/chef/provider/package/windows/msi.rb | 4 +-- spec/unit/platform/query_helpers_spec.rb | 49 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index 9ba8d2261d..dfb99ed750 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -58,6 +58,22 @@ class Chef return nano == 1 end + def supports_msi? + return false unless windows? + require 'win32/registry' + + key = "System\\CurrentControlSet\\Services\\msiserver" + access = ::Win32::Registry::KEY_QUERY_VALUE + + begin + ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg| + true + end + rescue ::Win32::Registry::Error + false + end + end + def supports_powershell_execution_bypass?(node) node[:languages] && node[:languages][:powershell] && node[:languages][:powershell][:version].to_i >= 3 diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb index 31faa78215..7fdbbcff35 100644 --- a/lib/chef/provider/package/windows/msi.rb +++ b/lib/chef/provider/package/windows/msi.rb @@ -18,7 +18,7 @@ # TODO: Allow @new_resource.source to be a Product Code as a GUID for uninstall / network install -require 'chef/win32/api/installer' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ +require 'chef/win32/api/installer' if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi? require 'chef/mixin/shell_out' class Chef @@ -26,7 +26,7 @@ class Chef class Package class Windows class MSI - include Chef::ReservedNames::Win32::API::Installer if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + include Chef::ReservedNames::Win32::API::Installer if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi? include Chef::Mixin::ShellOut def initialize(resource) diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index b5c56e8f9a..d18b6f7902 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -102,6 +102,55 @@ describe "Chef::Platform#windows_nano_server?" do end end +describe "Chef::Platform#supports_msi?" do + include_context "Win32" # clear and restore Win32:: namespace + + let(:key) { "System\\CurrentControlSet\\Services\\msiserver" } + let(:key_query_value) { 0x0001 } + let(:access) { key_query_value } + let(:hive) { double("Win32::Registry::HKEY_LOCAL_MACHINE") } + let(:registry) { double("Win32::Registry") } + + before(:all) do + Win32::Registry = Class.new + Win32::Registry::Error = Class.new(RuntimeError) + end + + before do + Win32::Registry::HKEY_LOCAL_MACHINE = hive + Win32::Registry::KEY_QUERY_VALUE = key_query_value + end + + after do + Win32::Registry.send(:remove_const, 'HKEY_LOCAL_MACHINE') if defined?(Win32::Registry::HKEY_LOCAL_MACHINE) + Win32::Registry.send(:remove_const, 'KEY_QUERY_VALUE') if defined?(Win32::Registry::KEY_QUERY_VALUE) + end + + it "returns false early when not on windows" do + allow(ChefConfig).to receive(:windows?).and_return(false) + expect(Chef::Platform).to_not receive(:require) + expect(Chef::Platform.supports_msi?).to be false + end + + it "returns true when the registry key exists" do + allow(ChefConfig).to receive(:windows?).and_return(true) + allow(Chef::Platform).to receive(:require).with('win32/registry') + expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). + with(key, access). + and_yield(registry) + expect(Chef::Platform.supports_msi?).to be true + end + + it "returns false when the registry key does not exist" do + allow(ChefConfig).to receive(:windows?).and_return(true) + allow(Chef::Platform).to receive(:require).with('win32/registry') + expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). + with(key, access). + and_raise(Win32::Registry::Error, "The system cannot find the file specified.") + expect(Chef::Platform.supports_msi?).to be false + end +end + describe 'Chef::Platform#supports_dsc?' do it 'returns false if powershell is not present' do node = Chef::Node.new -- cgit v1.2.1 From d8f7ca4d08f00db96fbf1e98a7f1ed763c7bab01 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 22 Sep 2015 22:15:19 -0700 Subject: Honor the ordering of whichever `name_attribute` or `default` comes first --- lib/chef/property.rb | 15 +++++++++++++++ spec/unit/property_spec.rb | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 09198d90f1..5b6a969d5f 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -86,6 +86,21 @@ class Chef # def initialize(**options) options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } + # Only pick the first of :default, :name_property and :name_attribute if + # more than one is specified. + found_default = false + options.reject! do |k,v| + if [ :name_property, :name_attribute, :default ].include?(k) + if found_default + true + else + found_default = true + false + end + else + false + end + end options[:name_property] = options.delete(:name_attribute) if options.has_key?(:name_attribute) && !options.has_key?(:name_property) @options = options diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index f758b5f403..533f0f4e4d 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -952,8 +952,8 @@ describe "Chef::Resource.property" do end end with_property ":x, #{name}: true, default: 10" do - it "chooses default over #{name}" do - expect(resource.x).to eq 10 + it "chooses #{name} over default" do + expect(resource.x).to eq 'blah' end end with_property ":x, #{name}: true, required: true" do -- cgit v1.2.1 From 71fc5c5650f8bd3ec819087d6a5ca7f4eaeb1158 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 22 Sep 2015 22:39:41 -0700 Subject: Add deprecation warning for properties that specify default and name_property --- lib/chef/property.rb | 16 +++++++++------- lib/chef/resource.rb | 2 ++ spec/unit/property_spec.rb | 25 +++++++++++++++++++------ 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 5b6a969d5f..7f42616bdd 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -86,22 +86,24 @@ class Chef # def initialize(**options) options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } + # Only pick the first of :default, :name_property and :name_attribute if # more than one is specified. - found_default = false + found_defaults = [] options.reject! do |k,v| if [ :name_property, :name_attribute, :default ].include?(k) - if found_default - true - else - found_default = true - false - end + found_defaults << k + # Reject all but the first default key you find + found_defaults.size > 1 else false end end + if found_defaults.size > 1 + Chef::Log.deprecation("Cannot specify keys #{found_defaults.join(", ")} together on property #{options[:name]}--only the first one (#{found_defaults[0]}) will be obeyed. Please pick one.", caller(5..5)[0]) + end options[:name_property] = options.delete(:name_attribute) if options.has_key?(:name_attribute) && !options.has_key?(:name_property) + @options = options options[:name] = options[:name].to_sym if options[:name] diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 6c2f91ed8b..9e40fdccfd 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -782,6 +782,8 @@ class Chef def self.property(name, type=NOT_PASSED, **options) name = name.to_sym + options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } + options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name) options.merge!(name: name, declared_in: self) diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 533f0f4e4d..369c065aaa 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -946,14 +946,27 @@ describe "Chef::Resource.property" do expect(resource.x).to eq 'blah' end end - with_property ":x, default: 10, #{name}: true" do - it "chooses default over #{name}" do - expect(resource.x).to eq 10 + context "default ordering deprecation warnings" do + it "emits a deprecation warning for property :x, default: 10, #{name}: true" do + expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Cannot specify keys default, #{name} together on property x--only the first one \(default\) will be obeyed. Please pick one./ + end + it "emits a deprecation warning for property :x, #{name}: true, default: 10" do + expect { resource_class.property :x, name.to_sym => true, :default => 10 }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Cannot specify keys #{name}, default together on property x--only the first one \(#{name}\) will be obeyed. Please pick one./ end end - with_property ":x, #{name}: true, default: 10" do - it "chooses #{name} over default" do - expect(resource.x).to eq 'blah' + context "default ordering" do + before { Chef::Config[:treat_deprecation_warnings_as_errors] = false } + with_property ":x, default: 10, #{name}: true" do + it "chooses default over #{name}" do + expect(resource.x).to eq 10 + end + end + with_property ":x, #{name}: true, default: 10" do + it "chooses #{name} over default" do + expect(resource.x).to eq 'blah' + end end end with_property ":x, #{name}: true, required: true" do -- cgit v1.2.1 From 2ded4c34fdb2bf8b1f2caa8213cacd022db7084a Mon Sep 17 00:00:00 2001 From: John Keiser Date: Wed, 23 Sep 2015 11:22:44 -0700 Subject: Run the chef service executable from the bin directory we are currently running from instead of guessing where it is --- bin/chef-service-manager | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/chef-service-manager b/bin/chef-service-manager index 7c031f70d4..5808a0be46 100755 --- a/bin/chef-service-manager +++ b/bin/chef-service-manager @@ -28,7 +28,7 @@ if Chef::Platform.windows? :service_name => "chef-client", :service_display_name => "Chef Client Service", :service_description => "Runs Chef Client on regular, configurable intervals.", - :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../../../bin/chef-windows-service')), + :service_file_path => File.expand_path('../chef-windows-service', $PROGRAM_NAME), :delayed_start => true, :dependencies => ['Winmgmt'] } -- cgit v1.2.1 From efe6286540a56960e109dcc65db63cc7461c1753 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Wed, 23 Sep 2015 12:48:31 -0700 Subject: Re-upgrade chef-zero to latest --- Gemfile | 4 ---- lib/chef/chef_fs/data_handler/client_data_handler.rb | 4 +++- lib/chef/chef_fs/file_system/chef_server_root_dir.rb | 3 ++- lib/chef/chef_fs/file_system/organization_members_entry.rb | 4 ++-- spec/integration/knife/download_spec.rb | 4 ++++ spec/integration/knife/list_spec.rb | 8 ++++++++ spec/integration/knife/upload_spec.rb | 2 +- 7 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index edbd853c47..2b135fb8e8 100644 --- a/Gemfile +++ b/Gemfile @@ -5,10 +5,6 @@ gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby" gem 'chef-config', path: "chef-config" -# We are pinning chef-zero to 4.2.x until ChefFS can deal -# with V1 api calls or chef-zero supports both v0 and v1 -gem "chef-zero", "~> 4.2.3" - group(:docgen) do gem "yard" end diff --git a/lib/chef/chef_fs/data_handler/client_data_handler.rb b/lib/chef/chef_fs/data_handler/client_data_handler.rb index d81f35e861..5bcbd4e373 100644 --- a/lib/chef/chef_fs/data_handler/client_data_handler.rb +++ b/lib/chef/chef_fs/data_handler/client_data_handler.rb @@ -13,11 +13,13 @@ class Chef 'validator' => false, 'chef_type' => 'client' } + # Handle the fact that admin/validator have changed type from string -> boolean + client['admin'] = (client['admin'] == 'true') if client['admin'].is_a?(String) + client['validator'] = (client['validator'] == 'true') if client['validator'].is_a?(String) if entry.respond_to?(:org) && entry.org defaults['orgname'] = entry.org end result = normalize_hash(client, defaults) - # You can NOT send json_class, or it will fail result.delete('json_class') result end diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb index e3ffd644ad..a243e0ae6b 100644 --- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb +++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb @@ -120,7 +120,8 @@ class Chef if File.dirname(path) == '/organizations' File.basename(path) else - nil + # In Chef 12, everything is in an org. + 'chef' end end end diff --git a/lib/chef/chef_fs/file_system/organization_members_entry.rb b/lib/chef/chef_fs/file_system/organization_members_entry.rb index 94393b341f..40042a9cbc 100644 --- a/lib/chef/chef_fs/file_system/organization_members_entry.rb +++ b/lib/chef/chef_fs/file_system/organization_members_entry.rb @@ -39,9 +39,9 @@ class Chef members = minimize_value(_read_json) (desired_members - members).each do |member| begin - rest.post(File.join(api_path, member), {}) + rest.post(api_path, 'username' => member) rescue Net::HTTPServerException => e - if e.response.code == '404' + if %w(404 405).include?(e.response.code) raise "Chef server at #{api_path} does not allow you to directly add members. Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json." else raise diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb index c87e6fe20a..8842ed5ac4 100644 --- a/spec/integration/knife/download_spec.rb +++ b/spec/integration/knife/download_spec.rb @@ -1113,11 +1113,13 @@ Created /acls/clients/foo-validator.json Created /acls/containers Created /acls/containers/clients.json Created /acls/containers/containers.json +Created /acls/containers/cookbook_artifacts.json Created /acls/containers/cookbooks.json Created /acls/containers/data.json Created /acls/containers/environments.json Created /acls/containers/groups.json Created /acls/containers/nodes.json +Created /acls/containers/policies.json Created /acls/containers/roles.json Created /acls/containers/sandboxes.json Created /acls/containers/x.json @@ -1139,11 +1141,13 @@ Created /clients/foo-validator.json Created /containers Created /containers/clients.json Created /containers/containers.json +Created /containers/cookbook_artifacts.json Created /containers/cookbooks.json Created /containers/data.json Created /containers/environments.json Created /containers/groups.json Created /containers/nodes.json +Created /containers/policies.json Created /containers/roles.json Created /containers/sandboxes.json Created /containers/x.json diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb index 911b56ef18..b289642c7d 100644 --- a/spec/integration/knife/list_spec.rb +++ b/spec/integration/knife/list_spec.rb @@ -702,11 +702,13 @@ foo-validator.json /acls/containers: clients.json containers.json +cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json +policies.json roles.json sandboxes.json @@ -733,11 +735,13 @@ foo-validator.json /containers: clients.json containers.json +cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json +policies.json roles.json sandboxes.json @@ -804,11 +808,13 @@ foo-validator.json /acls/containers: clients.json containers.json +cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json +policies.json roles.json sandboxes.json @@ -835,11 +841,13 @@ foo-validator.json /containers: clients.json containers.json +cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json +policies.json roles.json sandboxes.json diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb index 826ecec364..6ca8c3d8ce 100644 --- a/spec/integration/knife/upload_spec.rb +++ b/spec/integration/knife/upload_spec.rb @@ -198,7 +198,7 @@ Created /nodes/y.json Created /roles/y.json Created /users/y.json EOM - knife('diff --name-status /').should_succeed '' + knife('diff /').should_succeed '' end it 'knife upload --no-diff adds the new files' do -- cgit v1.2.1 From e92665d448eaaf92fdfb5bb3cdb4be3a2ddad9ac Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Tue, 22 Sep 2015 15:40:34 -0700 Subject: Add policyfile support to bootstrap context --- lib/chef/knife/core/bootstrap_context.rb | 7 ++++++- spec/unit/knife/core/bootstrap_context_spec.rb | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 867b6fe366..d85ee91490 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -164,7 +164,12 @@ CONFIG def first_boot (@config[:first_boot_attributes] || {}).tap do |attributes| - attributes.merge!(:run_list => @run_list) + if @config[:policy_name] && @config[:policy_group] + attributes.merge!(:policy_name => @config[:policy_name], :policy_group => @config[:policy_group]) + else + attributes.merge!(:run_list => @run_list) + end + attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty? end end diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 268b61c756..a93e5362a9 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -117,6 +117,16 @@ EXPECTED end end + describe "when policy_name and policy_group are present in config" do + + let(:config) { { policy_name: "my_app_server", policy_group: "staging" } } + + it "includes them in the first_boot data and excludes run_list" do + expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json(config)) + end + + end + describe "when an encrypted_data_bag_secret is provided" do let(:secret) { "supersekret" } it "reads the encrypted_data_bag_secret" do -- cgit v1.2.1 From 4141c33df8e31b78359fb316315547d36f837df6 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Tue, 22 Sep 2015 17:22:32 -0700 Subject: Add --policy-name and --policy-group opts to knife bootstrap --- lib/chef/knife/bootstrap.rb | 35 +++++++++++++++++++++++ spec/unit/knife/bootstrap_spec.rb | 58 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index f173b6b909..93236225a2 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -143,6 +143,16 @@ class Chef :proc => lambda { |o| o.split(/[\s,]+/) }, :default => [] + option :policy_name, + :long => "--policy-name POLICY_NAME", + :description => "Policyfile name to use (--policy-group must also be given)", + :default => nil + + option :policy_group, + :long => "--policy-group POLICY_GROUP", + :description => "Policy group name to use (--policy-name must also be given)", + :default => nil + option :tags, :long => "--tags TAGS", :description => "Comma separated list of tags to apply to the node", @@ -315,6 +325,7 @@ class Chef def run validate_name_args! + validate_options! $stdout.sync = true @@ -363,6 +374,17 @@ class Chef end end + def validate_options! + if incomplete_policyfile_options? + ui.error("--policy-name and --policy-group must be specified together") + exit 1 + elsif policyfile_and_run_list_given? + ui.error("Policyfile options and --run-list are exclusive") + exit 1 + end + true + end + def knife_ssh ssh = Chef::Knife::Ssh.new ssh.ui = ui @@ -395,6 +417,19 @@ class Chef command end + + private + + # True if policy_name and run_list are both given + def policyfile_and_run_list_given? + !config[:run_list].empty? && !!config[:policy_name] + end + + # True if one of policy_name or policy_group was given, but not both + def incomplete_policyfile_options? + (!!config[:policy_name] ^ config[:policy_group]) + end + end end end diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index 0195e6d406..48aae3e61b 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -250,14 +250,14 @@ describe Chef::Knife::Bootstrap do it "should create a hint file when told to" do knife.parse_options(["--hint", "openstack"]) knife.merge_configs - expect(knife.render_template).to match /\/etc\/chef\/ohai\/hints\/openstack.json/ + expect(knife.render_template).to match(/\/etc\/chef\/ohai\/hints\/openstack.json/) end it "should populate a hint file with JSON when given a file to read" do allow(::File).to receive(:read).and_return('{ "foo" : "bar" }') knife.parse_options(["--hint", "openstack=hints/openstack.json"]) knife.merge_configs - expect(knife.render_template).to match /\{\"foo\":\"bar\"\}/ + expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/) end end @@ -395,6 +395,58 @@ describe Chef::Knife::Bootstrap do end end + describe "handling policyfile options" do + + context "when only policy_name is given" do + + let(:bootstrap_cli_options) { %w[ --policy-name my-app-server ] } + + it "returns an error stating that policy_name and policy_group must be given together" do + expect { knife.validate_options! }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together") + end + + end + + context "when only policy_group is given" do + + let(:bootstrap_cli_options) { %w[ --policy-group staging ] } + + it "returns an error stating that policy_name and policy_group must be given together" do + expect { knife.validate_options! }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together") + end + + end + + context "when both policy_name and policy_group are given, but run list is also given" do + + let(:bootstrap_cli_options) { %w[ --policy-name my-app --policy-group staging --run-list cookbook ] } + + it "returns an error stating that policyfile and run_list are exclusive" do + expect { knife.validate_options! }.to raise_error(SystemExit) + expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive") + end + + end + + context "when policy_name and policy_group are given with no conflicting options" do + + let(:bootstrap_cli_options) { %w[ --policy-name my-app --policy-group staging ] } + + it "passes options validation" do + expect { knife.validate_options! }.to_not raise_error + end + + it "passes them into the bootstrap context" do + expect(knife.bootstrap_context.first_boot).to have_key(:policy_name) + expect(knife.bootstrap_context.first_boot).to have_key(:policy_group) + end + + end + + end + describe "when configuring the underlying knife ssh command" do context "from the command line" do let(:knife_ssh) do @@ -525,7 +577,7 @@ describe Chef::Knife::Bootstrap do it "verifies that a server to bootstrap was given as a command line arg" do knife.name_args = nil expect { knife.run }.to raise_error(SystemExit) - expect(stderr.string).to match /ERROR:.+FQDN or ip/ + expect(stderr.string).to match(/ERROR:.+FQDN or ip/) end describe "when running the bootstrap" do -- cgit v1.2.1 From 14d7baab6365e0b4146990a5306f80a7f0d09a82 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Wed, 23 Sep 2015 12:50:54 -0700 Subject: Don't pass -E to chef-client unless given an environment Chef::PolicyBuilder::Policyfile requires `Chef::Config[:environment]` to be nil or empty, in order to prevent confusion that could arise from using two conflicting features. Because `chef-client` merges CLI options to `Chef::Config` automatically, running `chef-cient -E _default` causes `Chef::Config[:environment]` to be non-nil, resulting in chef-client emitting this error when bootstrapping: ``` 192.168.99.143 Unexpected Error: 192.168.99.143 ----------------- 192.168.99.143 Chef::PolicyBuilder::Policyfile::UnsupportedFeature: Policyfile does not work with Chef Environments ``` For non-policyfile users, this should behave the same as before since Chef will just default to the `_default` environment (this gets set via Node#initialize) if none is specified. --- lib/chef/knife/core/bootstrap_context.rb | 4 ++-- spec/unit/knife/core/bootstrap_context_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index d85ee91490..d210b9418f 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -40,7 +40,7 @@ class Chef end def bootstrap_environment - @chef_config[:environment] || '_default' + @chef_config[:environment] end def validation_key @@ -128,7 +128,7 @@ CONFIG client_path = @chef_config[:chef_client_path] || 'chef-client' s = "#{client_path} -j /etc/chef/first-boot.json" s << ' -l debug' if @config[:verbosity] and @config[:verbosity] >= 2 - s << " -E #{bootstrap_environment}" + s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil? s end diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index a93e5362a9..0433ef9983 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -38,14 +38,14 @@ describe Chef::Knife::Core::BootstrapContext do expect{described_class.new(config, run_list, chef_config)}.not_to raise_error end - it "runs chef with the first-boot.json in the _default environment" do - expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -E _default" + it "runs chef with the first-boot.json with no environment specified" do + expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json" end describe "when in verbosity mode" do let(:config) { {:verbosity => 2} } it "adds '-l debug' when verbosity is >= 2" do - expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug -E _default" + expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug" end end @@ -70,7 +70,7 @@ EXPECTED describe "alternate chef-client path" do let(:chef_config){ {:chef_client_path => '/usr/local/bin/chef-client'} } it "runs chef-client from another path when specified" do - expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json -E _default" + expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json" end end -- cgit v1.2.1 From 33509c1bbb7366d3e2a50f61d8d53161e51eb6fa Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 24 Sep 2015 10:08:59 -0700 Subject: Add policyfile attributes to client builder --- lib/chef/knife/bootstrap/client_builder.rb | 12 ++++++++++++ spec/unit/knife/bootstrap/client_builder_spec.rb | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb index 59b0cabd49..7eb1e22628 100644 --- a/lib/chef/knife/bootstrap/client_builder.rb +++ b/lib/chef/knife/bootstrap/client_builder.rb @@ -91,6 +91,16 @@ class Chef knife_config[:run_list] end + # @return [String] policy_name from the knife_config + def policy_name + knife_config[:policy_name] + end + + # @return [String] policy_group from the knife_config + def policy_group + knife_config[:policy_group] + end + # @return [Hash,Array] Object representation of json first-boot attributes from the knife_config def first_boot_attributes knife_config[:first_boot_attributes] @@ -141,6 +151,8 @@ class Chef node.run_list(normalized_run_list) node.normal_attrs = first_boot_attributes if first_boot_attributes node.environment(environment) if environment + node.policy_name = policy_name if policy_name + node.policy_group = policy_group if policy_group (knife_config[:tags] || []).each do |tag| node.tags << tag end diff --git a/spec/unit/knife/bootstrap/client_builder_spec.rb b/spec/unit/knife/bootstrap/client_builder_spec.rb index 930ae8c9d3..e7232fe8d6 100644 --- a/spec/unit/knife/bootstrap/client_builder_spec.rb +++ b/spec/unit/knife/bootstrap/client_builder_spec.rb @@ -190,5 +190,16 @@ describe Chef::Knife::Bootstrap::ClientBuilder do expect(node).to receive(:run_list).with([]) client_builder.run end + + it "builds a node with policy_name and policy_group when given" do + knife_config[:policy_name] = "my-app" + knife_config[:policy_group] = "staging" + + expect(node).to receive(:run_list).with([]) + expect(node).to receive(:policy_name=).with("my-app") + expect(node).to receive(:policy_group=).with("staging") + + client_builder.run + end end end -- cgit v1.2.1 From 37df1cf0bcc8405ba9d01245dd8cf6ebcceb426b Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 24 Sep 2015 13:28:34 -0700 Subject: Remove experimental feature warning for policyfiles Also, improve the language around unsupported features/options. --- lib/chef/policy_builder/policyfile.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb index bb9593eb36..d6dcdf67b2 100644 --- a/lib/chef/policy_builder/policyfile.rb +++ b/lib/chef/policy_builder/policyfile.rb @@ -68,22 +68,20 @@ class Chef @node = nil - Chef::Log.warn("Using experimental Policyfile feature") - if Chef::Config[:solo] - raise UnsupportedFeature, "Policyfile does not support chef-solo at this time." + raise UnsupportedFeature, "Policyfile does not support chef-solo. Use chef-client local mode instead." end if override_runlist - raise UnsupportedFeature, "Policyfile does not support override run lists at this time" + raise UnsupportedFeature, "Policyfile does not support override run lists. Use named run_lists instead." end if json_attribs && json_attribs.key?("run_list") - raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data at this time" + raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data." end if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty? - raise UnsupportedFeature, "Policyfile does not work with Chef Environments" + raise UnsupportedFeature, "Policyfile does not work with Chef Environments." end end -- cgit v1.2.1 From a37c95afa90666574e6335f8212e0f8fcd4b6f4c Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Thu, 24 Sep 2015 15:04:38 -0700 Subject: Derive locations from expanded path to config file This resolves an issue where running `chef-client -c client.rb -z` will attempt to create the local mode cache at the filesystem root with an error like: ``` ERROR: Permission denied @ dir_s_mkdir - /local-mode-cache ``` --- chef-config/lib/chef-config/config.rb | 2 +- chef-config/spec/unit/config_spec.rb | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb index 32058f283a..2405b127f3 100644 --- a/chef-config/lib/chef-config/config.rb +++ b/chef-config/lib/chef-config/config.rb @@ -68,7 +68,7 @@ module ChefConfig default(:config_dir) do if config_file - PathHelper.dirname(config_file) + PathHelper.dirname(PathHelper.canonical_path(config_file, false)) else PathHelper.join(user_home, ".chef", "") end diff --git a/chef-config/spec/unit/config_spec.rb b/chef-config/spec/unit/config_spec.rb index 395fa2618e..bc35fbee69 100644 --- a/chef-config/spec/unit/config_spec.rb +++ b/chef-config/spec/unit/config_spec.rb @@ -301,14 +301,36 @@ RSpec.describe ChefConfig::Config do describe "setting the config dir" do + context "when the config file is given with a relative path" do + + before do + ChefConfig::Config.config_file = "client.rb" + end + + it "expands the path when determining config_dir" do + # config_dir goes through PathHelper.canonical_path, which + # downcases on windows because the FS is case insensitive, so we + # have to downcase expected and actual to make the tests work. + expect(ChefConfig::Config.config_dir.downcase).to eq(to_platform(Dir.pwd).downcase) + end + + it "does not set derived paths at FS root" do + ChefConfig::Config.local_mode = true + expect(ChefConfig::Config.cache_path.downcase).to eq(to_platform(File.join(Dir.pwd, 'local-mode-cache')).downcase) + end + + end + context "when the config file is /etc/chef/client.rb" do before do - ChefConfig::Config.config_file = to_platform("/etc/chef/client.rb") + config_location = to_platform("/etc/chef/client.rb").downcase + allow(File).to receive(:absolute_path).with(config_location).and_return(config_location) + ChefConfig::Config.config_file = config_location end it "config_dir is /etc/chef" do - expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef")) + expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef").downcase) end context "and chef is running in local mode" do @@ -317,7 +339,7 @@ RSpec.describe ChefConfig::Config do end it "config_dir is /etc/chef" do - expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef")) + expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef").downcase) end end -- cgit v1.2.1 From e84d5f2f697c51de82b0b83e0892c8d869ae6227 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 25 Sep 2015 08:03:38 -0700 Subject: Make race condition tests explicit Fixes issue where tests expected a file to have a PID, but sometimes it would race and be empty --- spec/functional/run_lock_spec.rb | 360 ++++++++++++++++++++++++++++----------- 1 file changed, 260 insertions(+), 100 deletions(-) diff --git a/spec/functional/run_lock_spec.rb b/spec/functional/run_lock_spec.rb index 0cb8635256..ce0598bead 100644 --- a/spec/functional/run_lock_spec.rb +++ b/spec/functional/run_lock_spec.rb @@ -35,15 +35,17 @@ describe Chef::RunLock do # make sure to start with a clean slate. before(:each){ FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) } - after(:each){ FileUtils.rm_r(random_temp_root) } + after(:each){ FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) } + WAIT_ON_LOCK_TIME = 1.0 def wait_on_lock - tries = 0 - until File.exist?(lockfile) - raise "Lockfile never created, abandoning test" if tries > 10 - tries += 1 - sleep 0.1 + Timeout::timeout(WAIT_ON_LOCK_TIME) do + until File.exist?(lockfile) + sleep 0.1 + end end + rescue Timeout::Error + raise "Lockfile never created, abandoning test" end ## @@ -142,103 +144,143 @@ describe Chef::RunLock do results_write.close unless results_write.closed? end - # writes the message to the results pipe for later checking. - # note that nothing accounts for the pipe filling and waiting forever on a - # read or write call, so don't put too much data in. - def record(message) - results_write.puts(message) - results_write.flush - end - - def results - results_write.flush - results_write.close - message = results_read.read - results_read.close - message - end - - ## - # Run lock is the system under test - let!(:run_lock) { Chef::RunLock.new(lockfile) } - - it "creates the full path to the lockfile" do - expect { run_lock.acquire }.not_to raise_error - expect(File).to exist(lockfile) - end + CLIENT_PROCESS_TIMEOUT = 2 + BREATHING_ROOM = 1 - it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do - run_lock.acquire - expect(run_lock.runlock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC) + # ClientProcess is defined below + let!(:p1) { ClientProcess.new(self, 'p1') } + let!(:p2) { ClientProcess.new(self, 'p2') } + after do + p1.stop + p2.stop end - it "allows only one chef client run per lockfile" do - # First process, gets the lock and keeps it. - p1 = fork do - run_lock.acquire - record "p1 has lock" - # Wait until the other process is trying to get the lock: - sync_wait - # sleep a little bit to make process p2 wait on the lock - sleep 2 - record "p1 releasing lock" - run_lock.release - exit!(0) + context "when the lockfile does not already exist" do + context "when a client acquires the lock but has not yet saved the pid" do + before { p1.run_to("acquired lock") } + + it "the lockfile is created" do + expect(File.exist?(lockfile)).to be_truthy + end + + it "the lockfile is locked" do + run_lock = Chef::RunLock.new(lockfile) + begin + expect(run_lock.test).to be_falsey + ensure + run_lock.release + end + end + + it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do + run_lock = File.open(lockfile) + expect(run_lock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC) + end + + it "the lockfile is empty" do + expect(IO.read(lockfile)).to eq('') + end + + it "and a second client tries to acquire the lock, it doesn't get the lock until *after* the first client exits" do + # Start p2 and tell it to move forward in the background + p2.run_to("acquired lock") do + # While p2 is trying to acquire, wait a bit and then let p1 complete + sleep(BREATHING_ROOM) + expect(p2.last_event).to eq("started") + p1.run_to_completion + end + + p2.run_to_completion + end + + it "and a second client tries to get the lock and the first is killed, the second client gets the lock immediately" do + p2.run_to("acquired lock") do + sleep BREATHING_ROOM + expect(p2.last_event).to eq("started") + p1.stop + end + p2.run_to_completion + end end - # Wait until p1 creates the lockfile - wait_on_lock - - p2 = fork do - # inform process p1 that we're trying to get the lock - sync_send - run_lock.acquire - record "p2 has lock" - run_lock.release - exit!(0) - end - - Process.waitpid2(p1) - Process.waitpid2(p2) - - raise_side_channel_error! - - expected=<<-E -p1 has lock -p1 releasing lock -p2 has lock -E - expect(results).to eq(expected) - end - - it "clears the lock if the process dies unexpectedly" do - p1 = fork do - run_lock.acquire - record "p1 has lock" - sleep 60 - record "p1 still has lock" - exit! 1 + context "when a client acquires the lock and saves the pid" do + before { p1.run_to("saved pid") } + + it "the lockfile is created" do + expect(File.exist?(lockfile)).to be_truthy + end + + it "the lockfile is locked" do + run_lock = Chef::RunLock.new(lockfile) + begin + expect(run_lock.test).to be_falsey + ensure + run_lock.release + end + end + + it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do + run_lock = File.open(lockfile) + expect(run_lock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC) + end + + it "the PID is in the lockfile" do + expect(IO.read(lockfile)).to eq p1.pid.to_s + end + + it "and a second client tries to acquire the lock, it doesn't get the lock until *after* the first client exits" do + # Start p2 and tell it to move forward in the background + p2.run_to("acquired lock") do + # While p2 is trying to acquire, wait a bit and then let p1 complete + sleep(BREATHING_ROOM) + expect(p2.last_event).to eq("started") + p1.run_to_completion + end + + p2.run_to_completion + end + + it "when a second client tries to get the lock and the first is killed, the second client gets the lock immediately" do + p2.run_to("acquired lock") do + sleep BREATHING_ROOM + expect(p2.last_event).to eq("started") + p1.stop + end + p2.run_to_completion + end end - wait_on_lock - Process.kill(:KILL, p1) - Process.waitpid2(p1) - - p2 = fork do - run_lock.acquire - record "p2 has lock" - run_lock.release - exit! 0 + context "when a client acquires a lock and exits normally" do + before { p1.run_to_completion } + + it "the lockfile remains" do + expect(File.exist?(lockfile)).to be_truthy + end + + it "the lockfile is not locked" do + run_lock = Chef::RunLock.new(lockfile) + begin + expect(run_lock.test).to be_truthy + ensure + run_lock.release + end + end + + it "the PID is in the lockfile" do + expect(IO.read(lockfile)).to eq p1.pid.to_s + end + + it "and a second client tries to acquire the lock, it gets the lock immediately" do + p2.run_to_completion + end end - - Process.waitpid2(p2) - - expect(results).to match(/p2 has lock\Z/) end it "test returns true and acquires the lock" do + run_lock = Chef::RunLock.new(lockfile) p1 = fork do expect(run_lock.test).to eq(true) + run_lock.save_pid sleep 2 exit! 1 end @@ -255,8 +297,10 @@ E end it "test returns without waiting when the lock is acquired" do + run_lock = Chef::RunLock.new(lockfile) p1 = fork do run_lock.acquire + run_lock.save_pid sleep 2 exit! 1 end @@ -267,20 +311,136 @@ E Process.waitpid2(p1) end - it "doesn't truncate the lock file so that contents can be read" do - p1 = fork do - run_lock.acquire - run_lock.save_pid - sleep 2 - exit! 1 + end + + # + # Runs a process in the background that will: + # + # 1. start up (`started` event) + # 2. acquire the runlock file (`acquired lock` event) + # 3. save the pid to the lockfile (`saved pid` event) + # 4. exit + # + # You control exactly how far the client process goes with the `run_to` + # method: it will stop at any given spot so you can test for race conditions. + # + # It uses a pair of pipes to communicate with the process. The tests will + # send an event name over to the process, which gives the process permission + # to run until it reaches that event (at which point it waits for another event + # name). The process sends the name of each event it reaches back to the tests. + # + class ClientProcess + def initialize(example, name) + @example = example + @name = name + @read_from_process, @write_to_tests = IO.pipe + @read_from_tests, @write_to_process = IO.pipe + end + + attr_reader :example + attr_reader :name + attr_reader :pid + + def fire_event(event) + # Let the caller know what event we've reached + write_to_tests.puts(event) + + # Block until the client tells us where to stop + if !@run_to_event || event == @run_to_event + @run_to_event = read_from_tests.gets.strip end + end - wait_on_lock - sleep 0.5 # Possible race condition on Solaris which pid is observed as 0 - expect(File.read(lockfile)).to eq(p1.to_s) + def last_event + while true + event = readline_nonblock(read_from_process) + break if event.nil? + @last_event = event.strip + end + @last_event + end - Process.waitpid2(p1) + def run_to(event, &background_block) + # Start the process if it's not started + start if !pid + + # Tell the process what to stop at (also means it can go) + write_to_process.puts event + + # Run the background block + background_block.call if background_block + + # Wait until it gets there + Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do + until @last_event == event + @last_event = read_from_process.gets.strip + end + end + end + + def run_to_completion + # Start the process if it's not started + start if !pid + + # Tell the process to stop at nothing (no blocking) + @write_to_process.puts "nothing" + + # Wait for the process to exit + wait_for_exit + end + + def wait_for_exit + Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do + Process.wait(pid) if pid + end + end + + def stop + if pid + begin + Process.kill(:KILL, pid) + Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do + Process.waitpid2(pid) + end + # Process not found is perfectly fine when we're trying to kill a process :) + rescue Errno::ESRCH + end + end + end + + private + + attr_reader :read_from_process + attr_reader :write_to_tests + attr_reader :read_from_tests + attr_reader :write_to_process + + def start + @pid = fork do + Timeout::timeout(CLIENT_PROCESS_TIMEOUT) do + run_lock = Chef::RunLock.new(example.lockfile) + fire_event("started") + run_lock.acquire + fire_event("acquired lock") + run_lock.save_pid + fire_event("saved pid") + exit!(0) + end + end end + def readline_nonblock(fd) + buffer = "" + buffer << fd.read_nonblock(1) while buffer[-1] != "\n" + + buffer + #rescue IO::EAGAINUnreadable + rescue IO::WaitReadable + unless buffer == "" + sleep 0.1 + retry + end + nil + end end end -- cgit v1.2.1 From c576de210dc42889d796074187a109a8d0dd6a19 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Thu, 24 Sep 2015 18:19:33 -0700 Subject: Prefer name_property: true over default: nil --- lib/chef/property.rb | 23 ++++++++++++----------- spec/unit/property_spec.rb | 22 ++++++++++++++++++++-- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 7f42616bdd..6c9d4619b1 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -89,18 +89,19 @@ class Chef # Only pick the first of :default, :name_property and :name_attribute if # more than one is specified. - found_defaults = [] - options.reject! do |k,v| - if [ :name_property, :name_attribute, :default ].include?(k) - found_defaults << k - # Reject all but the first default key you find - found_defaults.size > 1 - else - false - end - end + found_defaults = options.keys.select { |k| [ :default, :name_attribute, :name_property ].include?(k) } if found_defaults.size > 1 - Chef::Log.deprecation("Cannot specify keys #{found_defaults.join(", ")} together on property #{options[:name]}--only the first one (#{found_defaults[0]}) will be obeyed. Please pick one.", caller(5..5)[0]) + preferred_default = found_defaults[0] + # We do *not* prefer `default: nil` even if it's first, because earlier + # versions of Chef (backcompat) treat specifying something as `nil` the + # same as not specifying it at all. In Chef 13 we can switch this behavior + # back to normal, since only one default will be specifiable. + if preferred_default == :default && options[:default].nil? + preferred_default = found_defaults[1] + end + Chef::Log.deprecation("Cannot specify keys #{found_defaults.join(", ")} together on property #{options[:name]}--only one (#{preferred_default}) will be obeyed. In Chef 13, specifying multiple defaults will become an error.", caller(5..5)[0]) + # Only honor the preferred default + options.reject! { |k,v| found_defaults.include?(k) && k != preferred_default } end options[:name_property] = options.delete(:name_attribute) if options.has_key?(:name_attribute) && !options.has_key?(:name_property) diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 369c065aaa..ebf94c1dd0 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -949,11 +949,19 @@ describe "Chef::Resource.property" do context "default ordering deprecation warnings" do it "emits a deprecation warning for property :x, default: 10, #{name}: true" do expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys default, #{name} together on property x--only the first one \(default\) will be obeyed. Please pick one./ + /Cannot specify keys default, #{name} together on property x--only one \(default\) will be obeyed./ + end + it "emits a deprecation warning for property :x, default: nil, #{name}: true" do + expect { resource_class.property :x, :default => nil, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Cannot specify keys default, #{name} together on property x--only one \(#{name}\) will be obeyed./ end it "emits a deprecation warning for property :x, #{name}: true, default: 10" do expect { resource_class.property :x, name.to_sym => true, :default => 10 }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys #{name}, default together on property x--only the first one \(#{name}\) will be obeyed. Please pick one./ + /Cannot specify keys #{name}, default together on property x--only one \(#{name}\) will be obeyed./ + end + it "emits a deprecation warning for property :x, #{name}: true, default: nil" do + expect { resource_class.property :x, name.to_sym => true, :default => nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, + /Cannot specify keys #{name}, default together on property x--only one \(#{name}\) will be obeyed./ end end context "default ordering" do @@ -963,11 +971,21 @@ describe "Chef::Resource.property" do expect(resource.x).to eq 10 end end + with_property ":x, default: nil, #{name}: true" do + it "chooses #{name} over default" do + expect(resource.x).to eq 'blah' + end + end with_property ":x, #{name}: true, default: 10" do it "chooses #{name} over default" do expect(resource.x).to eq 'blah' end end + with_property ":x, #{name}: true, default: nil" do + it "chooses #{name} over default" do + expect(resource.x).to eq 'blah' + end + end end with_property ":x, #{name}: true, required: true" do it "defaults x to resource.name" do -- cgit v1.2.1 From 424b2dda9b4a2a0ca3e7ca8c9a598643b303ec0f Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 25 Sep 2015 07:19:48 -0700 Subject: Find the spot the user called and use that as the deprecation location --- lib/chef/chef_class.rb | 13 ++++++++++++- lib/chef/cookbook_version.rb | 6 +++--- lib/chef/deprecation/provider/remote_directory.rb | 2 +- lib/chef/deprecation/warnings.rb | 2 +- lib/chef/formatters/base.rb | 2 +- lib/chef/resource/file/verification.rb | 3 +-- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb index c2cb9e2b24..b18c3fbdde 100644 --- a/lib/chef/chef_class.rb +++ b/lib/chef/chef_class.rb @@ -204,7 +204,18 @@ class Chef # # @api private this will likely be removed in favor of an as-yet unwritten # `Chef.log` - def log_deprecation(message, location=caller(2..2)[0]) + def log_deprecation(message, location=nil) + if !location + # Pick the first caller that is *not* part of the Chef gem, that's the + # thing the user wrote. + chef_gem_path = File.expand_path("../..", __FILE__) + caller(0..10).each do |c| + if !c.start_with?(chef_gem_path) + location = c + break + end + end + end # `run_context.events` is the primary deprecation target if we're in a # run. If we are not yet in a run, print to `Chef::Log`. if run_context && run_context.events diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb index bff3146572..3cdfd8c10b 100644 --- a/lib/chef/cookbook_version.rb +++ b/lib/chef/cookbook_version.rb @@ -51,12 +51,12 @@ class Chef attr_accessor :metadata_filenames def status=(new_status) - Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) + Chef.log_deprecation("Deprecated method `status' called. This method will be removed.") @status = new_status end def status - Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1)) + Chef.log_deprecation("Deprecated method `status' called. This method will be removed.") @status end @@ -480,7 +480,7 @@ class Chef # @deprecated This method was used by the Ruby Chef Server and is no longer # needed. There is no replacement. def generate_manifest_with_urls(&url_generator) - Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.", caller(1..1)) + Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.") rendered_manifest = manifest.dup COOKBOOK_SEGMENTS.each do |segment| diff --git a/lib/chef/deprecation/provider/remote_directory.rb b/lib/chef/deprecation/provider/remote_directory.rb index b55a304696..cc8026ec55 100644 --- a/lib/chef/deprecation/provider/remote_directory.rb +++ b/lib/chef/deprecation/provider/remote_directory.rb @@ -22,7 +22,7 @@ class Chef module RemoteDirectory def directory_root_in_cookbook_cache - Chef::Log.deprecation "the Chef::Provider::RemoteDirectory#directory_root_in_cookbook_cache method is deprecated" + Chef.log_deprecation "the Chef::Provider::RemoteDirectory#directory_root_in_cookbook_cache method is deprecated" @directory_root_in_cookbook_cache ||= begin diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb index 376629710e..0b1ec2d5ed 100644 --- a/lib/chef/deprecation/warnings.rb +++ b/lib/chef/deprecation/warnings.rb @@ -27,7 +27,7 @@ class Chef message = [] message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13." message << "Please update your cookbooks accordingly." - Chef.log_deprecation(message, caller(0..3)) + Chef.log_deprecation(message) super(*args) end end diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb index d3756ef00c..e4056a00fc 100644 --- a/lib/chef/formatters/base.rb +++ b/lib/chef/formatters/base.rb @@ -213,7 +213,7 @@ class Chef end def deprecation(message, location=caller(2..2)[0]) - Chef::Log.deprecation("#{message} at #{location}") + Chef.log_deprecation("#{message} at #{location}") end end diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb index 9b0788fad3..ba0bb08201 100644 --- a/lib/chef/resource/file/verification.rb +++ b/lib/chef/resource/file/verification.rb @@ -110,8 +110,7 @@ class Chef # is interpolated. Until `file` can be deprecated, interpolate both. Chef.log_deprecation( '%{file} is deprecated in verify command and will not be '\ - 'supported in Chef 13. Please use %{path} instead.', - caller(2..2)[0] + 'supported in Chef 13. Please use %{path} instead.' ) if @command.include?('%{file}') command = @command % {:file => path, :path => path} interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts) -- cgit v1.2.1 From af422456552b8a64cb3ea90167a0f80f401790cf Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 25 Sep 2015 07:20:03 -0700 Subject: Fix up property deprecation text --- lib/chef/property.rb | 2 +- spec/unit/property_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 6c9d4619b1..011ff93aef 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -99,7 +99,7 @@ class Chef if preferred_default == :default && options[:default].nil? preferred_default = found_defaults[1] end - Chef::Log.deprecation("Cannot specify keys #{found_defaults.join(", ")} together on property #{options[:name]}--only one (#{preferred_default}) will be obeyed. In Chef 13, specifying multiple defaults will become an error.", caller(5..5)[0]) + Chef.log_deprecation("Cannot specify keys #{found_defaults.join(", ")} together on property #{options[:name]}#{options[:resource_class] ? " of resource #{options[:resource_class].resource_name}" : ""}. Only one (#{preferred_default}) will be obeyed. In Chef 13, specifying multiple defaults will become an error.") # Only honor the preferred default options.reject! { |k,v| found_defaults.include?(k) && k != preferred_default } end diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index ebf94c1dd0..6415286397 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -949,19 +949,19 @@ describe "Chef::Resource.property" do context "default ordering deprecation warnings" do it "emits a deprecation warning for property :x, default: 10, #{name}: true" do expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys default, #{name} together on property x--only one \(default\) will be obeyed./ + /Cannot specify keys default, #{name} together on property x. Only one \(default\) will be obeyed./ end it "emits a deprecation warning for property :x, default: nil, #{name}: true" do expect { resource_class.property :x, :default => nil, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys default, #{name} together on property x--only one \(#{name}\) will be obeyed./ + /Cannot specify keys default, #{name} together on property x. Only one \(#{name}\) will be obeyed./ end it "emits a deprecation warning for property :x, #{name}: true, default: 10" do expect { resource_class.property :x, name.to_sym => true, :default => 10 }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys #{name}, default together on property x--only one \(#{name}\) will be obeyed./ + /Cannot specify keys #{name}, default together on property x. Only one \(#{name}\) will be obeyed./ end it "emits a deprecation warning for property :x, #{name}: true, default: nil" do expect { resource_class.property :x, name.to_sym => true, :default => nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys #{name}, default together on property x--only one \(#{name}\) will be obeyed./ + /Cannot specify keys #{name}, default together on property x. Only one \(#{name}\) will be obeyed./ end end context "default ordering" do -- cgit v1.2.1 From ff54a6dd7ef781f242f1ab5b513f90a76902f5b8 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 25 Sep 2015 12:21:50 -0700 Subject: Don't treat name_property/attribute as defaults if they are false --- lib/chef/property.rb | 6 +++++- spec/unit/property_spec.rb | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index 011ff93aef..d699f16a9d 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -89,7 +89,11 @@ class Chef # Only pick the first of :default, :name_property and :name_attribute if # more than one is specified. - found_defaults = options.keys.select { |k| [ :default, :name_attribute, :name_property ].include?(k) } + found_defaults = options.keys.select do |k| + # Only treat name_property or name_attribute as a default if they are `true` + k == :default || + ((k == :name_property || k == :name_attribute) && options[k]) + end if found_defaults.size > 1 preferred_default = found_defaults[0] # We do *not* prefer `default: nil` even if it's first, because earlier diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index 6415286397..de8d493026 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -946,6 +946,19 @@ describe "Chef::Resource.property" do expect(resource.x).to eq 'blah' end end + + with_property ":x, #{name}: false" do + it "defaults to nil" do + expect(resource.x).to be_nil + end + end + + with_property ":x, #{name}: nil" do + it "defaults to nil" do + expect(resource.x).to be_nil + end + end + context "default ordering deprecation warnings" do it "emits a deprecation warning for property :x, default: 10, #{name}: true" do expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, @@ -964,6 +977,7 @@ describe "Chef::Resource.property" do /Cannot specify keys #{name}, default together on property x. Only one \(#{name}\) will be obeyed./ end end + context "default ordering" do before { Chef::Config[:treat_deprecation_warnings_as_errors] = false } with_property ":x, default: 10, #{name}: true" do @@ -987,6 +1001,33 @@ describe "Chef::Resource.property" do end end end + + context "default ordering when #{name} is nil" do + with_property ":x, #{name}: nil, default: 10" do + it "chooses default" do + expect(resource.x).to eq 10 + end + end + with_property ":x, default: 10, #{name}: nil" do + it "chooses default" do + expect(resource.x).to eq 10 + end + end + end + + context "default ordering when #{name} is false" do + with_property ":x, #{name}: false, default: 10" do + it "chooses default" do + expect(resource.x).to eq 10 + end + end + with_property ":x, default: 10, #{name}: nil" do + it "chooses default" do + expect(resource.x).to eq 10 + end + end + end + with_property ":x, #{name}: true, required: true" do it "defaults x to resource.name" do expect(resource.x).to eq 'blah' -- cgit v1.2.1 From 2c083878547343a4ed423e7d20ecbf6059285721 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 25 Sep 2015 13:10:58 -0700 Subject: If both name_attribute and name_property are specified, raise an error. --- lib/chef/property.rb | 39 ++++++++++++++++++++------------------- spec/unit/property_spec.rb | 18 +++++++++--------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index d699f16a9d..f6160d6954 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -87,27 +87,28 @@ class Chef def initialize(**options) options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) } + # Replace name_attribute with name_property + if options.has_key?(:name_attribute) + # If we have both name_attribute and name_property and they differ, raise an error + if options.has_key?(:name_property) + raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}." + end + # replace name_property with name_attribute in place + options = options.map { |k,v| k == :name_attribute ? [ :name_property, v ] : [ k,v ] }.to_h + end + # Only pick the first of :default, :name_property and :name_attribute if # more than one is specified. - found_defaults = options.keys.select do |k| - # Only treat name_property or name_attribute as a default if they are `true` - k == :default || - ((k == :name_property || k == :name_attribute) && options[k]) - end - if found_defaults.size > 1 - preferred_default = found_defaults[0] - # We do *not* prefer `default: nil` even if it's first, because earlier - # versions of Chef (backcompat) treat specifying something as `nil` the - # same as not specifying it at all. In Chef 13 we can switch this behavior - # back to normal, since only one default will be specifiable. - if preferred_default == :default && options[:default].nil? - preferred_default = found_defaults[1] + if options.has_key?(:default) && options[:name_property] + if options[:default].nil? || options.keys.index(:name_property) < options.keys.index(:default) + options.delete(:default) + preferred_default = :name_property + else + options.delete(:name_property) + preferred_default = :default end - Chef.log_deprecation("Cannot specify keys #{found_defaults.join(", ")} together on property #{options[:name]}#{options[:resource_class] ? " of resource #{options[:resource_class].resource_name}" : ""}. Only one (#{preferred_default}) will be obeyed. In Chef 13, specifying multiple defaults will become an error.") - # Only honor the preferred default - options.reject! { |k,v| found_defaults.include?(k) && k != preferred_default } + Chef.log_deprecation("Cannot specify both default and name_property together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error.") end - options[:name_property] = options.delete(:name_attribute) if options.has_key?(:name_attribute) && !options.has_key?(:name_property) @options = options @@ -446,10 +447,10 @@ class Chef EOM rescue SyntaxError # If the name is not a valid ruby name, we use define_method. - resource_class.define_method(name) do |value=NOT_PASSED| + declared_in.define_method(name) do |value=NOT_PASSED| self.class.properties[name].call(self, value) end - resource_class.define_method("#{name}=") do |value| + declared_in.define_method("#{name}=") do |value| self.class.properties[name].set(self, value) end end diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index de8d493026..64638d9be9 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -962,19 +962,19 @@ describe "Chef::Resource.property" do context "default ordering deprecation warnings" do it "emits a deprecation warning for property :x, default: 10, #{name}: true" do expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys default, #{name} together on property x. Only one \(default\) will be obeyed./ + /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(default\) will be obeyed./ end it "emits a deprecation warning for property :x, default: nil, #{name}: true" do expect { resource_class.property :x, :default => nil, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys default, #{name} together on property x. Only one \(#{name}\) will be obeyed./ + /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./ end it "emits a deprecation warning for property :x, #{name}: true, default: 10" do expect { resource_class.property :x, name.to_sym => true, :default => 10 }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys #{name}, default together on property x. Only one \(#{name}\) will be obeyed./ + /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./ end it "emits a deprecation warning for property :x, #{name}: true, default: nil" do expect { resource_class.property :x, name.to_sym => true, :default => nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, - /Cannot specify keys #{name}, default together on property x. Only one \(#{name}\) will be obeyed./ + /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./ end end @@ -1028,11 +1028,11 @@ describe "Chef::Resource.property" do end end - with_property ":x, #{name}: true, required: true" do - it "defaults x to resource.name" do - expect(resource.x).to eq 'blah' - end - end end end + + it "raises an error if both name_property and name_attribute are specified (even if they are false or nil)" do + expect { resource_class.property :x, :name_property => false, :name_attribute => true }.to raise_error ArgumentError, + /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./ + end end -- cgit v1.2.1 From 47fe94f8fafab031065dfe8f46dbbb4b3f90ce47 Mon Sep 17 00:00:00 2001 From: Noah Kantrowitz Date: Fri, 25 Sep 2015 16:05:06 -0700 Subject: I think this was a bad search-and-replace, causes an infinite loop. --- lib/chef/formatters/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb index e4056a00fc..d3756ef00c 100644 --- a/lib/chef/formatters/base.rb +++ b/lib/chef/formatters/base.rb @@ -213,7 +213,7 @@ class Chef end def deprecation(message, location=caller(2..2)[0]) - Chef.log_deprecation("#{message} at #{location}") + Chef::Log.deprecation("#{message} at #{location}") end end -- cgit v1.2.1 From cf5e1d000979b9f0004eea4020c8d1d375b4a4c0 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 25 Sep 2015 16:26:10 -0700 Subject: Use 2.0-compatible Hash rather than to_h --- lib/chef/property.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/property.rb b/lib/chef/property.rb index f6160d6954..2b151b350a 100644 --- a/lib/chef/property.rb +++ b/lib/chef/property.rb @@ -94,7 +94,7 @@ class Chef raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}." end # replace name_property with name_attribute in place - options = options.map { |k,v| k == :name_attribute ? [ :name_property, v ] : [ k,v ] }.to_h + options = Hash[options.map { |k,v| k == :name_attribute ? [ :name_property, v ] : [ k,v ] }] end # Only pick the first of :default, :name_property and :name_attribute if -- cgit v1.2.1 From 8d32fdd4377476a9f7dc36a864ccdaa17c32b3a1 Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Fri, 25 Sep 2015 15:27:04 -0700 Subject: Create empty config context for chefdk Allows `knife` and such to parse config files with `chefdk.settting` in them without error. --- chef-config/lib/chef-config/config.rb | 8 ++++++++ chef-config/spec/unit/config_spec.rb | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb index 2405b127f3..069f0ed6c0 100644 --- a/chef-config/lib/chef-config/config.rb +++ b/chef-config/lib/chef-config/config.rb @@ -690,6 +690,14 @@ module ChefConfig default :watchdog_timeout, 2 * (60 * 60) # 2 hours end + # Add an empty and non-strict config_context for chefdk. This lets the user + # have code like `chefdk.generator_cookbook "/path/to/cookbook"` in their + # config.rb, and it will be ignored by tools like knife and ohai. ChefDK + # itself can define the config options it accepts and enable strict mode, + # and that will only apply when running `chef` commands. + config_context :chefdk do + end + # Chef requires an English-language UTF-8 locale to function properly. We attempt # to use the 'locale -a' command and search through a list of preferences until we # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be diff --git a/chef-config/spec/unit/config_spec.rb b/chef-config/spec/unit/config_spec.rb index bc35fbee69..d99ff428fb 100644 --- a/chef-config/spec/unit/config_spec.rb +++ b/chef-config/spec/unit/config_spec.rb @@ -561,6 +561,14 @@ RSpec.describe ChefConfig::Config do end end + describe "allowing chefdk configuration outside of chefdk" do + + it "allows arbitrary settings in the chefdk config context" do + expect { ChefConfig::Config.chefdk.generator_cookbook("/path") }.to_not raise_error + end + + end + describe "Treating deprecation warnings as errors" do context "when using our default RSpec configuration" do -- cgit v1.2.1 From 650df1b431de34804e3e8fa00a29ecef5ca079ca Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 26 Sep 2015 19:23:16 -0700 Subject: Fix awkward wording in the contributing doc --- CONTRIBUTING.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc416537ab..e7f2e05f06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ Chef uses [the Apache 2.0 license](https://github.com/opscode/chef/blob/master/L The license tells you what rights you have that are provided by the copyright holder. It is important that the contributor fully understands what rights they are licensing and agrees to them. Sometimes the copyright holder isn't the contributor, - most often when the contributor is doing work for a company. + such as when the contributor is doing work for a company. To make a good faith effort to ensure these criteria are met, Chef requires an Individual CLA or a Corporate CLA for contributions. This agreement helps ensure you are aware of the @@ -134,8 +134,8 @@ There is also a listing of the various Chef products and where to file issues th Otherwise you can file your issue in the [Chef project](https://github.com/opscode/chef/issues) and we will make sure it gets filed against the appropriate project. -In order to decrease the back and forth an issues and help us get to the bottom of them quickly - we use below issue template. You can copy paste this code into the issue you are opening and +In order to decrease the back and forth in issues, and to help us get to the bottom of them quickly + we use the below issue template. You can copy/paste this template into the issue you are opening and edit it accordingly. @@ -148,16 +148,12 @@ In order to decrease the back and forth an issues and help us get to the bottom ### Scenario: [What you are trying to achieve and you can't?] - - ### Steps to Reproduce: [If you are filing an issue what are the things we need to do in order to repro your problem?] - ### Expected Result: [What are you expecting to happen as the consequence of above reproduction steps?] - ### Actual Result: [What actually happens after the reproduction steps?] ``` -- cgit v1.2.1 From 42446f98101eb9ebb80030c63704879539d320df Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 26 Sep 2015 19:27:20 -0700 Subject: Bootstrap doc doesnt match reality The other scripts are gone. Also update the URLs --- lib/chef/knife/bootstrap/templates/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/chef/knife/bootstrap/templates/README.md b/lib/chef/knife/bootstrap/templates/README.md index 13a0fe7ada..538d38d0b6 100644 --- a/lib/chef/knife/bootstrap/templates/README.md +++ b/lib/chef/knife/bootstrap/templates/README.md @@ -1,12 +1,11 @@ This directory contains bootstrap templates which can be used with the -d flag to 'knife bootstrap' to install Chef in different ways. To simplify installation, and reduce the matrix of common installation patterns to support, we have -standardized on the [Omnibus](https://github.com/opscode/omnibus-ruby) built installation +standardized on the [Omnibus](https://github.com/chef/omnibus) built installation packages. The 'chef-full' template downloads a script which is used to determine the correct -Omnibus package for this system from the [Omnitruck](https://github.com/opscode/opscode-omnitruck) API. All other templates in this directory are deprecated and will be removed -in the future. +Omnibus package for this system from the [Omnitruck](https://github.com/chef/omnitruck) API. You can still utilize custom bootstrap templates on your system if your installation -needs are unique. Additional information can be found on the [docs site](http://docs.opscode.com/knife_bootstrap.html#custom-templates). \ No newline at end of file +needs are unique. Additional information can be found on the [docs site](https://docs.chef.io/knife_bootstrap.html#custom-templates). -- cgit v1.2.1 From abe044cfdf4ae4a2468bc61aaeeba8c6cfaf39ae Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 26 Sep 2015 23:29:24 -0700 Subject: Copyright doesn't need to be capitalized --- lib/chef/knife/cookbook_create.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb index e17a54079f..97f6e65d3c 100644 --- a/lib/chef/knife/cookbook_create.rb +++ b/lib/chef/knife/cookbook_create.rb @@ -48,7 +48,7 @@ class Chef option :cookbook_copyright, :short => "-C COPYRIGHT", :long => "--copyright COPYRIGHT", - :description => "Name of Copyright holder" + :description => "Name of copyright holder" option :cookbook_email, :short => "-m EMAIL", -- cgit v1.2.1 From 655189ad40cc2ff44795d473c60bb819805ef4d0 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 26 Sep 2015 23:29:55 -0700 Subject: Fix error message to mention Supermarket not community site Also Supermarket doesn't have a capital M --- lib/chef/knife/cookbook_site_share.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index efd2e7f129..5784c3d73e 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -48,7 +48,7 @@ class Chef :short => '-n', :boolean => true, :default => false, - :description => "Don't take action, only print what files will be upload to SuperMarket." + :description => "Don't take action, only print what files will be upload to Supermarket." def run config[:cookbook_path] ||= Chef::Config[:cookbook_path] @@ -94,7 +94,7 @@ class Chef Chef::Log.debug("Removing local staging directory at #{tmp_cookbook_dir}") FileUtils.rm_rf tmp_cookbook_dir rescue => e - ui.error("Error uploading cookbook #{cookbook_name} to the Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.") + ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") Chef::Log.debug("\n#{e.backtrace.join("\n")}") exit(1) end -- cgit v1.2.1 From 28bb3f589e423f37d97294dfe607e45e8d79567d Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 26 Sep 2015 23:30:51 -0700 Subject: Capitalize first word in sentences --- lib/chef/knife/search.rb | 4 ++-- lib/chef/knife/ssh.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb index 2b0c79ff6c..014fc8dd87 100644 --- a/lib/chef/knife/search.rb +++ b/lib/chef/knife/search.rb @@ -136,7 +136,7 @@ class Chef def read_cli_args if config[:query] if @name_args[1] - ui.error "please specify query as an argument or an option via -q, not both" + ui.error "Please specify query as an argument or an option via -q, not both" ui.msg opt_parser exit 1 end @@ -145,7 +145,7 @@ class Chef else case name_args.size when 0 - ui.error "no query specified" + ui.error "No query specified" ui.msg opt_parser exit 1 when 1 diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb index bb3d9d78bb..62af853e88 100644 --- a/lib/chef/knife/ssh.rb +++ b/lib/chef/knife/ssh.rb @@ -425,7 +425,7 @@ class Chef begin require 'appscript' rescue LoadError - STDERR.puts "you need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install" + STDERR.puts "You need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install" raise end -- cgit v1.2.1 From def9260537b44c4c96c4323ced46d12b300e8b74 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 26 Sep 2015 23:33:56 -0700 Subject: Fix tense --- lib/chef/knife/cookbook_site_share.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index 5784c3d73e..8f4cf3a5b3 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -48,7 +48,7 @@ class Chef :short => '-n', :boolean => true, :default => false, - :description => "Don't take action, only print what files will be upload to Supermarket." + :description => "Don't take action, only print what files will be uploaded to Supermarket." def run config[:cookbook_path] ||= Chef::Config[:cookbook_path] -- cgit v1.2.1 From d3b7dd925597dbeedafb2e65b1fd10ffc5b60d22 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 26 Sep 2015 23:47:05 -0700 Subject: More Community site -> Supermarket --- lib/chef/knife/cookbook_site_share.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index 8f4cf3a5b3..b0f7f50e4d 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -108,15 +108,15 @@ class Chef def get_category(cookbook_name) begin - data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}") + data = noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") if !data["category"] && data["error_code"] - ui.fatal("Received an error from the Opscode Cookbook site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") + ui.fatal("Received an error from the Supermarket site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") exit(1) else data['category'] end rescue => e - ui.fatal("Unable to reach Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.") + ui.fatal("Unable to reach Supermarket site: #{e.message}. Increase log verbosity (-VV) for more information.") Chef::Log.debug("\n#{e.backtrace.join("\n")}") exit(1) end @@ -136,7 +136,7 @@ class Chef if http_resp.code.to_i != 201 if res['error_messages'] if res['error_messages'][0] =~ /Version already exists/ - ui.error "The same version of this cookbook already exists on the Opscode Cookbook Site." + ui.error "The same version of this cookbook already exists on Supermarket." exit(1) else ui.error "#{res['error_messages'][0]}" -- cgit v1.2.1 From 3968f4402d03822051169056138b769508e85c17 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sun, 27 Sep 2015 11:44:06 -0700 Subject: Supermarket vs. the Supermarket Judging by our docs is seems we're just calling it Supermarket not the Supermarket or the Supermarket site. Lets be consistent here --- lib/chef/knife/cookbook_site_download.rb | 2 +- lib/chef/knife/cookbook_site_share.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/chef/knife/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb index c2d72ef8da..3e586e6542 100644 --- a/lib/chef/knife/cookbook_site_download.rb +++ b/lib/chef/knife/cookbook_site_download.rb @@ -84,7 +84,7 @@ class Chef end def download_cookbook - ui.info "Downloading #{@name_args[0]} from the cookbooks site at version #{version} to #{download_location}" + ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}" noauth_rest.sign_on_redirect = false tf = noauth_rest.get_rest desired_cookbook_data["file"], true diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb index b0f7f50e4d..beb98b71b8 100644 --- a/lib/chef/knife/cookbook_site_share.rb +++ b/lib/chef/knife/cookbook_site_share.rb @@ -110,13 +110,13 @@ class Chef begin data = noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}") if !data["category"] && data["error_code"] - ui.fatal("Received an error from the Supermarket site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") + ui.fatal("Received an error from Supermarket: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.") exit(1) else data['category'] end rescue => e - ui.fatal("Unable to reach Supermarket site: #{e.message}. Increase log verbosity (-VV) for more information.") + ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") Chef::Log.debug("\n#{e.backtrace.join("\n")}") exit(1) end -- cgit v1.2.1 From dabb31a3586a7ec6409b0c054c842e97a4d3380b Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sun, 27 Sep 2015 11:44:16 -0700 Subject: Capitalize another message --- lib/chef/knife/cookbook_site_install.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb index d0ab6da3ef..aee8b7fa94 100644 --- a/lib/chef/knife/cookbook_site_install.rb +++ b/lib/chef/knife/cookbook_site_install.rb @@ -93,7 +93,7 @@ class Chef # TODO: it'd be better to store these outside the cookbook repo and # keep them around, e.g., in ~/Library/Caches on OS X. - ui.info("removing downloaded tarball") + ui.info("Removing downloaded tarball") File.unlink(upstream_file) if @repo.finalize_updates_to(@cookbook_name, downloader.version) -- cgit v1.2.1 From 57d4b8c1da44717f13758a761427e993105a6fe9 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sun, 27 Sep 2015 12:18:11 -0700 Subject: Update specs for Supermarket --- spec/unit/knife/cookbook_site_share_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/unit/knife/cookbook_site_share_spec.rb b/spec/unit/knife/cookbook_site_share_spec.rb index 76e4ec730e..a7caca9744 100644 --- a/spec/unit/knife/cookbook_site_share_spec.rb +++ b/spec/unit/knife/cookbook_site_share_spec.rb @@ -78,21 +78,21 @@ describe Chef::Knife::CookbookSiteShare do it 'should not fail when given only 1 argument and can determine category' do @knife.name_args = ['cookbook_name'] - expect(@noauth_rest).to receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@category_response) + expect(@noauth_rest).to receive(:get_rest).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@category_response) expect(@knife).to receive(:do_upload) @knife.run end it 'should print error and exit when given only 1 argument and cannot determine category' do @knife.name_args = ['cookbook_name'] - expect(@noauth_rest).to receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name").and_return(@bad_category_response) + expect(@noauth_rest).to receive(:get_rest).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@bad_category_response) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end it 'should print error and exit when given only 1 argument and Chef::REST throws an exception' do @knife.name_args = ['cookbook_name'] - expect(@noauth_rest).to receive(:get_rest).with("http://cookbooks.opscode.com/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" } + expect(@noauth_rest).to receive(:get_rest).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" } expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end -- cgit v1.2.1 From f0976789c8e810e8e8cf938f950975affda73446 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sun, 27 Sep 2015 12:18:50 -0700 Subject: Update more references from cookbooks -> supermarket --- distro/common/html/knife_cookbook_site.html | 36 ++++++++++++++-------------- distro/common/man/man1/knife-cookbook-site.1 | 22 ++++++++--------- lib/chef/cookbook_site_streaming_uploader.rb | 2 +- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/distro/common/html/knife_cookbook_site.html b/distro/common/html/knife_cookbook_site.html index 8815961629..60efafa7e1 100644 --- a/distro/common/html/knife_cookbook_site.html +++ b/distro/common/html/knife_cookbook_site.html @@ -5,12 +5,12 @@ - + knife cookbook site — chef-client Man Pages - + - +