diff options
| -rw-r--r-- | lib/puppet/feature/base.rb | 3 | ||||
| -rw-r--r-- | lib/puppet/provider/service/windows.rb | 101 | ||||
| -rw-r--r-- | lib/puppet/type/service.rb | 10 | ||||
| -rwxr-xr-x | spec/unit/provider/service/windows_spec.rb | 130 | ||||
| -rwxr-xr-x | spec/unit/type/service_spec.rb | 15 |
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 |
