summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan McLellan <btm@opscode.com>2014-02-19 10:57:37 -0800
committerBryan McLellan <btm@loftninjas.org>2014-02-27 11:14:21 -0500
commit62e4f1f1f983029f3565a59d42409312eda17d5a (patch)
tree7765565bd1befba62bcee210a91ed12c2454e174
parentea6feff495e88dbdf17a5bdb1ca942385b403da2 (diff)
downloadchef-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.rb61
-rw-r--r--lib/chef/exceptions.rb5
-rw-r--r--lib/chef/recipe.rb2
-rw-r--r--lib/chef/resource.rb2
-rw-r--r--spec/functional/dsl/reboot_pending_spec.rb118
-rw-r--r--spec/unit/dsl/reboot_pending_spec.rb100
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