diff options
Diffstat (limited to 'lib/chef')
-rw-r--r-- | lib/chef/application.rb | 7 | ||||
-rw-r--r-- | lib/chef/application/client.rb | 3 | ||||
-rw-r--r-- | lib/chef/application/windows_service.rb | 168 | ||||
-rw-r--r-- | lib/chef/application/windows_service_manager.rb | 179 | ||||
-rw-r--r-- | lib/chef/config.rb | 2 | ||||
-rw-r--r-- | lib/chef/exceptions.rb | 10 | ||||
-rw-r--r-- | lib/chef/formatters/error_inspectors/registration_error_inspector.rb | 4 | ||||
-rw-r--r-- | lib/chef/knife.rb | 3 | ||||
-rw-r--r-- | lib/chef/log.rb | 4 | ||||
-rw-r--r-- | lib/chef/mixin/windows_architecture_helper.rb | 91 | ||||
-rw-r--r-- | lib/chef/monologger.rb | 93 | ||||
-rw-r--r-- | lib/chef/platform.rb | 8 | ||||
-rw-r--r-- | lib/chef/provider/batch.rb | 35 | ||||
-rw-r--r-- | lib/chef/provider/package/rubygems.rb | 50 | ||||
-rw-r--r-- | lib/chef/provider/powershell.rb | 35 | ||||
-rw-r--r-- | lib/chef/provider/script.rb | 9 | ||||
-rw-r--r-- | lib/chef/provider/windows_script.rb | 73 | ||||
-rw-r--r-- | lib/chef/providers.rb | 2 | ||||
-rw-r--r-- | lib/chef/resource/batch.rb | 31 | ||||
-rw-r--r-- | lib/chef/resource/powershell.rb | 31 | ||||
-rw-r--r-- | lib/chef/resource/windows_script.rb | 62 | ||||
-rw-r--r-- | lib/chef/resources.rb | 2 | ||||
-rw-r--r-- | lib/chef/rest.rb | 6 | ||||
-rw-r--r-- | lib/chef/win32/version.rb | 2 |
24 files changed, 832 insertions, 78 deletions
diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 6e65e48384..0ed8d3dd9f 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -29,9 +29,6 @@ require 'rbconfig' class Chef::Application include Mixlib::CLI - class Wakeup < Exception - end - def initialize super @@ -125,7 +122,7 @@ class Chef::Application # that a user has configured a log_location in client.rb, but is running # chef-client by hand to troubleshoot a problem. def configure_logging - Chef::Log.init(Chef::Config[:log_location]) + Chef::Log.init(MonoLogger.new(Chef::Config[:log_location])) if want_additional_logger? configure_stdout_logger end @@ -133,7 +130,7 @@ class Chef::Application end def configure_stdout_logger - stdout_logger = Logger.new(STDOUT) + stdout_logger = MonoLogger.new(STDOUT) STDOUT.sync = true stdout_logger.formatter = Chef::Log.logger.formatter Chef::Log.loggers << stdout_logger diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index c72f82d7bd..fca2e9a92d 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -306,9 +306,6 @@ class Chef::Application::Client < Chef::Application else Chef::Application.exit! "Exiting", 0 end - rescue Chef::Application::Wakeup => e - Chef::Log.debug("Received Wakeup signal. Starting run.") - next rescue SystemExit => e raise rescue Exception => e diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb index 0d4a022fdf..5b77a44392 100644 --- a/lib/chef/application/windows_service.rb +++ b/lib/chef/application/windows_service.rb @@ -17,6 +17,7 @@ # require 'chef' +require 'chef/monologger' require 'chef/application' require 'chef/client' require 'chef/config' @@ -56,58 +57,54 @@ class Chef :description => "Set the number of seconds to wait between chef-client runs", :proc => lambda { |s| s.to_i } - option :override_runlist, - :short => "-o RunlistItem,RunlistItem...", - :long => "--override-runlist RunlistItem,RunlistItem...", - :description => "Replace current run list with specified items", - :proc => lambda{|items| - items = items.split(',') - items.compact.map{|item| - Chef::RunList::RunListItem.new(item) - } - } - def service_init + @service_action_mutex = Mutex.new + @service_signal = ConditionVariable.new + reconfigure Chef::Log.info("Chef Client Service initialized") end def service_main(*startup_parameters) + # Chef::Config is initialized during service_init + # Set the initial timeout to splay sleep time + timeout = rand Chef::Config[:splay] - while running? - if state == RUNNING + while running? do + # Grab the service_action_mutex to make a chef-client run + @service_action_mutex.synchronize do begin + Chef::Log.info("Next chef-client run will happen in #{timeout} seconds") + @service_signal.wait(@service_action_mutex, timeout) + + # Continue only if service is RUNNING + next if state != RUNNING + # Reconfigure each time through to pick up any changes in the client file Chef::Log.info("Reconfiguring with startup parameters") reconfigure(startup_parameters) + timeout = Chef::Config[:interval] - splay = rand Chef::Config[:splay] - Chef::Log.debug("Splay sleep #{splay} seconds") - sleep splay + # Honor splay sleep config + timeout += rand Chef::Config[:splay] - # If we've stopped, then bail out now, instead of going on to run Chef + # run chef-client only if service is in RUNNING state next if state != RUNNING + Chef::Log.info("Chef-Client service is starting a chef-client run...") run_chef_client - - Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds") - client_sleep Chef::Config[:interval] - rescue Chef::Application::Wakeup => e - Chef::Log.debug("Received Wakeup signal. Starting run.") - next rescue SystemExit => e - raise + # Do not raise any of the errors here in order to + # prevent service crash + Chef::Log.error("#{e.class}: #{e}") rescue Exception => e Chef::Log.error("#{e.class}: #{e}") Chef::Application.debug_stacktrace(e) - Chef::Log.error("Sleeping for #{Chef::Config[:interval]} seconds before trying again") - client_sleep Chef::Config[:interval] - retry end - else # PAUSED or IDLE - sleep 5 end end + + Chef::Log.debug("Exiting service...") end ################################################################################ @@ -115,19 +112,45 @@ class Chef ################################################################################ def service_stop - Chef::Log.info("SERVICE_CONTROL_STOP received, stopping") + Chef::Log.info("STOP request from operating system.") + if @service_action_mutex.try_lock + @service_signal.signal + @service_action_mutex.unlock + Chef::Log.info("Service is stopping....") + else + Chef::Log.info("Currently a chef-client run is happening.") + Chef::Log.info("Service will stop once it's completed.") + end end def service_pause - Chef::Log.info("SERVICE_CONTROL_PAUSE received, pausing") + Chef::Log.info("PAUSE request from operating system.") + + # We don't need to wake up the service_main if it's waiting + # since this is a PAUSE signal. + + if @service_action_mutex.locked? + Chef::Log.info("Currently a chef-client run is happening.") + Chef::Log.info("Service will pause once it's completed.") + else + Chef::Log.info("Service is pausing....") + end end def service_resume - Chef::Log.info("SERVICE_CONTROL_CONTINUE received, resuming") + # We don't need to wake up the service_main if it's waiting + # since this is a RESUME signal. + + Chef::Log.info("RESUME signal received from the OS.") + Chef::Log.info("Service is resuming....") end def service_shutdown - Chef::Log.info("SERVICE_CONTROL_SHUTDOWN received, shutting down") + Chef::Log.info("SHUTDOWN signal received from the OS.") + + # Treat shutdown similar to stop. + + service_stop end ################################################################################ @@ -136,6 +159,19 @@ class Chef private + # Initializes Chef::Client instance and runs it + def run_chef_client + @chef_client = Chef::Client.new( + @chef_client_json, + :override_runlist => config[:override_runlist] + ) + @chef_client_json = nil + + @chef_client.run + @chef_client = nil + end + + def apply_config(config_file_path) Chef::Config.from_file(config_file_path) Chef::Config.merge!(config) @@ -155,16 +191,52 @@ class Chef Chef::Config[:interval] ||= 1800 end - # Lifted from Chef::Application and Chef::Application::Client - # MUST BE RUN AFTER configuration has been parsed! + # Lifted from application.rb + # See application.rb for related comments. + def configure_logging - # Implementation from Chef::Application - Chef::Log.init(Chef::Config[:log_location]) - Chef::Log.level = Chef::Config[:log_level] + Chef::Log.init(MonoLogger.new(Chef::Config[:log_location])) + if want_additional_logger? + configure_stdout_logger + end + Chef::Log.level = resolve_log_level + end - # Implementation from Chef::Application::Client - Mixlib::Authentication::Log.use_log_devices( Chef::Log ) - Ohai::Log.use_log_devices( Chef::Log ) + def configure_stdout_logger + stdout_logger = MonoLogger.new(STDOUT) + STDOUT.sync = true + stdout_logger.formatter = Chef::Log.logger.formatter + Chef::Log.loggers << stdout_logger + end + + # Based on config and whether or not STDOUT is a tty, should we setup a + # secondary logger for stdout? + def want_additional_logger? + ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger]) + end + + # Use of output formatters is assumed if `force_formatter` is set or if + # `force_logger` is not set and STDOUT is to a console (tty) + def using_output_formatter? + Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) + end + + def auto_log_level? + Chef::Config[:log_level] == :auto + end + + # if log_level is `:auto`, convert it to :warn (when using output formatter) + # or :info (no output formatter). See also +using_output_formatter?+ + def resolve_log_level + if auto_log_level? + if using_output_formatter? + :warn + else + :info + end + else + Chef::Config[:log_level] + end end def configure_chef(startup_parameters) @@ -205,20 +277,6 @@ class Chef end end - # Since we need to be able to respond to signals between Chef runs, we need to periodically - # wake up to see if we're still in the running state. The method returns when it has slept - # for +sec+ seconds (but at least +10+ seconds), or when the service - # is no client_sleep in the +RUNNING+ state, whichever comes first. - def client_sleep(sec) - chunk_length = 10 - chunks = sec / chunk_length - chunks = 1 if chunks < 1 - (1..chunks).each do - return unless state == RUNNING - sleep chunk_length - end - end - end end end diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb new file mode 100644 index 0000000000..13bd2c5cd6 --- /dev/null +++ b/lib/chef/application/windows_service_manager.rb @@ -0,0 +1,179 @@ +# +# Author:: Seth Chisamore (<schisamo@opscode.com>) +# Copyright:: Copyright (c) 2011 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 'win32/service' +require 'chef/config' +require 'mixlib/cli' + +class Chef + class Application + # + # This class is used to create and manage a windows service. + # Service should be created using Daemon class from + # win32/service gem. + # For an example see: Chef::Application::WindowsService + # + # Outside programs are expected to use this class to manage + # windows services. + # + class WindowsServiceManager + include Mixlib::CLI + + option :action, + :short => "-a ACTION", + :long => "--action ACTION", + :default => "status", + :description => "Action to carry out on chef-service (install, uninstall, status, start, stop, pause, or resume)" + + option :config_file, + :short => "-c CONFIG", + :long => "--config CONFIG", + :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb", + :description => "The configuration file to use for chef runs" + + option :log_location, + :short => "-L LOGLOCATION", + :long => "--logfile LOGLOCATION", + :description => "Set the log file location for chef-service", + :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log" + + option :help, + :short => "-h", + :long => "--help", + :description => "Show this message", + :on => :tail, + :boolean => true, + :show_options => true, + :exit => 0 + + def initialize(service_options) + # having to call super in initialize is the most annoying + # anti-pattern :( + super() + + raise ArgumentError, "Service definition is not provided" if service_options.nil? + + required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path] + + required_options.each do |req_option| + if !service_options.has_key?(req_option) + raise ArgumentError, "Service definition doesn't contain required option #{req_option}" + end + end + + @service_name = service_options[:service_name] + @service_display_name = service_options[:service_display_name] + @service_description = service_options[:service_description] + @service_file_path = service_options[:service_file_path] + end + + def run(params = ARGV) + parse_options(params) + + case config[:action] + when 'install' + if service_exists? + puts "Service #{@service_name} already exists on the system." + else + ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby') + + opts = "" + opts << " -c #{config[:config_file]}" if config[:config_file] + opts << " -L #{config[:log_location]}" if config[:log_location] + + # Quote the full paths to deal with possible spaces in the path name. + # Also ensure all forward slashes are backslashes + cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR) + + ::Win32::Service.new( + :service_name => @service_name, + :display_name => @service_display_name, + :description => @service_description, + :start_type => ::Win32::Service::SERVICE_AUTO_START, + :binary_path_name => cmd + ) + puts "Service '#{@service_name}' has successfully been installed." + end + when 'status' + if !service_exists? + puts "Service #{@service_name} doesn't exist on the system." + else + puts "State of #{@service_name} service is: #{current_state}" + end + when 'start' + # TODO: allow override of startup parameters here? + take_action('start', RUNNING) + when 'stop' + take_action('stop', STOPPED) + when 'uninstall', 'delete' + take_action('stop', STOPPED) + unless service_exists? + puts "Service #{@service_name} doesn't exist on the system." + else + ::Win32::Service.delete(@service_name) + puts "Service #{@service_name} deleted" + end + when 'pause' + take_action('pause', PAUSED) + when 'resume' + take_action('resume', RUNNING) + end + end + + private + + # Just some state constants + STOPPED = "stopped" + RUNNING = "running" + PAUSED = "paused" + + def service_exists? + return ::Win32::Service.exists?(@service_name) + end + + def take_action(action=nil, desired_state=nil) + if service_exists? + if current_state != desired_state + ::Win32::Service.send(action, @service_name) + wait_for_state(desired_state) + puts "Service '#{@service_name}' is now '#{current_state}'." + else + puts "Service '#{@service_name}' is already '#{desired_state}'." + end + else + puts "Cannot '#{action}' service '#{@service_name}'" + puts "Service #{@service_name} doesn't exist on the system." + end + end + + def current_state + ::Win32::Service.status(@service_name).current_state + end + + # Helper method that waits for a status to change its state since state + # changes aren't usually instantaneous. + def wait_for_state(desired_state) + while current_state != desired_state + puts "One moment... #{current_state}" + sleep 1 + end + end + + end + end +end diff --git a/lib/chef/config.rb b/lib/chef/config.rb index ca912b65ab..8846aa92ac 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -203,7 +203,7 @@ class Chef verbose_logging true node_name nil node_path "/var/chef/node" - diff_disable false + diff_disabled false diff_filesize_threshold 10000000 diff_output_threshold 1000000 diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb index c8654d7801..40dec4dc2a 100644 --- a/lib/chef/exceptions.rb +++ b/lib/chef/exceptions.rb @@ -97,6 +97,8 @@ class Chef # Attempting to run windows code on a not-windows node class Win32NotWindows < RuntimeError; end class WindowsNotAdmin < RuntimeError; end + # Attempting to access a 64-bit only resource on a 32-bit Windows system + class Win32ArchitectureIncorrect < RuntimeError; end class ObsoleteDependencySyntax < ArgumentError; end class InvalidDataBagPath < ArgumentError; end @@ -131,7 +133,7 @@ class Chef class StaleAttributeRead < StandardError; end #Registry Helper throws the following errors - class Win32RegArchitectureIncorrect < RuntimeError; end + class Win32RegArchitectureIncorrect < Win32ArchitectureIncorrect; end class Win32RegHiveMissing < ArgumentError; end class Win32RegKeyMissing < RuntimeError; end class Win32RegValueMissing < RuntimeError; end @@ -271,5 +273,11 @@ class Chef end # CookbookVersionSelection + # When the server sends a redirect, RFC 2616 states a user-agent should + # not follow it with a method other than GET or HEAD, unless a specific + # action is taken by the user. A redirect received as response to a + # non-GET and non-HEAD request will thus raise an InvalidRedirect. + class InvalidRedirect < StandardError; end + end end diff --git a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb index 5389f9f7d0..f31b348278 100644 --- a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb +++ b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb @@ -41,6 +41,10 @@ E error_description.section("Relevant Config Settings:",<<-E) validation_key "#{api_key}" E + when Chef::Exceptions::InvalidRedirect + error_description.section("Invalid Redirect:",<<-E) +Change your server location in client.rb to the server's FQDN to avoid unwanted redirections. +E else "#{exception.class.name}: #{exception.message}" end diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 421a329f22..7812fd232f 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -481,6 +481,9 @@ class Chef when Chef::Exceptions::PrivateKeyMissing ui.error "Your private key could not be loaded from #{api_key}" ui.info "Check your configuration file and ensure that your private key is readable" + when Chef::Exceptions::InvalidRedirect + ui.error "Invalid Redirect: #{e.message}" + ui.info "Change your server location in knife.rb to the server's FQDN to avoid unwanted redirections." else ui.error "#{e.class.name}: #{e.message}" end diff --git a/lib/chef/log.rb b/lib/chef/log.rb index 7355ec7574..131d706a5e 100644 --- a/lib/chef/log.rb +++ b/lib/chef/log.rb @@ -18,6 +18,7 @@ # limitations under the License. require 'logger' +require 'chef/monologger' require 'mixlib/log' class Chef @@ -25,8 +26,7 @@ class Chef extend Mixlib::Log # Force initialization of the primary log device (@logger) - init - + init(MonoLogger.new(STDOUT)) class Formatter def self.show_time=(*args) diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb new file mode 100644 index 0000000000..38c08e236d --- /dev/null +++ b/lib/chef/mixin/windows_architecture_helper.rb @@ -0,0 +1,91 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 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 'chef/exceptions' +require 'win32/api' if Chef::Platform.windows? + +class Chef + module Mixin + module WindowsArchitectureHelper + + def node_windows_architecture(node) + node[:kernel][:machine].to_sym + end + + def wow64_architecture_override_required?(node, desired_architecture) + is_i386_windows_process? && + node_windows_architecture(node) == :x86_64 && + desired_architecture == :x86_64 + end + + def node_supports_windows_architecture?(node, desired_architecture) + assert_valid_windows_architecture!(desired_architecture) + return (node_windows_architecture(node) == :x86_64 || + desired_architecture == :i386) ? true : false + end + + def valid_windows_architecture?(architecture) + return (architecture == :x86_64) || (architecture == :i386) + end + + def assert_valid_windows_architecture!(architecture) + if ! valid_windows_architecture?(architecture) + raise Chef::Exceptions::Win32ArchitectureIncorrect, + "The specified architecture was not valid. It must be one of :i386 or :x86_64" + end + end + + def is_i386_windows_process? + Chef::Platform.windows? && 'X86'.casecmp(ENV['PROCESSOR_ARCHITECTURE']) == 0 + end + + def disable_wow64_file_redirection( node ) + original_redirection_state = ['0'].pack('P') + + if ( ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?) + win32_wow_64_disable_wow_64_fs_redirection = + ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32') + + succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state) + + if succeeded == 0 + raise Win32APIError "Failed to disable Wow64 file redirection" + end + + end + + original_redirection_state + end + + def restore_wow64_file_redirection( node, original_redirection_state ) + if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?) + win32_wow_64_revert_wow_64_fs_redirection = + ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32') + + succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state) + + if succeeded == 0 + raise Win32APIError "Failed to revert Wow64 file redirection" + end + end + end + + end + end +end diff --git a/lib/chef/monologger.rb b/lib/chef/monologger.rb new file mode 100644 index 0000000000..fed60514d7 --- /dev/null +++ b/lib/chef/monologger.rb @@ -0,0 +1,93 @@ +require 'logger' + +require 'pp' + +#== MonoLogger +# A subclass of Ruby's stdlib Logger with all the mutex and logrotation stuff +# ripped out. +class MonoLogger < Logger + + # + # === Synopsis + # + # Logger.new(name, shift_age = 7, shift_size = 1048576) + # Logger.new(name, shift_age = 'weekly') + # + # === Args + # + # +logdev+:: + # The log device. This is a filename (String) or IO object (typically + # +STDOUT+, +STDERR+, or an open file). + # +shift_age+:: + # Number of old log files to keep, *or* frequency of rotation (+daily+, + # +weekly+ or +monthly+). + # +shift_size+:: + # Maximum logfile size (only applies when +shift_age+ is a number). + # + # === Description + # + # Create an instance. + # + def initialize(logdev) + @progname = nil + @level = DEBUG + @default_formatter = Formatter.new + @formatter = nil + @logdev = nil + if logdev + @logdev = LocklessLogDevice.new(logdev) + end + end + + + class LocklessLogDevice < LogDevice + + def initialize(log = nil) + @dev = @filename = @shift_age = @shift_size = nil + if log.respond_to?(:write) and log.respond_to?(:close) + @dev = log + else + @dev = open_logfile(log) + @dev.sync = true + @filename = log + end + end + + def write(message) + @dev.write(message) + rescue Exception => ignored + warn("log writing failed. #{ignored}") + end + + def close + @dev.close rescue nil + end + + private + + def open_logfile(filename) + if (FileTest.exist?(filename)) + open(filename, (File::WRONLY | File::APPEND)) + else + create_logfile(filename) + end + end + + def create_logfile(filename) + logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT)) + logdev.sync = true + add_log_header(logdev) + logdev + end + + def add_log_header(file) + file.write( + "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName] + ) + end + + end + + +end + diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb index 6e6de6004e..b176e9c731 100644 --- a/lib/chef/platform.rb +++ b/lib/chef/platform.rb @@ -72,6 +72,14 @@ class Chef :mdadm => Chef::Provider::Mdadm } }, + :gcel => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, :linaro => { :default => { :package => Chef::Provider::Package::Apt, diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb new file mode 100644 index 0000000000..e4b35b64f3 --- /dev/null +++ b/lib/chef/provider/batch.rb @@ -0,0 +1,35 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 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 'chef/provider/windows_script' + +class Chef + class Provider + class Batch < Chef::Provider::WindowsScript + + def initialize (new_resource, run_context) + super(new_resource, run_context, '.bat') + end + + def flags + @new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c' + end + + end + end +end diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb index e60d73ab62..b451450a8c 100644 --- a/lib/chef/provider/package/rubygems.rb +++ b/lib/chef/provider/package/rubygems.rb @@ -31,7 +31,14 @@ require 'rubygems/version' require 'rubygems/dependency' require 'rubygems/spec_fetcher' require 'rubygems/platform' -require 'rubygems/format' + +# Compatibility note: Rubygems 2.0 removes rubygems/format in favor of +# rubygems/package. +begin + require 'rubygems/format' +rescue LoadError + require 'rubygems/package' +end require 'rubygems/dependency_installer' require 'rubygems/uninstaller' require 'rubygems/specification' @@ -106,6 +113,22 @@ class Chef end ## + # Extracts the gemspec from a (on-disk) gem package. + # === Returns + # Gem::Specification + # + #-- + # Compatibility note: Rubygems 1.x uses Gem::Format, 2.0 moved this + # code into Gem::Package. + def spec_from_file(file) + if defined?(Gem::Format) + Gem::Format.from_file_by_path(file).spec + else + Gem::Package.new(file).spec + end + end + + ## # Determines the candidate version for a gem from a .gem file on disk # and checks if it matches the version contraints in +gem_dependency+ # === Returns @@ -114,7 +137,7 @@ class Chef # nil returns nil if the gem on disk doesn't match the # version constraints for +gem_dependency+ def candidate_version_from_file(gem_dependency, source) - spec = Gem::Format.from_file_by_path(source).spec + spec = spec_from_file(source) if spec.satisfies_requirement?(gem_dependency) logger.debug {"#{@new_resource} found candidate gem version #{spec.version} from local gem package #{source}"} spec.version @@ -142,17 +165,26 @@ class Chef # Find the newest gem version available from Gem.sources that satisfies # the constraints of +gem_dependency+ def find_newest_remote_version(gem_dependency, *sources) - # DependencyInstaller sorts the results such that the last one is - # always the one it considers best. - spec_with_source = dependency_installer.find_gems_with_sources(gem_dependency).last + available_gems = dependency_installer.find_gems_with_sources(gem_dependency) + spec, source = if available_gems.respond_to?(:last) + # DependencyInstaller sorts the results such that the last one is + # always the one it considers best. + spec_with_source = available_gems.last + spec_with_source && spec_with_source + else + # Rubygems 2.0 returns a Gem::Available set, which is a + # collection of AvailableSet::Tuple structs + available_gems.pick_best! + best_gem = available_gems.set.first + best_gem && [best_gem.spec, best_gem.source] + end - spec = spec_with_source && spec_with_source[0] - version = spec && spec_with_source[0].version + version = spec && spec.version if version - logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{spec_with_source[1]}" } + logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" } version else - source_list = sources.compact.empty? ? "[#{Gem.sources.join(', ')}]" : "[#{sources.join(', ')}]" + source_list = sources.compact.empty? ? "[#{Gem.sources.to_a.join(', ')}]" : "[#{sources.join(', ')}]" logger.warn { "#{@new_resource} failed to find gem #{gem_dependency} from #{source_list}" } nil end diff --git a/lib/chef/provider/powershell.rb b/lib/chef/provider/powershell.rb new file mode 100644 index 0000000000..aaa4a9255e --- /dev/null +++ b/lib/chef/provider/powershell.rb @@ -0,0 +1,35 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 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 'chef/provider/windows_script' + +class Chef + class Provider + class Powershell < Chef::Provider::WindowsScript + + def initialize (new_resource, run_context) + super(new_resource, run_context, '.ps1') + end + + def flags + @new_resource.flags.nil? ? '-ExecutionPolicy RemoteSigned -Command' : @new_resource.flags + '-ExecutionPolicy RemoteSigned -Command' + end + + end + end +end diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb index 9e5a7d7fe1..f1b765d52a 100644 --- a/lib/chef/provider/script.rb +++ b/lib/chef/provider/script.rb @@ -29,7 +29,7 @@ class Chef set_owner_and_group - @new_resource.command("\"#{@new_resource.interpreter}\" #{@new_resource.flags} \"#{script_file.path}\"") + @new_resource.command("\"#{interpreter}\" #{flags} \"#{script_file.path}\"") super converge_by(nil) do # ensure script is unlinked at end of converge! @@ -52,6 +52,13 @@ class Chef @script_file && @script_file.close! end + def interpreter + @new_resource.interpreter + end + + def flags + @new_resource.flags + end end end end diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb new file mode 100644 index 0000000000..398e1aee6e --- /dev/null +++ b/lib/chef/provider/windows_script.rb @@ -0,0 +1,73 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 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 'chef/provider/script' +require 'chef/mixin/windows_architecture_helper' + +class Chef + class Provider + class WindowsScript < Chef::Provider::Script + + protected + + include Chef::Mixin::WindowsArchitectureHelper + + def initialize( new_resource, run_context, script_extension='') + super( new_resource, run_context ) + @script_extension = script_extension + + target_architecture = new_resource.architecture.nil? ? + node_windows_architecture(run_context.node) : new_resource.architecture + + @is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture) + + if ( target_architecture == :i386 ) && ! is_i386_windows_process? + raise Chef::Exceptions::Win32ArchitectureIncorrect, + "Support for the i386 architecture from a 64-bit Ruby runtime is not yet implemented" + end + end + + public + + def action_run + wow64_redirection_state = nil + + if @is_wow64 + wow64_redirection_state = disable_wow64_file_redirection(@run_context.node) + end + + begin + super + rescue + raise + ensure + if ! wow64_redirection_state.nil? + restore_wow64_file_redirection(@run_context.node, wow64_redirection_state) + end + end + end + + def script_file + base_script_name = "chef-script" + temp_file_arguments = [ base_script_name, @script_extension ] + + @script_file ||= Tempfile.open(temp_file_arguments) + end + end + end +end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index be3f487ca3..ae95632eaa 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -16,6 +16,7 @@ # limitations under the License. # +require 'chef/provider/batch' require 'chef/provider/breakpoint' require 'chef/provider/cookbook_file' require 'chef/provider/cron' @@ -36,6 +37,7 @@ require 'chef/provider/ohai' require 'chef/provider/mdadm' require 'chef/provider/mount' require 'chef/provider/package' +require 'chef/provider/powershell' require 'chef/provider/remote_directory' require 'chef/provider/remote_file' require 'chef/provider/route' diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb new file mode 100644 index 0000000000..705260bbce --- /dev/null +++ b/lib/chef/resource/batch.rb @@ -0,0 +1,31 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 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 'chef/resource/windows_script' + +class Chef + class Resource + class Batch < Chef::Resource::WindowsScript + + def initialize(name, run_context=nil) + super(name, run_context, :batch, "cmd.exe") + end + + end + end +end diff --git a/lib/chef/resource/powershell.rb b/lib/chef/resource/powershell.rb new file mode 100644 index 0000000000..e726e6f35a --- /dev/null +++ b/lib/chef/resource/powershell.rb @@ -0,0 +1,31 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 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 'chef/resource/windows_script' + +class Chef + class Resource + class Powershell < Chef::Resource::WindowsScript + + def initialize(name, run_context=nil) + super(name, run_context, :powershell, "powershell.exe") + end + + end + end +end diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb new file mode 100644 index 0000000000..5f2311a5bb --- /dev/null +++ b/lib/chef/resource/windows_script.rb @@ -0,0 +1,62 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 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 'chef/resource/script' +require 'chef/mixin/windows_architecture_helper' + +class Chef + class Resource + class WindowsScript < Chef::Resource::Script + + protected + + def initialize(name, run_context, resource_name, interpreter_command) + super(name, run_context) + @interpreter = interpreter_command + @resource_name = resource_name + end + + include Chef::Mixin::WindowsArchitectureHelper + + public + + def architecture(arg=nil) + assert_architecture_compatible!(arg) if ! arg.nil? + result = set_or_return( + :architecture, + arg, + :kind_of => Symbol + ) + end + + protected + + def assert_architecture_compatible!(desired_architecture) + if ! node_supports_windows_architecture?(node, desired_architecture) + raise Chef::Exceptions::Win32ArchitectureIncorrect, + "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'" + end + end + + def node + run_context && run_context.node + end + + end + end +end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 6dea46bfc9..1b295fc4e1 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -18,6 +18,7 @@ require 'chef/resource/apt_package' require 'chef/resource/bash' +require 'chef/resource/batch' require 'chef/resource/breakpoint' require 'chef/resource/cookbook_file' require 'chef/resource/chef_gem' @@ -49,6 +50,7 @@ require 'chef/resource/package' require 'chef/resource/pacman_package' require 'chef/resource/perl' require 'chef/resource/portage_package' +require 'chef/resource/powershell' require 'chef/resource/python' require 'chef/resource/registry_key' require 'chef/resource/remote_directory' diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb index 21be437e24..85d6c5eafe 100644 --- a/lib/chef/rest.rb +++ b/lib/chef/rest.rb @@ -185,7 +185,11 @@ class Chef elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass. false elsif redirect_location = redirected_to(response) - follow_redirect {api_request(:GET, create_url(redirect_location))} + if [:GET, :HEAD].include?(method) + follow_redirect {api_request(method, create_url(redirect_location))} + else + raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." + end else # have to decompress the body before making an exception for it. But the body could be nil. response.body.replace(response_body) if response.body.respond_to?(:replace) diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb index 004fcad5ad..c30b59b1d7 100644 --- a/lib/chef/win32/version.rb +++ b/lib/chef/win32/version.rb @@ -30,6 +30,8 @@ class Chef # http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx WIN_VERSIONS = { + "Windows 8" => {:major => 6, :minor => 2, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, + "Windows Server 2012" => {:major => 6, :minor => 2, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, "Windows 7" => {:major => 6, :minor => 1, :callable => lambda{ @product_type == VER_NT_WORKSTATION }}, "Windows Server 2008 R2" => {:major => 6, :minor => 1, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, "Windows Server 2008" => {:major => 6, :minor => 0, :callable => lambda{ @product_type != VER_NT_WORKSTATION }}, |