summaryrefslogtreecommitdiff
path: root/lib/chef/application
diff options
context:
space:
mode:
authorSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
committerSeth Chisamore <schisamo@opscode.com>2012-10-30 10:39:35 -0400
commit24dc69a9a97e82a6e4207de68d6dcc664178249b (patch)
tree19bb289c9f88b4bbab066bc56b95d6d222fd5c35 /lib/chef/application
parent9348c1c9c80ee757354d624b7dc1b78ebc7605c4 (diff)
downloadchef-24dc69a9a97e82a6e4207de68d6dcc664178249b.tar.gz
[OC-3564] move core Chef to the repo root \o/ \m/
The opscode/chef repository now only contains the core Chef library code used by chef-client, knife and chef-solo!
Diffstat (limited to 'lib/chef/application')
-rw-r--r--lib/chef/application/agent.rb18
-rw-r--r--lib/chef/application/client.rb327
-rw-r--r--lib/chef/application/knife.rb183
-rw-r--r--lib/chef/application/solo.rb254
-rw-r--r--lib/chef/application/windows_service.rb237
5 files changed, 1019 insertions, 0 deletions
diff --git a/lib/chef/application/agent.rb b/lib/chef/application/agent.rb
new file mode 100644
index 0000000000..353d45252e
--- /dev/null
+++ b/lib/chef/application/agent.rb
@@ -0,0 +1,18 @@
+#
+# Author:: AJ Christensen (<aj@opscode.comz>)
+# Copyright:: Copyright (c) 2008 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/application'
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
new file mode 100644
index 0000000000..3a68a41c14
--- /dev/null
+++ b/lib/chef/application/client.rb
@@ -0,0 +1,327 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Author:: Mark Mzyk (mmzyk@opscode.com)
+# Copyright:: Copyright (c) 2008 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/application'
+require 'chef/client'
+require 'chef/config'
+require 'chef/daemon'
+require 'chef/log'
+require 'chef/rest'
+require 'chef/handler/error_report'
+
+
+class Chef::Application::Client < Chef::Application
+
+ # Mimic self_pipe sleep from Unicorn to capture signals safely
+ SELF_PIPE = []
+
+ option :config_file,
+ :short => "-c CONFIG",
+ :long => "--config CONFIG",
+ :default => Chef::Config.platform_specific_path("/etc/chef/client.rb"),
+ :description => "The configuration file to use"
+
+ option :formatter,
+ :short => "-F FORMATTER",
+ :long => "--format FORMATTER",
+ :description => "output format to use"
+
+ option :color,
+ :long => '--[no-]color',
+ :boolean => true,
+ :default => false,
+ :description => "Use colored output, defaults to enabled"
+
+ option :log_level,
+ :short => "-l LEVEL",
+ :long => "--log_level LEVEL",
+ :description => "Set the log level (debug, info, warn, error, fatal)",
+ :proc => lambda { |l| l.to_sym }
+
+ option :log_location,
+ :short => "-L LOGLOCATION",
+ :long => "--logfile LOGLOCATION",
+ :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing",
+ :proc => nil
+
+ option :help,
+ :short => "-h",
+ :long => "--help",
+ :description => "Show this message",
+ :on => :tail,
+ :boolean => true,
+ :show_options => true,
+ :exit => 0
+
+ option :user,
+ :short => "-u USER",
+ :long => "--user USER",
+ :description => "User to set privilege to",
+ :proc => nil
+
+ option :group,
+ :short => "-g GROUP",
+ :long => "--group GROUP",
+ :description => "Group to set privilege to",
+ :proc => nil
+
+ option :daemonize,
+ :short => "-d",
+ :long => "--daemonize",
+ :description => "Daemonize the process",
+ :proc => lambda { |p| true }
+
+ option :pid_file,
+ :short => "-P PID_FILE",
+ :long => "--pid PIDFILE",
+ :description => "Set the PID file location, defaults to /tmp/chef-client.pid",
+ :proc => nil
+
+ option :interval,
+ :short => "-i SECONDS",
+ :long => "--interval SECONDS",
+ :description => "Run chef-client periodically, in seconds",
+ :proc => lambda { |s| s.to_i }
+
+ option :once,
+ :long => "--once",
+ :description => "Cancel any interval or splay options, run chef once and exit",
+ :boolean => true
+
+ option :json_attribs,
+ :short => "-j JSON_ATTRIBS",
+ :long => "--json-attributes JSON_ATTRIBS",
+ :description => "Load attributes from a JSON file or URL",
+ :proc => nil
+
+ option :node_name,
+ :short => "-N NODE_NAME",
+ :long => "--node-name NODE_NAME",
+ :description => "The node name for this client",
+ :proc => nil
+
+ option :splay,
+ :short => "-s SECONDS",
+ :long => "--splay SECONDS",
+ :description => "The splay time for running at intervals, in seconds",
+ :proc => lambda { |s| s.to_i }
+
+ option :chef_server_url,
+ :short => "-S CHEFSERVERURL",
+ :long => "--server CHEFSERVERURL",
+ :description => "The chef server URL",
+ :proc => nil
+
+ option :validation_key,
+ :short => "-K KEY_FILE",
+ :long => "--validation_key KEY_FILE",
+ :description => "Set the validation key file location, used for registering new clients",
+ :proc => nil
+
+ option :client_key,
+ :short => "-k KEY_FILE",
+ :long => "--client_key KEY_FILE",
+ :description => "Set the client key file location",
+ :proc => nil
+
+ option :environment,
+ :short => '-E ENVIRONMENT',
+ :long => '--environment ENVIRONMENT',
+ :description => 'Set the Chef Environment on the node'
+
+ option :version,
+ :short => "-v",
+ :long => "--version",
+ :description => "Show chef version",
+ :boolean => true,
+ :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
+ :exit => 0
+
+ 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)
+ }
+ }
+
+ option :why_run,
+ :short => '-W',
+ :long => '--why-run',
+ :description => 'Enable whyrun mode',
+ :boolean => true
+
+ option :client_fork,
+ :short => "-f",
+ :long => "--fork",
+ :description => "Fork client",
+ :boolean => true
+
+ option :enable_reporting,
+ :short => "-R",
+ :long => "--enable-reporting",
+ :description => "Enable reporting data collection for chef runs",
+ :boolean => true
+
+ attr_reader :chef_client_json
+
+ def initialize
+ super
+
+ @chef_client = nil
+ @chef_client_json = nil
+ end
+
+ # Reconfigure the chef client
+ # Re-open the JSON attributes and load them into the node
+ def reconfigure
+ super
+
+ Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
+ unless Chef::Config[:exception_handlers].any? {|h| Chef::Handler::ErrorReport === h}
+ Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new
+ end
+
+ if Chef::Config[:daemonize]
+ Chef::Config[:interval] ||= 1800
+ end
+
+ if Chef::Config[:once]
+ Chef::Config[:interval] = nil
+ Chef::Config[:splay] = nil
+ end
+
+ if Chef::Config[:json_attribs]
+ begin
+ json_io = case Chef::Config[:json_attribs]
+ when /^(http|https):\/\//
+ @rest = Chef::REST.new(Chef::Config[:json_attribs], nil, nil)
+ @rest.get_rest(Chef::Config[:json_attribs], true).open
+ else
+ open(Chef::Config[:json_attribs])
+ end
+ rescue SocketError => error
+ Chef::Application.fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2)
+ rescue Errno::ENOENT => error
+ Chef::Application.fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2)
+ rescue Errno::EACCES => error
+ Chef::Application.fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2)
+ rescue Exception => error
+ Chef::Application.fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2)
+ end
+
+ begin
+ @chef_client_json = Chef::JSONCompat.from_json(json_io.read)
+ json_io.close unless json_io.closed?
+ rescue JSON::ParserError => error
+ Chef::Application.fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2)
+ end
+ end
+ end
+
+ def configure_logging
+ super
+ Mixlib::Authentication::Log.use_log_devices( Chef::Log )
+ Ohai::Log.use_log_devices( Chef::Log )
+ end
+
+ def setup_application
+ Chef::Daemon.change_privilege
+ end
+
+ # Run the chef client, optionally daemonizing or looping at intervals.
+ def run_application
+ unless Chef::Platform.windows?
+ SELF_PIPE.replace IO.pipe
+
+ trap("USR1") do
+ Chef::Log.info("SIGUSR1 received, waking up")
+ SELF_PIPE[1].putc('.') # wakeup master process from select
+ end
+ end
+
+ if Chef::Config[:version]
+ puts "Chef version: #{::Chef::VERSION}"
+ end
+
+ if Chef::Config[:daemonize]
+ Chef::Daemon.daemonize("chef-client")
+ end
+
+ loop do
+ begin
+ if Chef::Config[:splay]
+ splay = rand Chef::Config[:splay]
+ Chef::Log.debug("Splay sleep #{splay} seconds")
+ sleep splay
+ end
+ @chef_client = Chef::Client.new(
+ @chef_client_json,
+ :override_runlist => config[:override_runlist]
+ )
+ @chef_client_json = nil
+
+ @chef_client.run
+ @chef_client = nil
+ if Chef::Config[:interval]
+ Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
+ unless SELF_PIPE.empty?
+ client_sleep Chef::Config[:interval]
+ else
+ # Windows
+ sleep Chef::Config[:interval]
+ end
+ 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
+ if Chef::Config[:interval]
+ Chef::Log.error("#{e.class}: #{e}")
+ Chef::Application.debug_stacktrace(e)
+ Chef::Log.error("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
+ unless SELF_PIPE.empty?
+ client_sleep Chef::Config[:interval]
+ else
+ # Windows
+ sleep Chef::Config[:interval]
+ end
+ retry
+ else
+ Chef::Application.debug_stacktrace(e)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ end
+ end
+ end
+ end
+
+ private
+
+ def client_sleep(sec)
+ IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
+ SELF_PIPE[0].getc
+ end
+end
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
new file mode 100644
index 0000000000..629cd9fc5f
--- /dev/null
+++ b/lib/chef/application/knife.rb
@@ -0,0 +1,183 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com)
+# Copyright:: Copyright (c) 2009 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/knife'
+require 'chef/application'
+require 'mixlib/log'
+require 'ohai/config'
+
+class Chef::Application::Knife < Chef::Application
+
+ NO_COMMAND_GIVEN = "You need to pass a sub-command (e.g., knife SUB-COMMAND)\n"
+
+ banner "Usage: knife sub-command (options)"
+
+ option :config_file,
+ :short => "-c CONFIG",
+ :long => "--config CONFIG",
+ :description => "The configuration file to use",
+ :proc => lambda { |path| File.expand_path(path, Dir.pwd) }
+
+ verbosity_level = 0
+ option :verbosity,
+ :short => '-V',
+ :long => '--verbose',
+ :description => "More verbose output. Use twice for max verbosity",
+ :proc => Proc.new { verbosity_level += 1},
+ :default => 0
+
+ option :color,
+ :long => '--[no-]color',
+ :boolean => true,
+ :default => true,
+ :description => "Use colored output, defaults to enabled"
+
+ option :environment,
+ :short => "-E ENVIRONMENT",
+ :long => "--environment ENVIRONMENT",
+ :description => "Set the Chef environment"
+
+ option :editor,
+ :short => "-e EDITOR",
+ :long => "--editor EDITOR",
+ :description => "Set the editor to use for interactive commands",
+ :default => ENV['EDITOR']
+
+ option :disable_editing,
+ :short => "-d",
+ :long => "--disable-editing",
+ :description => "Do not open EDITOR, just accept the data as is",
+ :boolean => true,
+ :defaut => false
+
+ option :help,
+ :short => "-h",
+ :long => "--help",
+ :description => "Show this message",
+ :on => :tail,
+ :boolean => true
+
+ option :node_name,
+ :short => "-u USER",
+ :long => "--user USER",
+ :description => "API Client Username"
+
+ option :client_key,
+ :short => "-k KEY",
+ :long => "--key KEY",
+ :description => "API Client Key",
+ :proc => lambda { |path| File.expand_path(path, Dir.pwd) }
+
+ option :chef_server_url,
+ :short => "-s URL",
+ :long => "--server-url URL",
+ :description => "Chef Server URL"
+
+ option :yes,
+ :short => "-y",
+ :long => "--yes",
+ :description => "Say yes to all prompts for confirmation"
+
+ option :defaults,
+ :long => "--defaults",
+ :description => "Accept default values for all questions"
+
+ option :print_after,
+ :long => "--print-after",
+ :description => "Show the data after a destructive operation"
+
+ option :format,
+ :short => "-F FORMAT",
+ :long => "--format FORMAT",
+ :description => "Which format to use for output",
+ :default => "summary"
+
+ option :version,
+ :short => "-v",
+ :long => "--version",
+ :description => "Show chef version",
+ :boolean => true,
+ :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
+ :exit => 0
+
+
+ # Run knife
+ def run
+ Mixlib::Log::Formatter.show_time = false
+ validate_and_parse_options
+ quiet_traps
+ Chef::Knife.run(ARGV, options)
+ exit 0
+ end
+
+ private
+
+ def quiet_traps
+ trap("TERM") do
+ exit 1
+ end
+
+ trap("INT") do
+ exit 2
+ end
+ end
+
+ def validate_and_parse_options
+ # Checking ARGV validity *before* parse_options because parse_options
+ # mangles ARGV in some situations
+ if no_command_given?
+ print_help_and_exit(1, NO_COMMAND_GIVEN)
+ elsif no_subcommand_given?
+ if (want_help? || want_version?)
+ print_help_and_exit
+ else
+ print_help_and_exit(2, NO_COMMAND_GIVEN)
+ end
+ end
+ end
+
+ def no_subcommand_given?
+ ARGV[0] =~ /^-/
+ end
+
+ def no_command_given?
+ ARGV.empty?
+ end
+
+ def want_help?
+ ARGV[0] =~ /^(--help|-h)$/
+ end
+
+ def want_version?
+ ARGV[0] =~ /^(--version|-v)$/
+ end
+
+ def print_help_and_exit(exitcode=1, fatal_message=nil)
+ Chef::Log.error(fatal_message) if fatal_message
+
+ begin
+ self.parse_options
+ rescue OptionParser::InvalidOption => e
+ puts "#{e}\n"
+ end
+ puts self.opt_parser
+ puts
+ Chef::Knife.list_commands
+ exit exitcode
+ end
+
+end
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
new file mode 100644
index 0000000000..7ec6c36e76
--- /dev/null
+++ b/lib/chef/application/solo.rb
@@ -0,0 +1,254 @@
+#
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Author:: Mark Mzyk (mmzyk@opscode.com)
+# Copyright:: Copyright (c) 2008 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'
+require 'chef/application'
+require 'chef/client'
+require 'chef/config'
+require 'chef/daemon'
+require 'chef/log'
+require 'chef/rest'
+require 'open-uri'
+require 'fileutils'
+
+class Chef::Application::Solo < Chef::Application
+
+ option :config_file,
+ :short => "-c CONFIG",
+ :long => "--config CONFIG",
+ :default => Chef::Config.platform_specfic_path('/etc/chef/solo.rb'),
+ :description => "The configuration file to use"
+
+ option :formatter,
+ :short => "-F FORMATTER",
+ :long => "--format FORMATTER",
+ :description => "output format to use"
+
+ option :color,
+ :long => '--[no-]color',
+ :boolean => true,
+ :default => false,
+ :description => "Use colored output, defaults to disabled"
+
+ option :log_level,
+ :short => "-l LEVEL",
+ :long => "--log_level LEVEL",
+ :description => "Set the log level (debug, info, warn, error, fatal)",
+ :proc => lambda { |l| l.to_sym }
+
+ option :log_location,
+ :short => "-L LOGLOCATION",
+ :long => "--logfile LOGLOCATION",
+ :description => "Set the log file location, defaults to STDOUT",
+ :proc => nil
+
+ option :help,
+ :short => "-h",
+ :long => "--help",
+ :description => "Show this message",
+ :on => :tail,
+ :boolean => true,
+ :show_options => true,
+ :exit => 0
+
+ option :user,
+ :short => "-u USER",
+ :long => "--user USER",
+ :description => "User to set privilege to",
+ :proc => nil
+
+ option :group,
+ :short => "-g GROUP",
+ :long => "--group GROUP",
+ :description => "Group to set privilege to",
+ :proc => nil
+
+ option :daemonize,
+ :short => "-d",
+ :long => "--daemonize",
+ :description => "Daemonize the process",
+ :proc => lambda { |p| true }
+
+ option :interval,
+ :short => "-i SECONDS",
+ :long => "--interval SECONDS",
+ :description => "Run chef-client periodically, in seconds",
+ :proc => lambda { |s| s.to_i }
+
+ option :json_attribs,
+ :short => "-j JSON_ATTRIBS",
+ :long => "--json-attributes JSON_ATTRIBS",
+ :description => "Load attributes from a JSON file or URL",
+ :proc => nil
+
+ option :node_name,
+ :short => "-N NODE_NAME",
+ :long => "--node-name NODE_NAME",
+ :description => "The node name for this client",
+ :proc => nil
+
+ option :splay,
+ :short => "-s SECONDS",
+ :long => "--splay SECONDS",
+ :description => "The splay time for running at intervals, in seconds",
+ :proc => lambda { |s| s.to_i }
+
+ option :recipe_url,
+ :short => "-r RECIPE_URL",
+ :long => "--recipe-url RECIPE_URL",
+ :description => "Pull down a remote gzipped tarball of recipes and untar it to the cookbook cache.",
+ :proc => nil
+
+ option :version,
+ :short => "-v",
+ :long => "--version",
+ :description => "Show chef version",
+ :boolean => true,
+ :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
+ :exit => 0
+
+ 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)
+ }
+ }
+
+ option :client_fork,
+ :short => "-f",
+ :long => "--fork",
+ :description => "Fork client",
+ :boolean => true
+
+ option :why_run,
+ :short => '-W',
+ :long => '--why-run',
+ :description => 'Enable whyrun mode',
+ :boolean => true
+
+ attr_reader :chef_solo_json
+
+ def initialize
+ super
+ @chef_solo = nil
+ @chef_solo_json = nil
+ end
+
+ def reconfigure
+ super
+
+ Chef::Config[:solo] = true
+
+ if Chef::Config[:daemonize]
+ Chef::Config[:interval] ||= 1800
+ end
+
+ if Chef::Config[:json_attribs]
+ begin
+ json_io = case Chef::Config[:json_attribs]
+ when /^(http|https):\/\//
+ @rest = Chef::REST.new(Chef::Config[:json_attribs], nil, nil)
+ @rest.get_rest(Chef::Config[:json_attribs], true).open
+ else
+ open(Chef::Config[:json_attribs])
+ end
+ rescue SocketError => error
+ Chef::Application.fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2)
+ rescue Errno::ENOENT => error
+ Chef::Application.fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2)
+ rescue Errno::EACCES => error
+ Chef::Application.fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2)
+ rescue Exception => error
+ Chef::Application.fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2)
+ end
+
+ begin
+ @chef_solo_json = Chef::JSONCompat.from_json(json_io.read)
+ json_io.close unless json_io.closed?
+ rescue JSON::ParserError => error
+ Chef::Application.fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2)
+ end
+ end
+
+ if Chef::Config[:recipe_url]
+ cookbooks_path = Array(Chef::Config[:cookbook_path]).detect{|e| e =~ /\/cookbooks\/*$/ }
+ recipes_path = File.expand_path(File.join(cookbooks_path, '..'))
+ target_file = File.join(recipes_path, 'recipes.tgz')
+
+ Chef::Log.debug "Creating path #{recipes_path} to extract recipes into"
+ FileUtils.mkdir_p recipes_path
+ path = File.join(recipes_path, 'recipes.tgz')
+ File.open(path, 'wb') do |f|
+ open(Chef::Config[:recipe_url]) do |r|
+ f.write(r.read)
+ end
+ end
+ Chef::Mixin::Command.run_command(:command => "tar zxvfC #{path} #{recipes_path}")
+ end
+ end
+
+ def setup_application
+ Chef::Daemon.change_privilege
+ end
+
+ def run_application
+ if Chef::Config[:daemonize]
+ Chef::Daemon.daemonize("chef-client")
+ end
+
+ loop do
+ begin
+ if Chef::Config[:splay]
+ splay = rand Chef::Config[:splay]
+ Chef::Log.debug("Splay sleep #{splay} seconds")
+ sleep splay
+ end
+
+ @chef_solo = Chef::Client.new(
+ @chef_solo_json,
+ :override_runlist => config[:override_runlist]
+ )
+ @chef_solo.run
+ @chef_solo = nil
+ if Chef::Config[:interval]
+ Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
+ sleep Chef::Config[:interval]
+ else
+ Chef::Application.exit! "Exiting", 0
+ end
+ rescue SystemExit => e
+ raise
+ rescue Exception => e
+ if Chef::Config[:interval]
+ Chef::Log.error("#{e.class}: #{e}")
+ Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
+ Chef::Log.fatal("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
+ sleep Chef::Config[:interval]
+ retry
+ else
+ Chef::Application.debug_stacktrace(e)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
new file mode 100644
index 0000000000..961ec8f92f
--- /dev/null
+++ b/lib/chef/application/windows_service.rb
@@ -0,0 +1,237 @@
+#
+# Author:: Christopher Maier (<maier@lambda.local>)
+# 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 'chef'
+require 'chef/application'
+require 'chef/client'
+require 'chef/config'
+require 'chef/handler/error_report'
+require 'chef/log'
+require 'chef/rest'
+require 'mixlib/cli'
+require 'socket'
+require 'win32/daemon'
+
+class Chef
+ class Application
+ class WindowsService < ::Win32::Daemon
+ include Mixlib::CLI
+
+ option :config_file,
+ :short => "-c CONFIG",
+ :long => "--config CONFIG",
+ :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb",
+ :description => ""
+
+ option :log_location,
+ :short => "-L LOGLOCATION",
+ :long => "--logfile LOGLOCATION",
+ :description => "Set the log file location",
+ :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+
+ option :splay,
+ :short => "-s SECONDS",
+ :long => "--splay SECONDS",
+ :description => "The splay time for running at intervals, in seconds",
+ :proc => lambda { |s| s.to_i }
+
+ option :interval,
+ :short => "-i SECONDS",
+ :long => "--interval SECONDS",
+ :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
+ reconfigure
+ Chef::Log.info("Chef Client Service initialized")
+ end
+
+ def service_main(*startup_parameters)
+
+ while running?
+ if state == RUNNING
+ begin
+ # Reconfigure each time through to pick up any changes in the client file
+ Chef::Log.info("Reconfiguring with startup parameters")
+ reconfigure(startup_parameters)
+
+ splay = rand Chef::Config[:splay]
+ Chef::Log.debug("Splay sleep #{splay} seconds")
+ sleep splay
+
+ # If we've stopped, then bail out now, instead of going on to run Chef
+ next if state != RUNNING
+
+ @chef_client = Chef::Client.new(
+ @chef_client_json,
+ :override_runlist => config[:override_runlist]
+ )
+ @chef_client_json = nil
+
+ @chef_client.run
+ @chef_client = nil
+
+ 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
+ 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
+ end
+
+ ################################################################################
+ # Control Signal Callback Methods
+ ################################################################################
+
+ def service_stop
+ Chef::Log.info("SERVICE_CONTROL_STOP received, stopping")
+ end
+
+ def service_pause
+ Chef::Log.info("SERVICE_CONTROL_PAUSE received, pausing")
+ end
+
+ def service_resume
+ Chef::Log.info("SERVICE_CONTROL_CONTINUE received, resuming")
+ end
+
+ def service_shutdown
+ Chef::Log.info("SERVICE_CONTROL_SHUTDOWN received, shutting down")
+ end
+
+ ################################################################################
+ # Internal Methods
+ ################################################################################
+
+ private
+
+ def apply_config(config_file_path)
+ Chef::Config.from_file(config_file_path)
+ Chef::Config.merge!(config)
+ end
+
+ # Lifted from Chef::Application, with addition of optional startup parameters
+ # for playing nicely with Windows Services
+ def reconfigure(startup_parameters=[])
+ configure_chef startup_parameters
+ configure_logging
+
+ Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
+ unless Chef::Config[:exception_handlers].any? {|h| Chef::Handler::ErrorReport === h}
+ Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new
+ end
+
+ Chef::Config[:interval] ||= 1800
+ end
+
+ # Lifted from Chef::Application and Chef::Application::Client
+ # MUST BE RUN AFTER configuration has been parsed!
+ def configure_logging
+ # Implementation from Chef::Application
+ Chef::Log.init(Chef::Config[:log_location])
+ Chef::Log.level = Chef::Config[:log_level]
+
+ # Implementation from Chef::Application::Client
+ Mixlib::Authentication::Log.use_log_devices( Chef::Log )
+ Ohai::Log.use_log_devices( Chef::Log )
+ end
+
+ def configure_chef(startup_parameters)
+ # Bit of a hack ahead:
+ # It is possible to specify a service's binary_path_name with arguments, like "foo.exe -x argX".
+ # It is also possible to specify startup parameters separately, either via the the Services manager
+ # or by using the registry (I think).
+
+ # In order to accommodate all possible sources of parameterization, we first parse any command line
+ # arguments. We then parse any startup parameters. This works, because Mixlib::CLI reuses its internal
+ # 'config' hash; thus, anything in startup parameters will override any command line parameters that
+ # might be set via the service's binary_path_name
+ #
+ # All these parameters then get layered on top of those from Chef::Config
+
+ parse_options # Operates on ARGV by default
+ parse_options startup_parameters
+
+ begin
+ case config[:config_file]
+ when /^(http|https):\/\//
+ Chef::REST.new("", nil, nil).fetch(config[:config_file]) { |f| apply_config(f.path) }
+ else
+ ::File::open(config[:config_file]) { |f| apply_config(f.path) }
+ end
+ rescue Errno::ENOENT => error
+ Chef::Log.warn("*****************************************")
+ Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
+ Chef::Log.warn("*****************************************")
+
+ Chef::Config.merge!(config)
+ rescue SocketError => error
+ Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", 2)
+ rescue Chef::Exceptions::ConfigurationError => error
+ Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
+ rescue Exception => error
+ Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
+ 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
+
+# To run this file as a service, it must be called as a script from within
+# the Windows Service framework. In that case, kick off the main loop!
+if __FILE__ == $0
+ Chef::Application::WindowsService.mainloop
+end