summaryrefslogtreecommitdiff
path: root/lib/chef/application/windows_service.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/application/windows_service.rb')
-rw-r--r--lib/chef/application/windows_service.rb338
1 files changed, 0 insertions, 338 deletions
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
deleted file mode 100644
index 8975556f75..0000000000
--- a/lib/chef/application/windows_service.rb
+++ /dev/null
@@ -1,338 +0,0 @@
-#
-# Author:: Christopher Maier (<maier@lambda.local>)
-# Copyright:: Copyright (c) Chef Software Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require_relative "../../chef"
-require_relative "../monologger"
-require_relative "../application"
-require_relative "../client"
-require_relative "../config"
-require_relative "../handler/error_report"
-require_relative "../log"
-require_relative "../http"
-require "mixlib/cli" unless defined?(Mixlib::CLI)
-require "socket" unless defined?(Socket)
-require "uri" unless defined?(URI)
-require "win32/daemon"
-require_relative "../mixin/shell_out"
-require "chef-utils/dist" unless defined?(ChefUtils::Dist)
-
-class Chef
- class Application
- class WindowsService < ::Win32::Daemon
- include Mixlib::CLI
- include Chef::Mixin::ShellOut
-
- option :config_file,
- short: "-c CONFIG",
- long: "--config CONFIG",
- default: "#{Chef::Config.etc_chef_dir}/client.rb",
- description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs."
-
- option :log_location,
- short: "-L LOGLOCATION",
- long: "--logfile LOGLOCATION",
- description: "Set the log file location."
-
- 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 #{ChefUtils::Dist::Infra::PRODUCT} runs.",
- proc: lambda { |s| s.to_i }
-
- DEFAULT_LOG_LOCATION ||= "#{Chef::Config.c_chef_dir}/client.log".freeze
-
- def service_init
- @service_action_mutex = Mutex.new
- @service_signal = ConditionVariable.new
-
- reconfigure
- Chef::Log.info("#{ChefUtils::Dist::Infra::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?
- # Grab the service_action_mutex to make a chef-client run
- @service_action_mutex.synchronize do
-
- Chef::Log.info("Next #{ChefUtils::Dist::Infra::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]
-
- # Honor splay sleep config
- timeout += rand Chef::Config[:splay]
-
- # run chef-client only if service is in RUNNING state
- next if state != RUNNING
-
- Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} service is starting a #{ChefUtils::Dist::Infra::CLIENT} run...")
- run_chef_client
- rescue SystemExit => e
- # 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}")
-
- end
- end
-
- # Daemon class needs to have all the signal callbacks return
- # before service_main returns.
- Chef::Log.trace("Giving signal callbacks some time to exit...")
- sleep 1
- Chef::Log.trace("Exiting service...")
- end
-
- ################################################################################
- # Control Signal Callback Methods
- ################################################################################
-
- def service_stop
- run_warning_displayed = false
- Chef::Log.info("STOP request from operating system.")
- loop do
- # See if a run is in flight
- if @service_action_mutex.try_lock
- # Run is not in flight. Wake up service_main to exit.
- @service_signal.signal
- @service_action_mutex.unlock
- break
- else
- unless run_warning_displayed
- Chef::Log.info("Currently a #{ChefUtils::Dist::Infra::PRODUCT} run is happening on this system.")
- Chef::Log.info("Service will stop when run is completed.")
- run_warning_displayed = true
- end
-
- Chef::Log.trace("Waiting for #{ChefUtils::Dist::Infra::PRODUCT} run...")
- sleep 1
- end
- end
- Chef::Log.info("Service is stopping....")
- end
-
- def service_pause
- 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 #{ChefUtils::Dist::Infra::PRODUCT} 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
- # 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("SHUTDOWN signal received from the OS.")
-
- # Treat shutdown similar to stop.
-
- service_stop
- end
-
- ################################################################################
- # Internal Methods
- ################################################################################
-
- private
-
- # Initializes Chef::Client instance and runs it
- def run_chef_client
- # The chef client will be started in a new process. We have used shell_out to start the chef-client.
- # The log_location and config_file of the parent process is passed to the new chef-client process.
- # We need to add the --no-fork, as by default it is set to fork=true.
-
- Chef::Log.info "Starting #{ChefUtils::Dist::Infra::CLIENT} in a new process"
- # Pass config params to the new process
- config_params = " --no-fork"
- config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil?
- # log_location might be an event logger and if so we cannot pass as a command argument
- # but shed no tears! If the logger is an event logger, it must have been configured
- # as such in the config file and chef-client will use that when no arg is passed here
- config_params += " -L #{resolve_log_location}" if resolve_log_location.is_a?(String)
-
- # Starts a new process and waits till the process exits
-
- result = shell_out(
- "#{ChefUtils::Dist::Infra::CLIENT}.bat #{config_params}",
- timeout: Chef::Config[:windows_service][:watchdog_timeout],
- logger: Chef::Log
- )
- Chef::Log.trace (result.stdout).to_s
- Chef::Log.trace (result.stderr).to_s
- rescue Mixlib::ShellOut::CommandTimeout => e
- Chef::Log.error "#{ChefUtils::Dist::Infra::CLIENT} timed out\n(#{e})"
- Chef::Log.error(<<-EOF)
- Your #{ChefUtils::Dist::Infra::CLIENT} run timed out. You can increase the time #{ChefUtils::Dist::Infra::CLIENT} is given
- to complete by configuring windows_service.watchdog_timeout in your client.rb.
- EOF
- rescue Mixlib::ShellOut::ShellCommandFailed => e
- Chef::Log.warn "Not able to start #{ChefUtils::Dist::Infra::CLIENT} in new process (#{e})"
- rescue => e
- Chef::Log.error e
- ensure
- # Once process exits, we log the current process' pid
- Chef::Log.info "Child process exited (pid: #{Process.pid})"
- end
-
- 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.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 application.rb
- # See application.rb for related comments.
-
- def configure_logging
- Chef::Log.init(MonoLogger.new(resolve_log_location))
- if want_additional_logger?
- configure_stdout_logger
- end
- Chef::Log.level = resolve_log_level
- end
-
- def configure_stdout_logger
- stdout_logger = MonoLogger.new(STDOUT)
- 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]
- end
-
- # Use of output formatters is assumed if `force_formatter` is set or if
- # `force_logger` is not set
- def using_output_formatter?
- Chef::Config[:force_formatter] || !Chef::Config[:force_logger]
- end
-
- def auto_log_level?
- Chef::Config[:log_level] == :auto
- end
-
- def resolve_log_location
- # STDOUT is the default log location, but makes no sense for a windows service
- Chef::Config[:log_location] == STDOUT ? DEFAULT_LOG_LOCATION : Chef::Config[:log_location]
- 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)
- # 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 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 %r{^(http|https)://}
- Chef::HTTP.new("").streaming_request(config[:config_file]) { |f| apply_config(f.path) }
- else
- ::File.open(config[:config_file]) { |f| apply_config(f.path) }
- end
- rescue Errno::ENOENT
- Chef::Log.warn("*****************************************")
- Chef::Log.warn("Did not find config file: #{config[:config_file]}. Using command line options instead.")
- Chef::Log.warn("*****************************************")
-
- Chef::Config.merge!(config)
- rescue SocketError
- Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}")
- rescue Chef::Exceptions::ConfigurationError => error
- Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}")
- rescue Exception => error
- Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}")
- 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