summaryrefslogtreecommitdiff
path: root/lib/chef
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef')
-rw-r--r--lib/chef/application.rb7
-rw-r--r--lib/chef/application/client.rb3
-rw-r--r--lib/chef/application/windows_service.rb168
-rw-r--r--lib/chef/application/windows_service_manager.rb179
-rw-r--r--lib/chef/config.rb2
-rw-r--r--lib/chef/exceptions.rb10
-rw-r--r--lib/chef/formatters/error_inspectors/registration_error_inspector.rb4
-rw-r--r--lib/chef/knife.rb3
-rw-r--r--lib/chef/log.rb4
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb91
-rw-r--r--lib/chef/monologger.rb93
-rw-r--r--lib/chef/platform.rb8
-rw-r--r--lib/chef/provider/batch.rb35
-rw-r--r--lib/chef/provider/package/rubygems.rb50
-rw-r--r--lib/chef/provider/powershell.rb35
-rw-r--r--lib/chef/provider/script.rb9
-rw-r--r--lib/chef/provider/windows_script.rb73
-rw-r--r--lib/chef/providers.rb2
-rw-r--r--lib/chef/resource/batch.rb31
-rw-r--r--lib/chef/resource/powershell.rb31
-rw-r--r--lib/chef/resource/windows_script.rb62
-rw-r--r--lib/chef/resources.rb2
-rw-r--r--lib/chef/rest.rb6
-rw-r--r--lib/chef/win32/version.rb2
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 }},