diff --git a/install.rb b/install.rb index dcff82403..784ec8c9b 100755 --- a/install.rb +++ b/install.rb @@ -1,442 +1,444 @@ #! /usr/bin/env ruby #-- # Copyright 2004 Austin Ziegler # Install utility. Based on the original installation script for rdoc by the # Pragmatic Programmers. # # This program is free software. It may be redistributed and/or modified under # the terms of the GPL version 2 (or later) or the Ruby licence. # # Usage # ----- # In most cases, if you have a typical project layout, you will need to do # absolutely nothing to make this work for you. This layout is: # # bin/ # executable files -- "commands" # lib/ # the source of the library # tests/ # unit tests # # The default behaviour: # 1) Run all unit test files (ending in .rb) found in all directories under # tests/. # 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), # all .rb files in lib/, ./README, ./ChangeLog, and ./Install. # 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), # and all .rb files in lib/. This is disabled by default on Microsoft Windows. # 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a # if a corresponding batch file (.bat or .cmd) exists in the bin directory, # it will be copied over as well. Otherwise, a batch file (always .bat) will # be created to run the specified command. # 5) Install all library files ending in .rb from lib/ into Ruby's # site_lib/version directory. # #++ require 'rbconfig' require 'find' require 'fileutils' begin require 'ftools' # apparently on some system ftools doesn't get loaded $haveftools = true rescue LoadError puts "ftools not found. Using FileUtils instead.." $haveftools = false end require 'optparse' require 'ostruct' begin require 'rdoc/rdoc' $haverdoc = true rescue LoadError puts "Missing rdoc; skipping documentation" $haverdoc = false end PREREQS = %w{openssl facter xmlrpc/client xmlrpc/server cgi} MIN_FACTER_VERSION = 1.5 InstallOptions = OpenStruct.new def glob(list) g = list.map { |i| Dir.glob(i) } g.flatten! g.compact! g.reject! { |e| e =~ /\.svn/ } g end # Set these values to what you want installed. configs = glob(%w{conf/auth.conf}) sbins = glob(%w{sbin/*}) bins = glob(%w{bin/*}) rdoc = glob(%w{bin/* sbin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } ri = glob(%w{bin/*.rb sbin/* lib/**/*.rb}).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man[0-9]/*}) libs = glob(%w{lib/**/*.rb lib/**/*.erb lib/**/*.py lib/puppet/util/command_line/*}) tests = glob(%w{test/**/*.rb}) def do_configs(configs, target, strip = 'conf/') Dir.mkdir(target) unless File.directory? target configs.each do |cf| ocf = File.join(InstallOptions.config_dir, cf.gsub(/#{strip}/, '')) if $haveftools File.install(cf, ocf, 0644, true) else FileUtils.install(cf, ocf, {:mode => 0644, :verbose => true}) end end end def do_bins(bins, target, strip = 's?bin/') Dir.mkdir(target) unless File.directory? target bins.each do |bf| obf = bf.gsub(/#{strip}/, '') install_binfile(bf, obf, target) end end def do_libs(libs, strip = 'lib/') libs.each do |lf| olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) op = File.dirname(olf) if $haveftools File.makedirs(op, true) File.chmod(0755, op) File.install(lf, olf, 0644, true) else FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) end end end def do_man(man, strip = 'man/') man.each do |mf| omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) om = File.dirname(omf) if $haveftools File.makedirs(om, true) File.chmod(0755, om) File.install(mf, omf, 0644, true) else FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, om) FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) end gzip = %x{which gzip} gzip.chomp! %x{#{gzip} -f #{omf}} end end # Verify that all of the prereqs are installed def check_prereqs PREREQS.each { |pre| begin require pre if pre == "facter" # to_f isn't quite exact for strings like "1.5.1" but is good # enough for this purpose. facter_version = Facter.version.to_f if facter_version < MIN_FACTER_VERSION puts "Facter version: #{facter_version}; minimum required: #{MIN_FACTER_VERSION}; cannot install" exit -1 end end rescue LoadError puts "Could not load #{pre}; cannot install" exit -1 end } end ## # Prepare the file installation. # def prepare_installation $operatingsystem = Facter["operatingsystem"].value InstallOptions.configs = true # Only try to do docs if we're sure they have rdoc if $haverdoc InstallOptions.rdoc = true InstallOptions.ri = $operatingsystem != "windows" else InstallOptions.rdoc = false InstallOptions.ri = false end InstallOptions.tests = true ARGV.options do |opts| opts.banner = "Usage: #{File.basename($0)} [options]" opts.separator "" opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| InstallOptions.rdoc = onrdoc end opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| InstallOptions.ri = onri end opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| InstallOptions.tests = ontest end opts.on('--[no-]configs', 'Prevents the installation of config files', 'Default off.') do |ontest| InstallOptions.configs = ontest end opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir end opts.on('--configdir[=OPTIONAL]', 'Installation directory for config files', 'Default /etc/puppet') do |configdir| InstallOptions.configdir = configdir end opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| InstallOptions.sbindir = sbindir end opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir end opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| InstallOptions.mandir = mandir end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false InstallOptions.tests = false InstallOptions.configs = true end opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| InstallOptions.rdoc = true InstallOptions.ri = true InstallOptions.tests = true InstallOptions.configs = true end opts.separator("") opts.on_tail('--help', "Shows this help text.") do $stderr.puts opts exit end opts.parse! end tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") libdir = File.join(Config::CONFIG["libdir"], "ruby", version) # Mac OS X 10.5 and higher declare bindir and sbindir as # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin # which is not generally where people expect executables to be installed # These settings are appropriate defaults for all OS X versions. if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ Config::CONFIG['bindir'] = "/usr/bin" Config::CONFIG['sbindir'] = "/usr/sbin" end if not InstallOptions.configdir.nil? configdir = InstallOptions.configdir else configdir = "/etc/puppet" end if not InstallOptions.bindir.nil? bindir = InstallOptions.bindir else bindir = Config::CONFIG['bindir'] end if not InstallOptions.sbindir.nil? sbindir = InstallOptions.sbindir else sbindir = Config::CONFIG['sbindir'] end if not InstallOptions.sitelibdir.nil? sitelibdir = InstallOptions.sitelibdir else sitelibdir = Config::CONFIG["sitelibdir"] if sitelibdir.nil? sitelibdir = $LOAD_PATH.find { |x| x =~ /site_ruby/ } if sitelibdir.nil? sitelibdir = File.join(libdir, "site_ruby") elsif sitelibdir !~ Regexp.quote(version) sitelibdir = File.join(sitelibdir, version) end end end if not InstallOptions.mandir.nil? mandir = InstallOptions.mandir else mandir = Config::CONFIG['mandir'] end # This is the new way forward if not InstallOptions.destdir.nil? destdir = InstallOptions.destdir # To be deprecated once people move over to using --destdir option elsif ENV['DESTDIR'] != nil? destdir = ENV['DESTDIR'] warn "DESTDIR is deprecated. Use --destdir instead." else destdir = '' end configdir = "#{destdir}#{configdir}" bindir = "#{destdir}#{bindir}" sbindir = "#{destdir}#{sbindir}" mandir = "#{destdir}#{mandir}" sitelibdir = "#{destdir}#{sitelibdir}" FileUtils.makedirs(configdir) if InstallOptions.configs FileUtils.makedirs(bindir) FileUtils.makedirs(sbindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) tmpdirs << bindir InstallOptions.tmp_dirs = tmpdirs.compact InstallOptions.site_dir = sitelibdir InstallOptions.config_dir = configdir InstallOptions.bin_dir = bindir InstallOptions.sbin_dir = sbindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir end ## # Build the rdoc documentation. Also, try to build the RI documentation. # def build_rdoc(files) return unless $haverdoc begin r = RDoc::RDoc.new r.document(["--main", "README", "--title", "Puppet -- Site Configuration Management", "--line-numbers"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" end end def build_ri(files) return unless $haverdoc begin ri = RDoc::RDoc.new #ri.document(["--ri-site", "--merge"] + files) ri.document(["--ri-site"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build Ri documentation\n#{e.message}" $stderr.puts "Continuing with install..." end end def run_tests(test_list) require 'test/unit/ui/console/testrunner' $LOAD_PATH.unshift "lib" test_list.each do |test| next if File.directory?(test) require test end tests = [] ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } tests.delete_if { |o| o == Test::Unit::TestCase } tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } $LOAD_PATH.shift rescue LoadError puts "Missing testrunner library; skipping tests" end ## # Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way # to insert a #! line; on a Unix install, the command is named as expected # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) tmp_dir = nil InstallOptions.tmp_dirs.each do |t| if File.directory?(t) and File.writable?(t) tmp_dir = t break end end fail "Cannot find a temporary directory" unless tmp_dir tmp_file = File.join(tmp_dir, '_tmp') ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) File.open(from) do |ip| File.open(tmp_file, "w") do |op| ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) op.puts "#!#{ruby}" contents = ip.readlines contents.shift if contents[0] =~ /^#!/ op.write contents.join end end if $operatingsystem == "windows" installed_wrapper = false if File.exists?("#{from}.bat") FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) installed_wrapper = true end if File.exists?("#{from}.cmd") FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) installed_wrapper = true end if not installed_wrapper tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') cwn = File.join(Config::CONFIG['bindir'], op_file) - cwv = CMD_WRAPPER.gsub('', ruby.gsub(%r{/}) { "\\" }).gsub!('', cwn.gsub(%r{/}) { "\\" } ) + regex_safe_ruby = Regexp.escape(ruby.gsub(%r{/}) { "\\" }) + regex_safe_cwn = Regexp.escape(cwn.gsub(%r{/}) { "\\" }) + cwv = CMD_WRAPPER.gsub('', regex_safe_ruby).gsub('', regex_safe_cwn) File.open(tmp_file2, "wb") { |cw| cw.puts cwv } FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) File.unlink(tmp_file2) installed_wrapper = true end end FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) File.unlink(tmp_file) end CMD_WRAPPER = <<-EOS @echo off if "%OS%"=="Windows_NT" goto WinNT -x "" %1 %2 %3 %4 %5 %6 %7 %8 %9 goto done :WinNT -x "" %* goto done :done EOS check_prereqs prepare_installation #run_tests(tests) if InstallOptions.tests #build_rdoc(rdoc) if InstallOptions.rdoc #build_ri(ri) if InstallOptions.ri do_configs(configs, InstallOptions.config_dir) if InstallOptions.configs do_bins(sbins, InstallOptions.sbin_dir) do_bins(bins, InstallOptions.bin_dir) do_libs(libs) do_man(man) unless $operatingsystem == "windows" diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb index 9ed3deef5..b4b1313f8 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -1,62 +1,63 @@ # Created by Luke Kanies on 2006-04-30. # Copyright (c) 2006. All rights reserved. require 'puppet/util/feature' # Add the simple features, all in one file. # We've got LDAP available. Puppet.features.add(:ldap, :libs => ["ldap"]) # We have the Rdoc::Usage library. Puppet.features.add(:usage, :libs => %w{rdoc/ri/ri_paths rdoc/usage}) # We have libshadow, useful for managing passwords. Puppet.features.add(:libshadow, :libs => ["shadow"]) # We're running as root. Puppet.features.add(:root) { require 'puppet/util/suidmanager'; Puppet::Util::SUIDManager.root? } # We've got mongrel available Puppet.features.add(:mongrel, :libs => %w{rubygems mongrel puppet/network/http_server/mongrel}) # We have lcs diff Puppet.features.add :diff, :libs => %w{diff/lcs diff/lcs/hunk} # We have augeas Puppet.features.add(:augeas, :libs => ["augeas"]) # We have RRD available Puppet.features.add(:rrd_legacy, :libs => ["RRDtool"]) Puppet.features.add(:rrd, :libs => ["RRD"]) # We have OpenSSL Puppet.features.add(:openssl, :libs => ["openssl"]) # We have a syslog implementation Puppet.features.add(:syslog, :libs => ["syslog"]) # We can use POSIX user functions Puppet.features.add(:posix) do require 'etc' Etc.getpwuid(0) != nil && Puppet.features.syslog? end # We can use Microsoft Windows functions Puppet.features.add(:microsoft_windows) do begin 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 raise Puppet::Error,"Cannot determine basic system flavour" unless Puppet.features.posix? or Puppet.features.microsoft_windows? # We have CouchDB Puppet.features.add(:couchdb, :libs => ["couchrest"]) # We have sqlite Puppet.features.add(:sqlite, :libs => ["sqlite3"]) 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 5a2c69b87..53ff72867 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -1,201 +1,211 @@ # This is our main way of managing processes right now. # # a service is distinct from a process in that services # can only be managed through the interface of an init script # which is why they have a search path for initscripts and such module Puppet newtype(:service) do @doc = "Manage running services. Service support unfortunately varies widely by platform --- some platforms have very little if any concept of a running service, and some have a very codified and powerful concept. Puppet's service support will generally be able to do the right thing regardless (e.g., if there is no 'status' command, then Puppet will look in the process table for a command matching the service name), but the more information you can provide, the better behaviour you will get. In particular, any virtual services that don't have a predictable entry in the process table (for example, `network` on Red Hat/CentOS systems) will manifest odd behavior on restarts if you don't specify `hasstatus` or a `status` command. Note that if a `service` receives an event from another resource, the service will get restarted. The actual command to restart the service depends on the platform. You can provide an explicit command for restarting with the `restart` attribute, or use the init script's restart command with the `hasrestart` attribute; if you do neither, the service's stop and start commands will be used." feature :refreshable, "The provider can restart the service.", :methods => [:restart] feature :enableable, "The provider can enable and disable the service", :methods => [:disable, :enable, :enabled?] feature :controllable, "The provider uses a control variable." newproperty(:enable, :required_features => :enableable) do desc "Whether a service should be enabled to start at boot. This property behaves quite differently depending on the platform; wherever possible, it relies on local tools to enable or disable a given service." newvalue(:true, :event => :service_enabled) do provider.enable end newvalue(:false, :event => :service_disabled) do 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. newproperty(:ensure) do desc "Whether a service should be running." newvalue(:stopped, :event => :service_stopped) do provider.stop end newvalue(:running, :event => :service_started) do provider.start end aliasvalue(:false, :stopped) aliasvalue(:true, :running) def retrieve provider.status end def sync event = super() if property = @resource.property(:enable) val = property.retrieve property.sync unless property.safe_insync?(val) end event end end newparam(:binary) do desc "The path to the daemon. This is only used for systems that do not support init scripts. This binary will be used to start the service if no `start` parameter is provided." end newparam(:hasstatus) do desc "Declare the the service's init script has a functional status command. Based on testing, it was found that a large number of init scripts on different platforms do not support any kind of status command; thus, you must specify manually whether the service you are running has such a command. Alternately, you can provide a specific command using the `status` attribute. If you specify neither of these, then Puppet will look for the service name in the process table. Be aware that 'virtual' init scripts such as networking will respond poorly to refresh events (via notify and subscribe relationships) if you don't override this default behavior." newvalues(:true, :false) defaultto :true end newparam(:name) do desc "The name of the service to run. This name is used to find the service in whatever service subsystem it is in." isnamevar end newparam(:path) do desc "The search path for finding init scripts. Multiple values should be separated by colons or provided as an array." munge do |value| value = [value] unless value.is_a?(Array) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] # It affects stand-alone blocks, too. paths = value.flatten.collect { |p| x = p.split(":") }.flatten end defaultto { provider.class.defpath if provider.class.respond_to?(:defpath) } end newparam(:pattern) do desc "The pattern to search for in the process table. This is used for stopping services on platforms that do not support init scripts, and is also used for determining service status on those service whose init scripts do not include a status command. If this is left unspecified and is needed to check the status of a service, then the service name will be used instead. The pattern can be a simple string or any legal Ruby pattern." defaultto { @resource[:binary] || @resource[:name] } end newparam(:restart) do desc "Specify a *restart* command manually. If left unspecified, the service will be stopped and then started." end newparam(:start) do desc "Specify a *start* command manually. Most service subsystems support a `start` command, so this will not need to be specified." end newparam(:status) do desc "Specify a *status* command manually. This command must return 0 if the service is running and a nonzero value otherwise. Ideally, these return codes should conform to [the LSB's specification for init script status actions](http://refspecs.freestandards.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html), but puppet only considers the difference between 0 and nonzero to be relevant. If left unspecified, the status method will be determined automatically, usually by looking for the service in the process table." end newparam(:stop) do desc "Specify a *stop* command manually." end newparam(:control) do desc "The control variable used to manage services (originally for HP-UX). Defaults to the upcased service name plus `START` replacing dots with underscores, for those providers that support the `controllable` feature." defaultto { resource.name.gsub(".","_").upcase + "_START" if resource.provider.controllable? } end newparam :hasrestart do desc "Specify that an init script has a `restart` option. Otherwise, the init script's `stop` and `start` methods are used." newvalues(:true, :false) end newparam(:manifest) do desc "Specify a command to config a service, or a path to a manifest to do so." end # Basically just a synonym for restarting. Used to respond # to events. def refresh # Only restart if we're actually running if (@parameters[:ensure] || newattr(:ensure)).retrieve == :running provider.restart else debug "Skipping restart; service is not running" end end end end 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 @@ -1,233 +1,248 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:service) do it "should have an :enableable feature that requires the :enable, :disable, and :enabled? methods" do Puppet::Type.type(:service).provider_feature(:enableable).methods.should == [:disable, :enable, :enabled?] end it "should have a :refreshable feature that requires the :restart method" do Puppet::Type.type(:service).provider_feature(:refreshable).methods.should == [:restart] end end describe Puppet::Type.type(:service), "when validating attributes" do [:name, :binary, :hasstatus, :path, :pattern, :start, :restart, :stop, :status, :hasrestart, :control].each do |param| it "should have a #{param} parameter" do Puppet::Type.type(:service).attrtype(param).should == :param end end [:ensure, :enable].each do |param| it "should have an #{param} property" do Puppet::Type.type(:service).attrtype(param).should == :property end end end describe Puppet::Type.type(:service), "when validating attribute values" do before do @provider = stub 'provider', :class => Puppet::Type.type(:service).defaultprovider, :clear => nil, :controllable? => false Puppet::Type.type(:service).defaultprovider.stubs(:new).returns(@provider) end it "should support :running as a value to :ensure" do Puppet::Type.type(:service).new(:name => "yay", :ensure => :running) end it "should support :stopped as a value to :ensure" do Puppet::Type.type(:service).new(:name => "yay", :ensure => :stopped) end it "should alias the value :true to :running in :ensure" do svc = Puppet::Type.type(:service).new(:name => "yay", :ensure => true) svc.should(:ensure).should == :running end it "should alias the value :false to :stopped in :ensure" do svc = Puppet::Type.type(:service).new(:name => "yay", :ensure => false) svc.should(:ensure).should == :stopped end it "should support :true as a value to :enable" do Puppet::Type.type(:service).new(:name => "yay", :enable => :true) end it "should support :false as a value to :enable" 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 it "should support :false as a value to :hasstatus" do Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :false) end it "should specify :true as the default value of hasstatus" do Puppet::Type.type(:service).new(:name => "yay")[:hasstatus].should == :true end it "should support :true as a value to :hasrestart" do Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :true) end it "should support :false as a value to :hasrestart" do Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :false) end it "should allow setting the :enable parameter if the provider has the :enableable feature" do Puppet::Type.type(:service).defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type.type(:service).defaultprovider.expects(:supports_parameter?).with(Puppet::Type.type(:service).attrclass(:enable)).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :enable => true) svc.should(:enable).should == :true end it "should not allow setting the :enable parameter if the provider is missing the :enableable feature" do Puppet::Type.type(:service).defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type.type(:service).defaultprovider.expects(:supports_parameter?).with(Puppet::Type.type(:service).attrclass(:enable)).returns(false) svc = Puppet::Type.type(:service).new(:name => "yay", :enable => true) svc.should(:enable).should be_nil end it "should split paths on ':'" do FileTest.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => "/one/two:/three/four") svc[:path].should == %w{/one/two /three/four} end it "should accept arrays of paths joined by ':'" do FileTest.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => ["/one:/two", "/three:/four"]) svc[:path].should == %w{/one /two /three /four} end end describe Puppet::Type.type(:service), "when setting default attribute values" do it "should default to the provider's default path if one is available" do FileTest.stubs(:directory?).returns(true) FileTest.stubs(:exist?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:respond_to?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:defpath).returns("testing") svc = Puppet::Type.type(:service).new(:name => "other") svc[:path].should == ["testing"] end it "should default 'pattern' to the binary if one is provided" do svc = Puppet::Type.type(:service).new(:name => "other", :binary => "/some/binary") svc[:pattern].should == "/some/binary" end it "should default 'pattern' to the name if no pattern is provided" do svc = Puppet::Type.type(:service).new(:name => "other") svc[:pattern].should == "other" end it "should default 'control' to the upcased service name with periods replaced by underscores if the provider supports the 'controllable' feature" do provider = stub 'provider', :controllable? => true, :class => Puppet::Type.type(:service).defaultprovider, :clear => nil Puppet::Type.type(:service).defaultprovider.stubs(:new).returns(provider) svc = Puppet::Type.type(:service).new(:name => "nfs.client") svc[:control].should == "NFS_CLIENT_START" end end describe Puppet::Type.type(:service), "when retrieving the host's current state" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should use the provider's status to determine whether the service is running" do @service.provider.expects(:status).returns(:yepper) @service[:ensure] = :running @service.property(:ensure).retrieve.should == :yepper end it "should ask the provider whether it is enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service.provider.expects(:enabled?).returns(:yepper) @service[:enable] = true @service.property(:enable).retrieve.should == :yepper end end describe Puppet::Type.type(:service), "when changing the host" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should start the service if it is supposed to be running" do @service[:ensure] = :running @service.provider.expects(:start) @service.property(:ensure).sync end it "should stop the service if it is supposed to be stopped" do @service[:ensure] = :stopped @service.provider.expects(:stop) @service.property(:ensure).sync end it "should enable the service if it is supposed to be enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = true @service.provider.expects(:enable) @service.property(:enable).sync end it "should disable the service if it is supposed to be disabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service.provider.expects(:disable) @service.property(:enable).sync end it "should sync the service's enable state when changing the state of :ensure if :enable is being managed" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service[:ensure] = :stopped @service.property(:enable).expects(:retrieve).returns("whatever") @service.property(:enable).expects(:insync?).returns(false) @service.property(:enable).expects(:sync) @service.provider.stubs(:stop) @service.property(:ensure).sync end end describe Puppet::Type.type(:service), "when refreshing the service" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should restart the service if it is running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should restart the service if it is running, even if it is supposed to stopped" do @service[:ensure] = :stopped @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should not restart the service if it is not running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:stopped) @service.refresh end it "should add :ensure as a property if it is not being managed" do @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end end