diff options
Diffstat (limited to 'lib/chef/provider/service')
-rw-r--r-- | lib/chef/provider/service/arch.rb | 113 | ||||
-rw-r--r-- | lib/chef/provider/service/debian.rb | 152 | ||||
-rw-r--r-- | lib/chef/provider/service/freebsd.rb | 175 | ||||
-rw-r--r-- | lib/chef/provider/service/gentoo.rb | 67 | ||||
-rw-r--r-- | lib/chef/provider/service/init.rb | 87 | ||||
-rw-r--r-- | lib/chef/provider/service/insserv.rb | 52 | ||||
-rw-r--r-- | lib/chef/provider/service/invokercd.rb | 35 | ||||
-rw-r--r-- | lib/chef/provider/service/macosx.rb | 144 | ||||
-rw-r--r-- | lib/chef/provider/service/redhat.rb | 77 | ||||
-rw-r--r-- | lib/chef/provider/service/simple.rb | 172 | ||||
-rw-r--r-- | lib/chef/provider/service/solaris.rb | 86 | ||||
-rw-r--r-- | lib/chef/provider/service/systemd.rb | 115 | ||||
-rw-r--r-- | lib/chef/provider/service/upstart.rb | 232 | ||||
-rw-r--r-- | lib/chef/provider/service/windows.rb | 163 |
14 files changed, 1670 insertions, 0 deletions
diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb new file mode 100644 index 0000000000..8c8216c37f --- /dev/null +++ b/lib/chef/provider/service/arch.rb @@ -0,0 +1,113 @@ +# +# Author:: Jan Zimmek (<jan.zimmek@web.de>) +# Copyright:: Copyright (c) 2010 Jan Zimmek +# 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/service/init' +require 'chef/mixin/command' + +class Chef::Provider::Service::Arch < Chef::Provider::Service::Init + + def initialize(new_resource, run_context) + super + @init_command = "/etc/rc.d/#{@new_resource.service_name}" + end + + def load_current_resource + raise Chef::Exceptions::Service, "Could not find /etc/rc.conf" unless ::File.exists?("/etc/rc.conf") + raise Chef::Exceptions::Service, "No DAEMONS found in /etc/rc.conf" unless ::File.read("/etc/rc.conf").match(/DAEMONS=\((.*)\)/m) + super + + @current_resource.enabled(daemons.include?(@current_resource.service_name)) + @current_resource + end + + # Get list of all daemons from the file '/etc/rc.conf'. + # Mutiple lines and background form are supported. Example: + # DAEMONS=(\ + # foobar \ + # @example \ + # !net \ + # ) + def daemons + entries = [] + if ::File.read("/etc/rc.conf").match(/DAEMONS=\((.*)\)/m) + entries += $1.gsub(/\\?[\r\n]/, ' ').gsub(/# *[^ ]+/,' ').split(' ') if $1.length > 0 + end + + yield(entries) if block_given? + + entries + end + + # FIXME: Multiple entries of DAEMONS will cause very bad results :) + def update_daemons(entries) + content = ::File.read("/etc/rc.conf").gsub(/DAEMONS=\((.*)\)/m, "DAEMONS=(#{entries.join(' ')})") + ::File.open("/etc/rc.conf", "w") do |f| + f.write(content) + end + end + + def enable_service() + new_daemons = [] + entries = daemons + + if entries.include?(new_resource.service_name) or entries.include?("@#{new_resource.service_name}") + # exists and already enabled (or already enabled as a background service) + # new_daemons += entries + else + if entries.include?("!#{new_resource.service_name}") + # exists but disabled + entries.each do |daemon| + if daemon == "!#{new_resource.service_name}" + new_daemons << new_resource.service_name + else + new_daemons << daemon + end + end + else + # does not exist + new_daemons += entries + new_daemons << new_resource.service_name + end + update_daemons(new_daemons) + end + end + + def disable_service() + new_daemons = [] + entries = daemons + + if entries.include?("!#{new_resource.service_name}") + # exists and disabled + # new_daemons += entries + else + if entries.include?(new_resource.service_name) or entries.include?("@#{new_resource.service_name}") + # exists but enabled (or enabled as a back-ground service) + # FIXME: Does arch support !@foobar ? + entries.each do |daemon| + if [new_resource.service_name, "@#{new_resource.service_name}"].include?(daemon) + new_daemons << "!#{new_resource.service_name}" + else + new_daemons << daemon + end + end + end + update_daemons(new_daemons) + end + end + +end diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb new file mode 100644 index 0000000000..e2a0f60d91 --- /dev/null +++ b/lib/chef/provider/service/debian.rb @@ -0,0 +1,152 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.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/provider/service' +require 'chef/provider/service/init' +require 'chef/mixin/command' + +class Chef + class Provider + class Service + class Debian < Chef::Provider::Service::Init + UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i + UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i + + def load_current_resource + super + @priority_success = true + @rcd_status = nil + @current_resource.priority(get_priority) + @current_resource.enabled(service_currently_enabled?(@current_resource.priority)) + @current_resource + end + + def define_resource_requirements + # do not call super here, inherit only shared_requirements + shared_resource_requirements + requirements.assert(:all_actions) do |a| + update_rcd = "/usr/sbin/update-rc.d" + a.assertion { ::File.exists? update_rcd } + a.failure_message Chef::Exceptions::Service, "#{update_rcd} does not exist!" + # no whyrun recovery - this is a base system component of debian + # distros and must be present + end + + requirements.assert(:all_actions) do |a| + a.assertion { @priority_success } + a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} failed - #{@rcd_status.inspect}" + # This can happen if the service is not yet installed,so we'll fake it. + a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.", + "Assigning temporary priorities to continue.", + "If this service is not properly installed prior to this point, this will fail."] do + temp_priorities = {"6"=>[:stop, "20"], + "0"=>[:stop, "20"], + "1"=>[:stop, "20"], + "2"=>[:start, "20"], + "3"=>[:start, "20"], + "4"=>[:start, "20"], + "5"=>[:start, "20"]} + @current_resource.priority(temp_priorities) + end + end + end + + def get_priority + priority = {} + + @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} remove") do |pid, stdin, stdout, stderr| + + [stdout, stderr].each do |iop| + iop.each_line do |line| + if UPDATE_RC_D_PRIORITIES =~ line + # priority[runlevel] = [ S|K, priority ] + # S = Start, K = Kill + # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot + priority[$1] = [($2 == "S" ? :start : :stop), $3] + end + if line =~ UPDATE_RC_D_ENABLED_MATCHES + enabled = true + end + end + end + end + + unless @rcd_status.exitstatus == 0 + @priority_success = false + end + priority + end + + def service_currently_enabled?(priority) + enabled = false + priority.each { |runlevel, arguments| + Chef::Log.debug("#{@new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}") + # if we are in a update-rc.d default startup runlevel && we start in this runlevel + if (2..5).include?(runlevel.to_i) && arguments[0] == :start + enabled = true + end + } + + enabled + end + + def enable_service() + if @new_resource.priority.is_a? Integer + run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") + run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}") + elsif @new_resource.priority.is_a? Hash + # we call the same command regardless of we're enabling or disabling + # users passing a Hash are responsible for setting their own start priorities + set_priority() + else # No priority, go with update-rc.d defaults + run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") + run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults") + end + + end + + def disable_service() + if @new_resource.priority.is_a? Integer + # Stop processes in reverse order of start using '100 - start_priority' + run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") + run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .") + elsif @new_resource.priority.is_a? Hash + # we call the same command regardless of we're enabling or disabling + # users passing a Hash are responsible for setting their own stop priorities + set_priority() + else + # no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start + run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") + run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 .") + end + end + + def set_priority() + args = "" + @new_resource.priority.each do |level, o| + action = o[0] + priority = o[1] + args += "#{action} #{priority} #{level} . " + end + run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove") + run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} #{args}") + end + end + end + end +end diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb new file mode 100644 index 0000000000..b875838ec2 --- /dev/null +++ b/lib/chef/provider/service/freebsd.rb @@ -0,0 +1,175 @@ +# +# Author:: Bryan McLellan (btm@loftninjas.org) +# Copyright:: Copyright (c) 2009 Bryan McLellan +# 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/mixin/shell_out' +require 'chef/provider/service' +require 'chef/mixin/command' + +class Chef + class Provider + class Service + class Freebsd < Chef::Provider::Service::Init + + include Chef::Mixin::ShellOut + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + @rcd_script_found = true + @enabled_state_found = false + # Determine if we're talking about /etc/rc.d or /usr/local/etc/rc.d + if ::File.exists?("/etc/rc.d/#{current_resource.service_name}") + @init_command = "/etc/rc.d/#{current_resource.service_name}" + elsif ::File.exists?("/usr/local/etc/rc.d/#{current_resource.service_name}") + @init_command = "/usr/local/etc/rc.d/#{current_resource.service_name}" + else + @rcd_script_found = false + return + end + Chef::Log.debug("#{@current_resource} found at #{@init_command}") + determine_current_status! + # Default to disabled if the service doesn't currently exist + # at all + var_name = service_enable_variable_name + if ::File.exists?("/etc/rc.conf") && var_name + read_rc_conf.each do |line| + case line + when /#{Regexp.escape(var_name)}="(\w+)"/ + @enabled_state_found = true + if $1 =~ /[Yy][Ee][Ss]/ + @current_resource.enabled true + elsif $1 =~ /[Nn][Oo][Nn]?[Oo]?[Nn]?[Ee]?/ + @current_resource.enabled false + end + end + end + end + unless @current_resource.enabled + Chef::Log.debug("#{@new_resource.name} enable/disable state unknown") + @current_resource.enabled false + end + + @current_resource + end + + def define_resource_requirements + shared_resource_requirements + requirements.assert(:start, :enable, :reload, :restart) do |a| + a.assertion { @rcd_script_found } + a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the rc.d script" + end + + requirements.assert(:all_actions) do |a| + a.assertion { @enabled_state_found } + # for consistentcy with original behavior, this will not fail in non-whyrun mode; + # rather it will silently set enabled state=>false + a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled." + end + + requirements.assert(:start, :enable, :reload, :restart) do |a| + a.assertion { @rcd_script_found && service_enable_variable_name != nil } + a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{@init_command} and rcvar" + # No recovery in whyrun mode - the init file is present but not correct. + end + end + + def start_service + if @new_resource.start_command + super + else + shell_out!("#{@init_command} faststart") + end + end + + def stop_service + if @new_resource.stop_command + super + else + shell_out!("#{@init_command} faststop") + end + end + + def restart_service + if @new_resource.restart_command + + super + elsif @new_resource.supports[:restart] + shell_out!("#{@init_command} fastrestart") + else + stop_service + sleep 1 + start_service + end + end + + def read_rc_conf + ::File.open("/etc/rc.conf", 'r') { |file| file.readlines } + end + + def write_rc_conf(lines) + ::File.open("/etc/rc.conf", 'w') do |file| + lines.each { |line| file.puts(line) } + end + end + + # The variable name used in /etc/rc.conf for enabling this service + def service_enable_variable_name + # Look for name="foo" in the shell script @init_command. Use this for determining the variable name in /etc/rc.conf + # corresponding to this service + # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need + # to set mysql_enable="YES" in /etc/rc.conf$ + if @rcd_script_found + ::File.open(@init_command) do |rcscript| + rcscript.each_line do |line| + if line =~ /^name="?(\w+)"?/ + return $1 + "_enable" + end + end + end + # some scripts support multiple instances through symlinks such as openvpn. + # We should get the service name from rcvar. + Chef::Log.debug("name=\"service\" not found at #{@init_command}. falling back to rcvar") + sn = shell_out!("#{@init_command} rcvar").stdout[/(\w+_enable)=/, 1] + return sn + end + # Fallback allows us to keep running in whyrun mode when + # the script does not exist. + @new_resource.service_name + end + + def set_service_enable(value) + lines = read_rc_conf + # Remove line that set the old value + lines.delete_if { |line| line =~ /#{Regexp.escape(service_enable_variable_name)}/ } + # And append the line that sets the new value at the end + lines << "#{service_enable_variable_name}=\"#{value}\"" + write_rc_conf(lines) + end + + def enable_service() + set_service_enable("YES") unless @current_resource.enabled + end + + def disable_service() + set_service_enable("NO") if @current_resource.enabled + end + + end + end + end +end diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb new file mode 100644 index 0000000000..45b5a21f9b --- /dev/null +++ b/lib/chef/provider/service/gentoo.rb @@ -0,0 +1,67 @@ +# +# Author:: Lee Jensen (<ljensen@engineyard.com>) +# Author:: AJ Christensen (<aj@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/provider/service' +require 'chef/mixin/command' + +class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init + def load_current_resource + + @new_resource.supports[:status] = true + @new_resource.supports[:restart] = true + @found_script = false + super + + @current_resource.enabled( + Dir.glob("/etc/runlevels/**/#{@current_resource.service_name}").any? do |file| + @found_script = true + exists = ::File.exists? file + readable = ::File.readable? file + Chef::Log.debug "#{@new_resource} exists: #{exists}, readable: #{readable}" + exists and readable + end + ) + Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}" + + @current_resource + end + + def define_resource_requirements + requirements.assert(:all_actions) do |a| + a.assertion { ::File.exists?("/sbin/rc-update") } + a.failure_message Chef::Exceptions::Service, "/sbin/rc-update does not exist" + # no whyrun recovery -t his is a core component whose presence is + # unlikely to be affected by what we do in the course of a chef run + end + + requirements.assert(:all_actions) do |a| + a.assertion { @found_script } + # No failure, just informational output from whyrun + a.whyrun "Could not find service #{@new_resource.service_name} under any runlevel" + end + end + + def enable_service() + run_command(:command => "/sbin/rc-update add #{@new_resource.service_name} default") + end + + def disable_service() + run_command(:command => "/sbin/rc-update del #{@new_resource.service_name} default") + end +end diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb new file mode 100644 index 0000000000..ab843d764d --- /dev/null +++ b/lib/chef/provider/service/init.rb @@ -0,0 +1,87 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.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/mixin/shell_out' +require 'chef/provider/service' +require 'chef/provider/service/simple' +require 'chef/mixin/command' + +class Chef + class Provider + class Service + class Init < Chef::Provider::Service::Simple + + include Chef::Mixin::ShellOut + + def initialize(new_resource, run_context) + super + @init_command = "/etc/init.d/#{@new_resource.service_name}" + end + + def define_resource_requirements + # do not call super here, inherit only shared_requirements + shared_resource_requirements + requirements.assert(:start, :stop, :restart, :reload) do |a| + a.assertion { ::File.exist?(@init_command) } + a.failure_message(Chef::Exceptions::Service, "#{@init_command} does not exist!") + a.whyrun("Init script '#{@init_command}' doesn't exist, assuming a prior action would have created it.") do + # blindly assume that the service exists but is stopped in why run mode: + @status_load_success = false + end + end + end + + def start_service + if @new_resource.start_command + super + else + shell_out!("#{@init_command} start") + end + end + + def stop_service + if @new_resource.stop_command + super + else + shell_out!("#{@init_command} stop") + end + end + + def restart_service + if @new_resource.restart_command + super + elsif @new_resource.supports[:restart] + shell_out!("#{@init_command} restart") + else + stop_service + sleep 1 + start_service + end + end + + def reload_service + if @new_resource.reload_command + super + elsif @new_resource.supports[:reload] + shell_out!("#{@init_command} reload") + end + end + end + end + end +end diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb new file mode 100644 index 0000000000..32152376ee --- /dev/null +++ b/lib/chef/provider/service/insserv.rb @@ -0,0 +1,52 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# 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/provider/service' +require 'chef/provider/service/init' +require 'chef/mixin/command' + +class Chef + class Provider + class Service + class Insserv < Chef::Provider::Service::Init + + def load_current_resource + super + + # Look for a /etc/rc.*/SnnSERVICE link to signifiy that the service would be started in a runlevel + if Dir.glob("/etc/rc**/S*#{@current_resource.service_name}").empty? + @current_resource.enabled false + else + @current_resource.enabled true + end + + @current_resource + end + + def enable_service() + run_command(:command => "/sbin/insserv -r -f #{@new_resource.service_name}") + run_command(:command => "/sbin/insserv -d -f #{@new_resource.service_name}") + end + + def disable_service() + run_command(:command => "/sbin/insserv -r -f #{@new_resource.service_name}") + end + end + end + end +end diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb new file mode 100644 index 0000000000..69a17bb4fb --- /dev/null +++ b/lib/chef/provider/service/invokercd.rb @@ -0,0 +1,35 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.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/provider/service' +require 'chef/provider/service/init' +require 'chef/mixin/command' + +class Chef + class Provider + class Service + class Invokercd < Chef::Provider::Service::Init + + def initialize(new_resource, run_context) + super + @init_command = "/usr/sbin/invoke-rc.d #{@new_resource.service_name}" + end + end + end + end +end diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb new file mode 100644 index 0000000000..72c02779c6 --- /dev/null +++ b/lib/chef/provider/service/macosx.rb @@ -0,0 +1,144 @@ +# +# Author:: Igor Afonov <afonov@gmail.com> +# Copyright:: Copyright (c) 2011 Igor Afonov +# 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/service' + +class Chef + class Provider + class Service + class Macosx < Chef::Provider::Service::Simple + include Chef::Mixin::ShellOut + + PLIST_DIRS = %w{~/Library/LaunchAgents + /Library/LaunchAgents + /Library/LaunchDaemons + /System/Library/LaunchAgents + /System/Library/LaunchDaemons } + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + @plist_size = 0 + @plist = find_service_plist + set_service_status + + @current_resource + end + + def define_resource_requirements + #super + requirements.assert(:enable) do |a| + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable" + end + + requirements.assert(:disable) do |a| + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable" + end + + requirements.assert(:reload) do |a| + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload" + end + + requirements.assert(:all_actions) do |a| + a.assertion { @plist_size < 2 } + a.failure_message Chef::Exceptions::Service, "Several plist files match service name. Please use full service name." + end + + requirements.assert(:all_actions) do |a| + a.assertion { @plist_size > 0 } + # No failrue here in original code - so we also will not + # fail. Instead warn that the service is potentially missing + a.whyrun "Assuming that the service would have been previously installed and is currently disabled." do + @current_resource.enabled(false) + @current_resource.running(false) + end + end + + end + + def start_service + if @current_resource.running + Chef::Log.debug("#{@new_resource} already running, not starting") + else + if @new_resource.start_command + super + else + shell_out!("launchctl load -w '#{@plist}'", :user => @owner_uid, :group => @owner_gid) + end + end + end + + def stop_service + unless @current_resource.running + Chef::Log.debug("#{@new_resource} not running, not stopping") + else + if @new_resource.stop_command + super + else + shell_out!("launchctl unload '#{@plist}'", :user => @owner_uid, :group => @owner_gid) + end + end + end + + def restart_service + if @new_resource.restart_command + super + else + stop_service + sleep 1 + start_service + end + end + + + def set_service_status + return if @plist == nil + + @current_resource.enabled(!@plist.nil?) + + if @current_resource.enabled + @owner_uid = ::File.stat(@plist).uid + @owner_gid = ::File.stat(@plist).gid + + shell_out!("launchctl list", :user => @owner_uid, :group => @owner_gid).stdout.each_line do |line| + case line + when /(\d+|-)\s+(?:\d+|-)\s+(.*\.?)#{@current_resource.service_name}/ + pid = $1 + @current_resource.running(!pid.to_i.zero?) + end + end + else + @current_resource.running(false) + end + end + + private + + def find_service_plist + plists = PLIST_DIRS.inject([]) do |results, dir| + entries = Dir.glob("#{::File.expand_path(dir)}/*#{@current_resource.service_name}*.plist") + entries.any? ? results << entries : results + end + plists.flatten! + @plist_size = plists.size + plists.first + end + end + end + end +end diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb new file mode 100644 index 0000000000..629e4ee0c3 --- /dev/null +++ b/lib/chef/provider/service/redhat.rb @@ -0,0 +1,77 @@ +# +# Author:: AJ Christensen (<aj@hjksolutions.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/provider/service' +require 'chef/provider/service/init' +require 'chef/mixin/shell_out' + +class Chef + class Provider + class Service + class Redhat < Chef::Provider::Service::Init + include Chef::Mixin::ShellOut + + CHKCONFIG_ON = /\d:on/ + CHKCONFIG_MISSING = /No such/ + + def initialize(new_resource, run_context) + super + @init_command = "/sbin/service #{@new_resource.service_name}" + @new_resource.supports[:status] = true + @service_missing = false + end + + def define_resource_requirements + shared_resource_requirements + + requirements.assert(:all_actions) do |a| + chkconfig_file = "/sbin/chkconfig" + a.assertion { ::File.exists? chkconfig_file } + a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!" + end + + requirements.assert(:start, :enable, :reload, :restart) do |a| + a.assertion { !@service_missing } + a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!" + a.whyrun "Assuming service would be disabled. The init script is not presently installed." + end + end + + def load_current_resource + super + + if ::File.exists?("/sbin/chkconfig") + chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1]) + @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON)) + @service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING) + end + + @current_resource + end + + def enable_service() + shell_out! "/sbin/chkconfig #{@new_resource.service_name} on" + end + + def disable_service() + shell_out! "/sbin/chkconfig #{@new_resource.service_name} off" + end + end + end + end +end diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb new file mode 100644 index 0000000000..670c62d480 --- /dev/null +++ b/lib/chef/provider/service/simple.rb @@ -0,0 +1,172 @@ +# +# Author:: Mathieu Sauve-Frankel <msf@kisoku.net> +# Copyright:: Copyright (c) 2009 Mathieu Sauve-Frankel +# 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/mixin/shell_out' +require 'chef/provider/service' +require 'chef/mixin/command' + +class Chef + class Provider + class Service + class Simple < Chef::Provider::Service + + include Chef::Mixin::ShellOut + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + + @status_load_success = true + @ps_command_failed = false + + determine_current_status! + + @current_resource + end + + def whyrun_supported? + true + end + + def shared_resource_requirements + super + requirements.assert(:all_actions) do |a| + a.assertion { @status_load_success } + a.whyrun ["Service status not available. Assuming a prior action would have installed the service.", "Assuming status of not running."] + end + end + + def define_resource_requirements + # FIXME? need reload from service.rb + shared_resource_requirements + requirements.assert(:start) do |a| + a.assertion { @new_resource.start_command } + a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that start_command be set" + end + requirements.assert(:stop) do |a| + a.assertion { @new_resource.stop_command } + a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that stop_command be set" + end + + requirements.assert(:restart) do |a| + a.assertion { @new_resource.restart_command || ( @new_resource.start_command && @new_resource.stop_command ) } + a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires a restart_command or both start_command and stop_command be set in order to perform a restart" + end + + requirements.assert(:reload) do |a| + a.assertion { @new_resource.reload_command } + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} requires a reload_command be set in order to perform a reload" + end + + requirements.assert(:all_actions) do |a| + a.assertion { @new_resource.status_command or @new_resource.supports[:status] or + (!ps_cmd.nil? and !ps_cmd.empty?) } + a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute" + end + requirements.assert(:all_actions) do |a| + a.assertion { !@ps_command_failed } + a.failure_message Chef::Exceptions::Service, "Command #{ps_cmd} failed to execute, cannot determine service current status" + end + end + + def start_service + shell_out!(@new_resource.start_command) + end + + def stop_service + shell_out!(@new_resource.stop_command) + end + + def restart_service + if @new_resource.restart_command + shell_out!(@new_resource.restart_command) + else + stop_service + sleep 1 + start_service + end + end + + def reload_service + shell_out!(@new_resource.reload_command) + end + + protected + def determine_current_status! + if @new_resource.status_command + Chef::Log.debug("#{@new_resource} you have specified a status command, running..") + + begin + if shell_out(@new_resource.status_command).exitstatus == 0 + @current_resource.running true + Chef::Log.debug("#{@new_resource} is running") + end + rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError + # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. + # Temporarily catching different types of exceptions here until we get Shellout fixed. + # TODO: Remove the line before one we get the ShellOut fix. + @status_load_success = false + @current_resource.running false + nil + end + + elsif @new_resource.supports[:status] + Chef::Log.debug("#{@new_resource} supports status, running") + begin + if shell_out("#{@init_command} status").exitstatus == 0 + @current_resource.running true + Chef::Log.debug("#{@new_resource} is running") + end + # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. + # Temporarily catching different types of exceptions here until we get Shellout fixed. + # TODO: Remove the line before one we get the ShellOut fix. + rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError + @status_load_success = false + @current_resource.running false + nil + end + else + Chef::Log.debug "#{@new_resource} falling back to process table inspection" + r = Regexp.new(@new_resource.pattern) + Chef::Log.debug "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list" + begin + shell_out!(ps_cmd).stdout.each_line do |line| + if r.match(line) + @current_resource.running true + break + end + end + + @current_resource.running false unless @current_resource.running + Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}" + # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. + # Temporarily catching different types of exceptions here until we get Shellout fixed. + # TODO: Remove the line before one we get the ShellOut fix. + rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError + @ps_command_failed = true + end + end + end + + def ps_cmd + @run_context.node[:command] && @run_context.node[:command][:ps] + end + end + end + end +end diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb new file mode 100644 index 0000000000..8e131590e8 --- /dev/null +++ b/lib/chef/provider/service/solaris.rb @@ -0,0 +1,86 @@ +# +# Author:: Toomas Pelberg (<toomasp@gmx.net>) +# Copyright:: Copyright (c) 2010 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/service' +require 'chef/mixin/command' + +class Chef + class Provider + class Service + class Solaris < Chef::Provider::Service + + def initialize(new_resource, run_context=nil) + super + @init_command = "/usr/sbin/svcadm" + @status_command = "/bin/svcs -l" + end + + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + unless ::File.exists? "/bin/svcs" + raise Chef::Exceptions::Service, "/bin/svcs does not exist!" + end + @status = service_status.enabled + @current_resource + end + + def enable_service + run_command(:command => "#{@init_command} enable #{@new_resource.service_name}") + return service_status.enabled + end + + def disable_service + run_command(:command => "#{@init_command} disable #{@new_resource.service_name}") + return service_status.enabled + end + + alias_method :stop_service, :disable_service + alias_method :start_service, :enable_service + + def reload_service + run_command(:command => "#{@init_command} refresh #{@new_resource.service_name}") + end + + def restart_service + disable_service + return enable_service + end + + def service_status + status = popen4("#{@status_command} #{@current_resource.service_name}") do |pid, stdin, stdout, stderr| + stdout.each do |line| + case line + when /state\s+online/ + @current_resource.enabled(true) + @current_resource.running(true) + end + end + end + unless @current_resource.enabled + @current_resource.enabled(false) + @current_resource.running(false) + end + @current_resource + end + + end + end + end +end diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb new file mode 100644 index 0000000000..59b4fe1564 --- /dev/null +++ b/lib/chef/provider/service/systemd.rb @@ -0,0 +1,115 @@ +# +# Author:: Stephen Haynes (<sh@nomitor.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 'chef/provider/service' +require 'chef/provider/service/simple' +require 'chef/mixin/command' + +class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + @status_check_success = true + + if @new_resource.status_command + Chef::Log.debug("#{@new_resource} you have specified a status command, running..") + + begin + if run_command_with_systems_locale(:command => @new_resource.status_command) == 0 + @current_resource.running(true) + end + rescue Chef::Exceptions::Exec + @status_check_success = false + @current_resource.running(false) + @current_resource.enabled(false) + nil + end + else + @current_resource.running(is_active?) + end + + @current_resource.enabled(is_enabled?) + @current_resource + end + + def define_resource_requirements + shared_resource_requirements + requirements.assert(:all_actions) do |a| + a.assertion { @status_check_success } + # We won't stop in any case, but in whyrun warn and tell what we're doing. + a.whyrun ["Failed to determine status of #{@new_resource}, using command #{@new_resource.status_command}.", + "Assuming service would have been installed and is disabled"] + end + end + + def start_service + if @current_resource.running + Chef::Log.debug("#{@new_resource} already running, not starting") + else + if @new_resource.start_command + super + else + run_command_with_systems_locale(:command => "/bin/systemctl start #{@new_resource.service_name}") + end + end + end + + def stop_service + unless @current_resource.running + Chef::Log.debug("#{@new_resource} not running, not stopping") + else + if @new_resource.stop_command + super + else + run_command_with_systems_locale(:command => "/bin/systemctl stop #{@new_resource.service_name}") + end + end + end + + def restart_service + if @new_resource.restart_command + super + else + run_command_with_systems_locale(:command => "/bin/systemctl restart #{@new_resource.service_name}") + end + end + + def reload_service + if @new_resource.reload_command + super + else + run_command_with_systems_locale(:command => "/bin/systemctl reload #{@new_resource.service_name}") + end + end + + def enable_service + run_command_with_systems_locale(:command => "/bin/systemctl enable #{@new_resource.service_name}") + end + + def disable_service + run_command_with_systems_locale(:command => "/bin/systemctl disable #{@new_resource.service_name}") + end + + def is_active? + run_command_with_systems_locale({:command => "/bin/systemctl is-active #{@new_resource.service_name}", :ignore_failure => true}) == 0 + end + + def is_enabled? + run_command_with_systems_locale({:command => "/bin/systemctl is-enabled #{@new_resource.service_name}", :ignore_failure => true}) == 0 + end +end diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb new file mode 100644 index 0000000000..763a2aa92b --- /dev/null +++ b/lib/chef/provider/service/upstart.rb @@ -0,0 +1,232 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# Copyright:: Copyright (c) 2010 Bryan McLellan +# 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/service' +require 'chef/provider/service/simple' +require 'chef/mixin/command' +require 'chef/util/file_edit' + +class Chef + class Provider + class Service + class Upstart < Chef::Provider::Service::Simple + UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/ + + # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in. + # In chef, when we ask a service to start, we expect it to have started before performing the next step + # since we have top down dependencies. Which is to say we may follow witha resource next that requires + # that service to be running. According to [2] we can trust that sending a 'goal' such as start will not + # return until that 'goal' is reached, or some error has occured. + # + # [1] http://upstart.ubuntu.com/wiki/JobStates + # [2] http://www.netsplit.com/2008/04/27/upstart-05-events/ + + def initialize(new_resource, run_context) + # TODO: re-evaluate if this is needed after integrating cookbook fix + raise ArgumentError, "run_context cannot be nil" unless run_context + super + + run_context.node + + @job = @new_resource.service_name + + if @new_resource.parameters + @new_resource.parameters.each do |key, value| + @job << " #{key}=#{value}" + end + end + + platform, version = Chef::Platform.find_platform_and_version(run_context.node) + if platform == "ubuntu" && (8.04..9.04).include?(version.to_f) + @upstart_job_dir = "/etc/event.d" + @upstart_conf_suffix = "" + else + @upstart_job_dir = "/etc/init" + @upstart_conf_suffix = ".conf" + end + + @command_success = true # new_resource.status_command= false, means upstart used + @config_file_found = true + @upstart_command_success = true + end + + def define_resource_requirements + # Do not call super, only call shared requirements + shared_resource_requirements + requirements.assert(:all_actions) do |a| + if !@command_success + whyrun_msg = @new_resource.status_command ? "Provided status command #{@new_resource.status_command} failed." : + "Could not determine upstart state for service" + end + a.assertion { @command_success } + # no failure here, just document the assumptions made. + a.whyrun "#{whyrun_msg} Assuming service installed and not running." + end + + requirements.assert(:all_actions) do |a| + a.assertion { @config_file_found } + # no failure here, just document the assumptions made. + a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled." + end + end + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + + # Get running/stopped state + # We do not support searching for a service via ps when using upstart since status is a native + # upstart function. We will however support status_command in case someone wants to do something special. + if @new_resource.status_command + Chef::Log.debug("#{@new_resource} you have specified a status command, running..") + + begin + if run_command_with_systems_locale(:command => @new_resource.status_command) == 0 + @current_resource.running true + end + rescue Chef::Exceptions::Exec + @command_success = false + @current_resource.running false + nil + end + else + begin + if upstart_state == "running" + @current_resource.running true + else + @current_resource.running false + end + rescue Chef::Exceptions::Exec + @command_success = false + @current_resource.running false + nil + end + end + # Get enabled/disabled state by reading job configuration file + if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") + Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") + ::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}",'r') do |file| + while line = file.gets + case line + when /^start on/ + Chef::Log.debug("#{@new_resource} enabled: #{line.chomp}") + @current_resource.enabled true + break + when /^#start on/ + Chef::Log.debug("#{@new_resource} disabled: #{line.chomp}") + @current_resource.enabled false + break + end + end + end + else + @config_file_found = false + Chef::Log.debug("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") + @current_resource.enabled false + end + + @current_resource + end + + def start_service + # Calling start on a service that is already started will return 1 + # Our 'goal' when we call start is to ensure the service is started + if @current_resource.running + Chef::Log.debug("#{@new_resource} already running, not starting") + else + if @new_resource.start_command + super + else + run_command_with_systems_locale(:command => "/sbin/start #{@job}") + end + end + end + + def stop_service + # Calling stop on a service that is already stopped will return 1 + # Our 'goal' when we call stop is to ensure the service is stopped + unless @current_resource.running + Chef::Log.debug("#{@new_resource} not running, not stopping") + else + if @new_resource.stop_command + super + else + run_command_with_systems_locale(:command => "/sbin/stop #{@job}") + end + end + end + + def restart_service + if @new_resource.restart_command + super + # Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start. + # Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883 + else @new_resource.supports[:restart] + if @current_resource.running + run_command_with_systems_locale(:command => "/sbin/restart #{@job}") + else + start_service + end + end + end + + def reload_service + if @new_resource.reload_command + super + else + # upstart >= 0.6.3-4 supports reload (HUP) + run_command_with_systems_locale(:command => "/sbin/reload #{@job}") + end + end + + # https://bugs.launchpad.net/upstart/+bug/94065 + + def enable_service + Chef::Log.debug("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file") + conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") + conf.search_file_replace(/^#start on/, "start on") + conf.write_file + end + + def disable_service + Chef::Log.debug("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file") + conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") + conf.search_file_replace(/^start on/, "#start on") + conf.write_file + end + + def upstart_state + command = "/sbin/status #{@job}" + status = popen4(command) do |pid, stdin, stdout, stderr| + stdout.each_line do |line| + # rsyslog stop/waiting + # service goal/state + # OR + # rsyslog (stop) waiting + # service (goal) state + line =~ UPSTART_STATE_FORMAT + data = Regexp.last_match + return data[2] + end + end + end + + end + end + end +end diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb new file mode 100644 index 0000000000..ba51e53bed --- /dev/null +++ b/lib/chef/provider/service/windows.rb @@ -0,0 +1,163 @@ +# +# Author:: Nuo Yan <nuo@opscode.com> +# Author:: Bryan McLellan <btm@loftninjas.org> +# Author:: Seth Chisamore <schisamo@opscode.com> +# Copyright:: Copyright (c) 2010-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/mixin/shell_out' +require 'chef/provider/service/simple' +if RUBY_PLATFORM =~ /mswin|mingw32|windows/ + require 'win32/service' +end + +class Chef::Provider::Service::Windows < Chef::Provider::Service + + include Chef::Mixin::ShellOut + + RUNNING = 'running' + STOPPED = 'stopped' + AUTO_START = 'auto start' + DISABLED = 'disabled' + + def whyrun_supported? + false + end + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + @current_resource.running(current_state == RUNNING) + Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}" + @current_resource.enabled(start_type == AUTO_START) + Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}" + @current_resource + end + + def start_service + if Win32::Service.exists?(@new_resource.service_name) + if current_state == RUNNING + Chef::Log.debug "#{@new_resource} already started - nothing to do" + else + if @new_resource.start_command + Chef::Log.debug "#{@new_resource} starting service using the given start_command" + shell_out!(@new_resource.start_command) + else + spawn_command_thread do + Win32::Service.start(@new_resource.service_name) + wait_for_state(RUNNING) + end + end + @new_resource.updated_by_last_action(true) + end + else + Chef::Log.debug "#{@new_resource} does not exist - nothing to do" + end + end + + def stop_service + if Win32::Service.exists?(@new_resource.service_name) + if current_state == RUNNING + if @new_resource.stop_command + Chef::Log.debug "#{@new_resource} stopping service using the given stop_command" + shell_out!(@new_resource.stop_command) + else + spawn_command_thread do + Win32::Service.stop(@new_resource.service_name) + wait_for_state(STOPPED) + end + end + @new_resource.updated_by_last_action(true) + else + Chef::Log.debug "#{@new_resource} already stopped - nothing to do" + end + else + Chef::Log.debug "#{@new_resource} does not exist - nothing to do" + end + end + + def restart_service + if Win32::Service.exists?(@new_resource.service_name) + if @new_resource.restart_command + Chef::Log.debug "#{@new_resource} restarting service using the given restart_command" + shell_out!(@new_resource.restart_command) + else + stop_service + start_service + end + @new_resource.updated_by_last_action(true) + else + Chef::Log.debug "#{@new_resource} does not exist - nothing to do" + end + end + + def enable_service + if Win32::Service.exists?(@new_resource.service_name) + if start_type == AUTO_START + Chef::Log.debug "#{@new_resource} already enabled - nothing to do" + else + Win32::Service.configure( + :service_name => @new_resource.service_name, + :start_type => Win32::Service::AUTO_START + ) + @new_resource.updated_by_last_action(true) + end + else + Chef::Log.debug "#{@new_resource} does not exist - nothing to do" + end + end + + def disable_service + if Win32::Service.exists?(@new_resource.service_name) + if start_type == AUTO_START + Win32::Service.configure( + :service_name => @new_resource.service_name, + :start_type => Win32::Service::DISABLED + ) + @new_resource.updated_by_last_action(true) + else + Chef::Log.debug "#{@new_resource} already disabled - nothing to do" + end + else + Chef::Log.debug "#{@new_resource} does not exist - nothing to do" + end + end + + private + def current_state + Win32::Service.status(@new_resource.service_name).current_state + end + + def start_type + Win32::Service.config_info(@new_resource.service_name).start_type + 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) + sleep 1 until current_state == desired_state + end + + # There ain't no party like a thread party... + def spawn_command_thread + worker = Thread.new do + yield + end + Timeout.timeout(60) do + worker.join + end + end +end |