diff options
author | Bryan McLellan <btm@opscode.com> | 2014-02-19 10:57:37 -0800 |
---|---|---|
committer | Bryan McLellan <btm@loftninjas.org> | 2014-02-27 11:14:21 -0500 |
commit | 62e4f1f1f983029f3565a59d42409312eda17d5a (patch) | |
tree | 7765565bd1befba62bcee210a91ed12c2454e174 | |
parent | ea6feff495e88dbdf17a5bdb1ca942385b403da2 (diff) | |
download | chef-62e4f1f1f983029f3565a59d42409312eda17d5a.tar.gz |
CHEF-5086/COOK-3890: Add reboot_pending? to core DSL
Accessible in recipes and in resources, can be used to detect if Windows
needs to reboot to finish a software installation or operating system
update.
-rw-r--r-- | lib/chef/dsl/reboot_pending.rb | 61 | ||||
-rw-r--r-- | lib/chef/exceptions.rb | 5 | ||||
-rw-r--r-- | lib/chef/recipe.rb | 2 | ||||
-rw-r--r-- | lib/chef/resource.rb | 2 | ||||
-rw-r--r-- | spec/functional/dsl/reboot_pending_spec.rb | 118 | ||||
-rw-r--r-- | spec/unit/dsl/reboot_pending_spec.rb | 100 |
6 files changed, 288 insertions, 0 deletions
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb new file mode 100644 index 0000000000..9f80d38c61 --- /dev/null +++ b/lib/chef/dsl/reboot_pending.rb @@ -0,0 +1,61 @@ +# Author:: Bryan McLellan <btm@loftninjas.org> +# Author:: Seth Chisamore <schisamo@opscode.com> +# Copyright:: Copyright (c) 2011,2014, Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/dsl/platform_introspection' +require 'chef/dsl/registry_helper' + +class Chef + module DSL + module RebootPending + + include Chef::DSL::RegistryHelper + include Chef::DSL::PlatformIntrospection + + # Returns true if the system needs a reboot or is expected to reboot + # Raises UnsupportedPlatform if this functionality isn't provided yet + def reboot_pending? + + if platform?("windows") + # PendingFileRenameOperations contains pairs (REG_MULTI_SZ) of filenames that cannot be updated + # due to a file being in use (usually a temporary file and a system file) + # \??\c:\temp\test.sys!\??\c:\winnt\system32\test.sys + # http://technet.microsoft.com/en-us/library/cc960241.aspx + registry_value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) || + + # RebootRequired key contains Update IDs with a value of 1 if they require a reboot. + # The existence of RebootRequired alone is sufficient on my Windows 8.1 workstation in Windows Update + registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') || + + # Vista + Server 2008 and newer may have reboots pending from CBS + registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') || + + # The mere existance 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') && + !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") + # This should work for Debian as well if update-notifier-common happens to be installed. We need an API for that. + File.exists?('/var/run/reboot-required') + else + raise Chef::Exceptions::UnsupportedPlatform.new(node[:platform]) + end + end + end + end +end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index afd42885f9..3877e3d17a 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -309,5 +309,10 @@ class Chef end end + class UnsupportedPlatform < RuntimeError + def initialize(platform) + super "This functionality is not supported on platform #{platform}." + end + end end end diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index 0c688cb5f8..5b95d80590 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -23,6 +23,7 @@ require 'chef/dsl/data_query' require 'chef/dsl/platform_introspection' require 'chef/dsl/include_recipe' require 'chef/dsl/registry_helper' +require 'chef/dsl/reboot_pending' require 'chef/mixin/from_file' @@ -38,6 +39,7 @@ class Chef include Chef::DSL::IncludeRecipe include Chef::DSL::Recipe include Chef::DSL::RegistryHelper + include Chef::DSL::RebootPending include Chef::Mixin::FromFile include Chef::Mixin::Deprecation diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 997c614171..e015a9eaf5 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -21,6 +21,7 @@ require 'chef/mixin/params_validate' require 'chef/dsl/platform_introspection' require 'chef/dsl/data_query' require 'chef/dsl/registry_helper' +require 'chef/dsl/reboot_pending' require 'chef/mixin/convert_to_class_name' require 'chef/resource/conditional' require 'chef/resource/conditional_action_not_nothing' @@ -125,6 +126,7 @@ F include Chef::Mixin::ParamsValidate include Chef::DSL::PlatformIntrospection include Chef::DSL::RegistryHelper + include Chef::DSL::RebootPending include Chef::Mixin::ConvertToClassName include Chef::Mixin::Deprecation diff --git a/spec/functional/dsl/reboot_pending_spec.rb b/spec/functional/dsl/reboot_pending_spec.rb new file mode 100644 index 0000000000..10d667f7bd --- /dev/null +++ b/spec/functional/dsl/reboot_pending_spec.rb @@ -0,0 +1,118 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/dsl/reboot_pending" +require "chef/win32/registry" +require "spec_helper" + +describe Chef::DSL::RebootPending, :windows_only do + def run_ohai + ohai = Ohai::System.new + # Would be nice to limit this to platform/kernel/arch etc for Ohai 7 + ohai.all_plugins + node.consume_external_attrs(ohai.data,{}) + + ohai + end + + def registry_safe? + !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 + let(:run_context) { Chef::RunContext.new(node, {}, events) } + let(:recipe) { Chef::Recipe.new("a windows cookbook", "the windows recipe", run_context) } + let(:registry) { Chef::Win32::Registry.new(run_context) } + + describe "reboot_pending?" do + + context "when there is nothing to indicate a reboot is pending" do + it { expect(recipe.reboot_pending?).to be_false } + end + + describe 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations' do + it "returns true if the registry value exists" do + pending "Found existing registry keys" unless registry_safe? + registry.set_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', + { :name => 'PendingFileRenameOperations', :type => :multi_string, :data => ['\??\C:\foo.txt|\??\C:\bar.txt'] }) + + expect(recipe.reboot_pending?).to be_true + end + + after do + if registry_safe? + registry.delete_value('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) + end + end + end + + describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' do + it "returns true if the registry key exists" do + pending "Found existing registry keys" unless registry_safe? + registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false) + + expect(recipe.reboot_pending?).to be_true + end + + after do + if registry_safe? + registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired', false) + end + end + end + + describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' do + it "returns true if the registry key exists" do + pending "Permissions are limited to 'TrustedInstaller' by default" + pending "Found existing registry keys" unless registry_safe? + registry.create_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false) + + expect(recipe.reboot_pending?).to be_true + end + + after do + if registry_safe? + registry.delete_key('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired', false) + end + end + end + + describe 'HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile\Flags' do + it "returns true if the registry key exists" do + pending "Found existing registry keys" unless registry_safe? + 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_true + end + + after do + if registry_safe? + registry.delete_value('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', { :name => 'Flags' }) + registry.delete_key('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile', false) + end + end + end + end +end diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb new file mode 100644 index 0000000000..8576ae168a --- /dev/null +++ b/spec/unit/dsl/reboot_pending_spec.rb @@ -0,0 +1,100 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/dsl/reboot_pending" +require "spec_helper" + +describe Chef::DSL::RebootPending do + describe "reboot_pending?" do + describe "in isoloation" do + let(:recipe) { Object.new.extend(Chef::DSL::RebootPending) } + + before do + recipe.stub(:platform?).and_return(false) + end + + context "platform is windows" do + before do + recipe.stub(:platform?).with('windows').and_return(true) + recipe.stub(:registry_key_exists?).and_return(false) + recipe.stub(:registry_value_exists?).and_return(false) + end + + it 'should return true if "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" exists' do + recipe.stub(:registry_value_exists?).with('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }).and_return(true) + expect(recipe.reboot_pending?).to be_true + end + + it 'should return true if "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" exists' do + recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired').and_return(true) + expect(recipe.reboot_pending?).to be_true + end + + it 'should return true if key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired" exists' do + recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired').and_return(true) + expect(recipe.reboot_pending?).to be_true + end + + it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data' do + recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true) + recipe.stub(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return( + [{:name => "Flags", :type => :dword, :data => 3}]) + expect(recipe.reboot_pending?).to be_true + end + end + + context "platform is ubuntu" do + before do + recipe.stub(:platform?).with('ubuntu').and_return(true) + end + + it 'should return true if /var/run/reboot-required exists' do + File.stub(:exists?).with('/var/run/reboot-required').and_return(true) + expect(recipe.reboot_pending?).to be_true + end + + it 'should return false if /var/run/reboot-required does not exist' do + File.stub(:exists?).with('/var/run/reboot-required').and_return(false) + expect(recipe.reboot_pending?).to be_false + end + end + + context "platform is not supported" do + it 'should raise an exception' do + recipe.stub_chain(:node, :[]).with(:platform).and_return('msdos') + expect { recipe.reboot_pending? }.to raise_error(Chef::Exceptions::UnsupportedPlatform) + end + end + end # describe in isolation + + describe "in a recipe" do + it "responds to reboot_pending?" do + # Chef::Recipe.new(cookbook_name, recipe_name, run_context(node, cookbook_collection, events)) + recipe = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, nil)) + expect(recipe).to respond_to(:reboot_pending?) + end + end # describe in a recipe + + describe "in a resource" do + it "responds to reboot_pending?" do + resource = Chef::Resource::new("Crackerjack::Timing", nil) + expect(resource).to respond_to(:reboot_pending?) + end + end # describe in a resource + end +end |