summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/feature/base.rb3
-rw-r--r--lib/puppet/provider/service/windows.rb101
-rw-r--r--lib/puppet/type/service.rb10
-rwxr-xr-xspec/unit/provider/service/windows_spec.rb130
-rwxr-xr-xspec/unit/type/service_spec.rb15
5 files changed, 258 insertions, 1 deletions
diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb
index 5f3f0023f..56782b3b6 100644
--- a/lib/puppet/feature/base.rb
+++ b/lib/puppet/feature/base.rb
@@ -45,8 +45,9 @@ Puppet.features.add(:microsoft_windows) do
require 'sys/admin'
require 'win32/process'
require 'win32/dir'
+ require 'win32/service'
rescue LoadError => err
- warn "Cannot run on Microsoft Windows without the sys-admin, win32-process & win32-dir gems: #{err}" unless Puppet.features.posix?
+ warn "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir & win32-service gems: #{err}" unless Puppet.features.posix?
end
end
diff --git a/lib/puppet/provider/service/windows.rb b/lib/puppet/provider/service/windows.rb
new file mode 100644
index 000000000..09754ffda
--- /dev/null
+++ b/lib/puppet/provider/service/windows.rb
@@ -0,0 +1,101 @@
+# Windows Service Control Manager (SCM) provider
+
+require 'win32/service' if Puppet.features.microsoft_windows?
+
+Puppet::Type.type(:service).provide :windows do
+
+ desc "Support for Windows Service Control Manager (SCM).
+
+ Services are controlled according to win32-service gem.
+
+ * All SCM operations (start/stop/enable/disable/query) are supported.
+
+ * Control of service groups (dependencies) is not yet supported."
+
+ defaultfor :operatingsystem => :windows
+ confine :operatingsystem => :windows
+
+ has_feature :refreshable
+
+ def enable
+ w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START )
+ raise Puppet::Error.new("Win32 service enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot enable #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def disable
+ w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DISABLED )
+ raise Puppet::Error.new("Win32 service disable of #{@resource[:name]} failed" ) if( w32ss.nil? )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot disable #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def manual_start
+ w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START )
+ raise Puppet::Error.new("Win32 service manual enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot enable #{@resource[:name]} for manual start, error was: #{detail}" )
+ end
+
+ def enabled?
+ w32ss = Win32::Service.config_info( @resource[:name] )
+ raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceConfigInfo ) )
+ Puppet.debug("Service #{@resource[:name]} start type is #{w32ss.start_type}")
+ case w32ss.start_type
+ when Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START),
+ Win32::Service.get_start_type(Win32::Service::SERVICE_BOOT_START),
+ Win32::Service.get_start_type(Win32::Service::SERVICE_SYSTEM_START)
+ true
+ when Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START)
+ :manual
+ when Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED)
+ false
+ else
+ raise Puppet::Error.new("Unknown start type: #{w32ss.start_type}")
+ end
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot get start type for #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def start
+ Win32::Service.start( @resource[:name] )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def stop
+ Win32::Service.stop( @resource[:name] )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def restart
+ self.stop
+ self.start
+ end
+
+ def status
+ w32ss = Win32::Service.status( @resource[:name] )
+ raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceStatus ) )
+ state = case w32ss.current_state
+ when "stopped", "pause pending", "stop pending", "paused" then :stopped
+ when "running", "continue pending", "start pending" then :running
+ else
+ raise Puppet::Error.new("Unknown service state '#{w32ss.current_state}' for service '#{@resource[:name]}'")
+ end
+ Puppet.debug("Service #{@resource[:name]} is #{w32ss.current_state}")
+ return state
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ # returns all providers for all existing services and startup state
+ def self.instances
+ srvcs = []
+ Win32::Service.services.collect{ |s|
+ srvcs << new(:name => s.service_name)
+ }
+ srvcs
+ end
+end
diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb
index 3116f5f8e..eaf2b8ee1 100644
--- a/lib/puppet/type/service.rb
+++ b/lib/puppet/type/service.rb
@@ -47,9 +47,19 @@ module Puppet
provider.disable
end
+ newvalue(:manual, :event => :service_manual_start) do
+ provider.manual_start
+ end
+
def retrieve
provider.enabled?
end
+
+ validate do |value|
+ if value == :manual and !Puppet.features.microsoft_windows?
+ raise Puppet::Error.new("Setting enable to manual is only supported on Microsoft Windows.")
+ end
+ end
end
# Handle whether the service should actually be running right now.
diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb
new file mode 100755
index 000000000..be2f33c20
--- /dev/null
+++ b/spec/unit/provider/service/windows_spec.rb
@@ -0,0 +1,130 @@
+#!/usr/bin/env rspec
+#
+# Unit testing for the Windows service Provider
+#
+
+require 'spec_helper'
+
+require 'ostruct'
+require 'win32/service' if Puppet.features.microsoft_windows?
+
+provider_class = Puppet::Type.type(:service).provider(:windows)
+
+describe provider_class, :if => Puppet.features.microsoft_windows? do
+
+ before :each do
+ @provider = Puppet::Type.type(:service).provider(:windows)
+ Puppet::Type.type(:service).stubs(:provider).returns(@provider)
+ end
+
+ describe ".instances" do
+ it "should enumerate all services" do
+ list_of_services = ['snmptrap', 'svchost', 'sshd'].map {|s| OpenStruct.new(:service_name => s)}
+ Win32::Service.expects(:services).returns(list_of_services)
+
+ provider_class.instances.map {|provider| provider.name}.should =~ ['snmptrap', 'svchost', 'sshd']
+ end
+ end
+
+ describe "#start" do
+ it "should call out to the Win32::Service API to start the service" do
+ Win32::Service.expects(:start).with('snmptrap')
+
+ resource = Puppet::Type.type(:service).new(:name => 'snmptrap')
+ resource.provider.start
+ end
+
+ it "should handle when Win32::Service.start raises a Win32::Service::Error" do
+ Win32::Service.expects(:start).with('snmptrap').raises(
+ Win32::Service::Error.new("The service cannot be started, either because it is disabled or because it has no enabled devices associated with it.")
+ )
+
+ resource = Puppet::Type.type(:service).new(:name => 'snmptrap')
+ expect { resource.provider.start }.to raise_error(
+ Puppet::Error,
+ /Cannot start snmptrap, error was: The service cannot be started, either/
+ )
+ end
+ end
+
+ describe "#stop" do
+ it "should stop a running service"
+ it "should not try to stop an already stopped service"
+ end
+
+ describe "#status" do
+ ['stopped', 'paused', 'stop pending', 'pause pending'].each do |state|
+ it "should report a #{state} service as stopped" do
+ Win32::Service.expects(:status).with('snmptrap').returns(
+ stub(
+ 'struct_service_status',
+ :instance_of? => Struct::ServiceStatus,
+ :current_state => state
+ )
+ )
+ resource = Puppet::Type.type(:service).new(:name => 'snmptrap')
+
+ resource.provider.status.should == :stopped
+ end
+ end
+
+ ["running", "continue pending", "start pending" ].each do |state|
+ it "should report a #{state} service as running" do
+ Win32::Service.expects(:status).with('snmptrap').returns(
+ stub(
+ 'struct_service_status',
+ :instance_of? => Struct::ServiceStatus,
+ :current_state => state
+ )
+ )
+ resource = Puppet::Type.type(:service).new(:name => 'snmptrap')
+ resource.provider.status.should == :running
+ end
+ end
+ end
+
+ describe "#enabled?" do
+ it "should report a service with a startup type of manual as manual" do
+ Win32::Service.expects(:config_info).with('snmptrap').returns(
+ stub(
+ 'struct_config_info',
+ :instance_of? => Struct::ServiceConfigInfo,
+ :start_type => Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START)
+ )
+ )
+ resource = Puppet::Type.type(:service).new(:name => 'snmptrap')
+ resource.provider.enabled?.should == :manual
+ end
+
+ it "should report a service with a startup type of disabled as false" do
+ Win32::Service.expects(:config_info).with('snmptrap').returns(
+ stub(
+ 'struct_config_info',
+ :instance_of? => Struct::ServiceConfigInfo,
+ :start_type => Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED)
+ )
+ )
+ resource = Puppet::Type.type(:service).new(:name => 'snmptrap')
+ resource.provider.enabled?.should == false
+ end
+
+ # We need to guard this section explicitly since rspec will always
+ # construct all examples, even if it isn't going to run them.
+ if Puppet.features.microsoft_windows?
+ [Win32::Service::SERVICE_AUTO_START, Win32::Service::SERVICE_BOOT_START, Win32::Service::SERVICE_SYSTEM_START].each do |start_type_const|
+ start_type = Win32::Service.get_start_type(start_type_const)
+ it "should report a service with a startup type of '#{start_type}' as true" do
+ Win32::Service.expects(:config_info).with('snmptrap').returns(
+ stub(
+ 'struct_config_info',
+ :instance_of? => Struct::ServiceConfigInfo,
+ :start_type => start_type
+ )
+ )
+ resource = Puppet::Type.type(:service).new(:name => 'snmptrap')
+ resource.provider.enabled?.should == true
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/type/service_spec.rb b/spec/unit/type/service_spec.rb
index 40270e7c8..ab006a4be 100755
--- a/spec/unit/type/service_spec.rb
+++ b/spec/unit/type/service_spec.rb
@@ -57,6 +57,21 @@ describe Puppet::Type.type(:service), "when validating attribute values" do
Puppet::Type.type(:service).new(:name => "yay", :enable => :false)
end
+ it "should support :manual as a value to :enable on Windows" do
+ Puppet.features.stubs(:microsoft_windows?).returns true
+
+ Puppet::Type.type(:service).new(:name => "yay", :enable => :manual)
+ end
+
+ it "should not support :manual as a value to :enable when not on Windows" do
+ Puppet.features.stubs(:microsoft_windows?).returns false
+
+ expect { Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) }.to raise_error(
+ Puppet::Error,
+ /Setting enable to manual is only supported on Microsoft Windows\./
+ )
+ end
+
it "should support :true as a value to :hasstatus" do
Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :true)
end