# # Author:: Seth Chisamore () # 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 option :version, :short => "-v", :long => "--version", :description => "Show chef version", :boolean => true, :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"}, :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, # Prior to 0.8.5, win32-service creates interactive services by default, # and we don't want that, so we need to override the service type. :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS, :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