diff --git a/lib/puppet/provider/computer/computer.rb b/lib/puppet/provider/computer/computer.rb index 76d0f1883..f3e526105 100644 --- a/lib/puppet/provider/computer/computer.rb +++ b/lib/puppet/provider/computer/computer.rb @@ -1,22 +1,22 @@ require 'puppet/provider/nameservice/directoryservice' Puppet::Type.type(:computer).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do desc "Computer object management using DirectoryService on OS X. - Note that these are distinctly different kinds of objects to 'hosts', as they require a MAC address and can have all sorts of policy attached to them. This provider only manages Computer objects in the local directory service domain, not in remote directories. If you wish to manage /etc/hosts on Mac OS X, then simply use the host type as per other platforms. + " confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin # hurray for abstraction. The nameservice directoryservice provider can # handle everything we need. super. -end \ No newline at end of file +end diff --git a/lib/puppet/provider/group/directoryservice.rb b/lib/puppet/provider/group/directoryservice.rb index 2f393052b..fd89698ce 100644 --- a/lib/puppet/provider/group/directoryservice.rb +++ b/lib/puppet/provider/group/directoryservice.rb @@ -1,24 +1,26 @@ # Created by Jeff McCune on 2007-07-22 # Copyright (c) 2007. All rights reserved. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation (version 2 of the License) # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA require 'puppet/provider/nameservice/directoryservice' Puppet::Type.type(:group).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do - desc "Group management using DirectoryService on OS X." + desc "Group management using DirectoryService on OS X. + + " commands :dscl => "/usr/bin/dscl" confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin has_feature :manages_members end diff --git a/lib/puppet/provider/group/groupadd.rb b/lib/puppet/provider/group/groupadd.rb index 371beee19..ccd631a75 100644 --- a/lib/puppet/provider/group/groupadd.rb +++ b/lib/puppet/provider/group/groupadd.rb @@ -1,28 +1,31 @@ require 'puppet/provider/nameservice/objectadd' Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameService::ObjectAdd do - desc "Group management via ``groupadd`` and its ilk. The default - for most platforms" + desc "Group management via ``groupadd`` and its ilk. + + The default for most platforms + + " commands :add => "groupadd", :delete => "groupdel", :modify => "groupmod" verify :gid, "GID must be an integer" do |value| value.is_a? Integer end def addcmd cmd = [command(:add)] if gid = @resource.should(:gid) unless gid == :absent cmd << flag(:gid) << gid end end if @resource.allowdupe? cmd << "-o" end cmd << @resource[:name] return cmd end end diff --git a/lib/puppet/provider/group/ldap.rb b/lib/puppet/provider/group/ldap.rb index 37a7e7343..5a41065f0 100644 --- a/lib/puppet/provider/group/ldap.rb +++ b/lib/puppet/provider/group/ldap.rb @@ -1,48 +1,50 @@ require 'puppet/provider/ldap' Puppet::Type.type(:group).provide :ldap, :parent => Puppet::Provider::Ldap do - desc "Group management via ``ldap``. This provider requires that you - have valid values for all of the ldap-related settings, - including ``ldapbase``. You will also almost definitely need settings - for ``ldapuser`` and ``ldappassword``, so that your clients can write - to ldap. + desc "Group management via ``ldap``. + + This provider requires that you have valid values for all of the + ldap-related settings, including ``ldapbase``. You will also almost + definitely need settings for ``ldapuser`` and ``ldappassword``, so that + your clients can write to ldap. - Note that this provider will automatically generate a GID for you if - you do not specify one, but it is a potentially expensive operation, - as it iterates across all existing groups to pick the appropriate next - one." + Note that this provider will automatically generate a GID for you if you do + not specify one, but it is a potentially expensive operation, as it + iterates across all existing groups to pick the appropriate next one. + + " confine :true => Puppet.features.ldap?, :false => (Puppet[:ldapuser] == "") # We're mapping 'members' here because we want to make it # easy for the ldap user provider to manage groups. This # way it can just use the 'update' method in the group manager, # whereas otherwise it would need to replicate that code. manages(:posixGroup).at("ou=Groups").and.maps :name => :cn, :gid => :gidNumber, :members => :memberUid # Find the next gid after the current largest gid. provider = self manager.generates(:gidNumber).with do largest = 500 if existing = provider.manager.search existing.each do |hash| next unless value = hash[:gid] num = value[0].to_i if num > largest largest = num end end end largest + 1 end # Convert a group name to an id. def self.name2id(group) return nil unless result = manager.search("cn=%s" % group) and result.length > 0 # Only use the first result. group = result[0] gid = group[:gid][0] return gid end end diff --git a/lib/puppet/provider/group/netinfo.rb b/lib/puppet/provider/group/netinfo.rb index 7c3539eae..4b8fc5f9e 100644 --- a/lib/puppet/provider/group/netinfo.rb +++ b/lib/puppet/provider/group/netinfo.rb @@ -1,13 +1,15 @@ # Manage NetInfo POSIX objects. # # This provider has been deprecated. You should be using the directoryservice # nameservice provider instead. require 'puppet/provider/nameservice/netinfo' Puppet::Type.type(:group).provide :netinfo, :parent => Puppet::Provider::NameService::NetInfo do - desc "Group management using NetInfo." + desc "Group management using NetInfo. + + " commands :nireport => "nireport", :niutil => "niutil" end diff --git a/lib/puppet/provider/group/pw.rb b/lib/puppet/provider/group/pw.rb index 3c384cd6d..4c7ba795e 100644 --- a/lib/puppet/provider/group/pw.rb +++ b/lib/puppet/provider/group/pw.rb @@ -1,30 +1,34 @@ require 'puppet/provider/nameservice/pw' Puppet::Type.type(:group).provide :pw, :parent => Puppet::Provider::NameService::PW do - desc "Group management via ``pw``. Only works on FreeBSD." + desc "Group management via ``pw``. + + Only works on FreeBSD. + + " commands :pw => "/usr/sbin/pw" defaultfor :operatingsystem => :freebsd verify :gid, "GID must be an integer" do |value| value.is_a? Integer end def addcmd cmd = [command(:pw), "groupadd", @resource[:name]] if gid = @resource.should(:gid) unless gid == :absent cmd << flag(:gid) << gid end end # Apparently, contrary to the man page, groupadd does # not accept -o. #if @parent[:allowdupe] == :true # cmd << "-o" #end return cmd end end diff --git a/lib/puppet/provider/host/netinfo.rb b/lib/puppet/provider/host/netinfo.rb index 07a9362c5..55294958c 100644 --- a/lib/puppet/provider/host/netinfo.rb +++ b/lib/puppet/provider/host/netinfo.rb @@ -1,16 +1,19 @@ # Manage NetInfo POSIX objects. Probably only used on OS X, but I suppose # it could be used elsewhere. require 'puppet/provider/nameservice/netinfo' Puppet::Type.type(:host).provide :netinfo, :parent => Puppet::Provider::NameService::NetInfo, :netinfodir => "machines" do - desc "Host management in NetInfo. This provider is highly experimental and is known - not to work currently." + desc "Host management in NetInfo. + + This provider is highly experimental and is known not to work currently. + + " commands :nireport => "nireport", :niutil => "niutil" commands :mountcmd => "mount", :umount => "umount", :df => "df" options :ip, :key => "ip_address" defaultfor :operatingsystem => :darwin end diff --git a/lib/puppet/provider/macauthorization/macauthorization.rb b/lib/puppet/provider/macauthorization/macauthorization.rb index 2cdef6c12..fce158e2a 100644 --- a/lib/puppet/provider/macauthorization/macauthorization.rb +++ b/lib/puppet/provider/macauthorization/macauthorization.rb @@ -1,313 +1,315 @@ require 'facter' require 'facter/util/plist' require 'puppet' require 'tempfile' Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppet::Provider do - desc "Manage Mac OS X authorization database rules and rights." + desc "Manage Mac OS X authorization database rules and rights. + + " commands :security => "/usr/bin/security" commands :sw_vers => "/usr/bin/sw_vers" confine :operatingsystem => :darwin # This should be confined based on macosx_productversion once # http://projects.reductivelabs.com/issues/show/1796 # is resolved. if FileTest.exists?("/usr/bin/sw_vers") product_version = sw_vers "-productVersion" confine :true => if /^10.5/.match(product_version) or /^10.6/.match(product_version) true end end defaultfor :operatingsystem => :darwin AuthDB = "/etc/authorization" @rights = {} @rules = {} @parsed_auth_db = {} @comment = "" # Not implemented yet. Is there any real need to? # This map exists due to the use of hyphens and reserved words in # the authorization schema. PuppetToNativeAttributeMap = { :allow_root => "allow-root", :authenticate_user => "authenticate-user", :auth_class => "class", :k_of_n => "k-of-n", :session_owner => "session-owner", } class << self attr_accessor :parsed_auth_db attr_accessor :rights attr_accessor :rules attr_accessor :comments # Not implemented yet. def prefetch(resources) self.populate_rules_rights end def instances if self.parsed_auth_db == {} self.prefetch(nil) end self.parsed_auth_db.collect do |k,v| new(:name => k) end end def populate_rules_rights auth_plist = Plist::parse_xml(AuthDB) if not auth_plist raise Puppet::Error.new("Cannot parse: #{AuthDB}") end self.rights = auth_plist["rights"].dup self.rules = auth_plist["rules"].dup self.parsed_auth_db = self.rights.dup self.parsed_auth_db.merge!(self.rules.dup) end end # standard required provider instance methods def initialize(resource) if self.class.parsed_auth_db == {} self.class.prefetch(resource) end super end def create # we just fill the @property_hash in here and let the flush method # deal with it rather than repeating code. new_values = {} validprops = Puppet::Type.type(resource.class.name).validproperties validprops.each do |prop| next if prop == :ensure if value = resource.should(prop) and value != "" new_values[prop] = value end end @property_hash = new_values.dup end def destroy # We explicitly delete here rather than in the flush method. case resource[:auth_type] when :right destroy_right when :rule destroy_rule else raise Puppet::Error.new("Must specify auth_type when destroying.") end end def exists? if self.class.parsed_auth_db.has_key?(resource[:name]) return true else return false end end def flush # deletion happens in the destroy methods if resource[:ensure] != :absent case resource[:auth_type] when :right flush_right when :rule flush_rule else raise Puppet::Error.new("flush requested for unknown type.") end @property_hash.clear end end # utility methods below def destroy_right security "authorizationdb", :remove, resource[:name] end def destroy_rule authdb = Plist::parse_xml(AuthDB) authdb_rules = authdb["rules"].dup if authdb_rules[resource[:name]] begin authdb["rules"].delete(resource[:name]) Plist::Emit.save_plist(authdb, AuthDB) rescue Errno::EACCES => e raise Puppet::Error.new("Error saving #{AuthDB}: #{e}") end end end def flush_right # first we re-read the right just to make sure we're in sync for # values that weren't specified in the manifest. As we're supplying # the whole plist when specifying the right it seems safest to be # paranoid given the low cost of quering the db once more. cmds = [] cmds << :security << "authorizationdb" << "read" << resource[:name] output = execute(cmds, :combine => false) current_values = Plist::parse_xml(output) if current_values.nil? current_values = {} end specified_values = convert_plist_to_native_attributes(@property_hash) # take the current values, merge the specified values to obtain a # complete description of the new values. new_values = current_values.merge(specified_values) set_right(resource[:name], new_values) end def flush_rule authdb = Plist::parse_xml(AuthDB) authdb_rules = authdb["rules"].dup current_values = {} if authdb_rules[resource[:name]] current_values = authdb_rules[resource[:name]] end specified_values = convert_plist_to_native_attributes(@property_hash) new_values = current_values.merge(specified_values) set_rule(resource[:name], new_values) end def set_right(name, values) # Both creates and modifies rights as it simply overwrites them. # The security binary only allows for writes using stdin, so we # dump the values to a tempfile. values = convert_plist_to_native_attributes(values) tmp = Tempfile.new('puppet_macauthorization') begin Plist::Emit.save_plist(values, tmp.path) cmds = [] cmds << :security << "authorizationdb" << "write" << name output = execute(cmds, :combine => false, :stdinfile => tmp.path.to_s) rescue Errno::EACCES => e raise Puppet::Error.new("Cannot save right to #{tmp.path}: #{e}") ensure tmp.close tmp.unlink end end def set_rule(name, values) # Both creates and modifies rules as it overwrites the entry in the # rules dictionary. Unfortunately the security binary doesn't # support modifying rules at all so we have to twiddle the whole # plist... :( See Apple Bug #6386000 values = convert_plist_to_native_attributes(values) authdb = Plist::parse_xml(AuthDB) authdb["rules"][name] = values begin Plist::Emit.save_plist(authdb, AuthDB) rescue raise Puppet::Error.new("Error writing to: #{AuthDB}") end end def convert_plist_to_native_attributes(propertylist) # This mainly converts the keys from the puppet attributes to the # 'native' ones, but also enforces that the keys are all Strings # rather than Symbols so that any merges of the resultant Hash are # sane. newplist = {} propertylist.each_pair do |key, value| next if key == :ensure # not part of the auth db schema. next if key == :auth_type # not part of the auth db schema. new_key = key if PuppetToNativeAttributeMap.has_key?(key) new_key = PuppetToNativeAttributeMap[key].to_s elsif not key.is_a?(String) new_key = key.to_s end newplist[new_key] = value end newplist end def retrieve_value(resource_name, attribute) if not self.class.parsed_auth_db.has_key?(resource_name) raise Puppet::Error.new("Cannot find #{resource_name} in auth db") end if PuppetToNativeAttributeMap.has_key?(attribute) native_attribute = PuppetToNativeAttributeMap[attribute] else native_attribute = attribute.to_s end if self.class.parsed_auth_db[resource_name].has_key?(native_attribute) value = self.class.parsed_auth_db[resource_name][native_attribute] case value when true, "true", :true value = :true when false, "false", :false value = :false end @property_hash[attribute] = value return value else @property_hash.delete(attribute) return "" # so ralsh doesn't display it. end end # property methods below # # We define them all dynamically apart from auth_type which is a special # case due to not being in the actual authorization db schema. properties = [ :allow_root, :authenticate_user, :auth_class, :comment, :group, :k_of_n, :mechanisms, :rule, :session_owner, :shared, :timeout, :tries ] properties.each do |field| define_method(field.to_s) do retrieve_value(resource[:name], field) end define_method(field.to_s + "=") do |value| @property_hash[field] = value end end def auth_type if resource.should(:auth_type) != nil return resource.should(:auth_type) elsif self.exists? # this is here just for ralsh, so it can work out what type it is. if self.class.rights.has_key?(resource[:name]) return :right elsif self.class.rules.has_key?(resource[:name]) return :rule else raise Puppet::Error.new("#{resource[:name]} is unknown type.") end else raise Puppet::Error.new("auth_type required for new resources.") end end def auth_type=(value) @property_hash[:auth_type] = value end -end \ No newline at end of file +end diff --git a/lib/puppet/provider/mcx/mcxcontent.rb b/lib/puppet/provider/mcx/mcxcontent.rb index 27c583ed1..1fea60ca0 100644 --- a/lib/puppet/provider/mcx/mcxcontent.rb +++ b/lib/puppet/provider/mcx/mcxcontent.rb @@ -1,199 +1,201 @@ #-- # Copyright (C) 2008 Jeffrey J McCune. # This program and entire repository is free software; you can # redistribute it and/or modify it under the terms of the GNU # General Public License as published by the Free Software # Foundation; either version 2 of the License, or any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Author: Jeff McCune require 'tempfile' Puppet::Type.type(:mcx).provide :mcxcontent, :parent => Puppet::Provider do desc "MCX Settings management using DirectoryService on OS X. -This provider manages the entire MCXSettings attribute available -to some directory services nodes. This management is 'all or nothing' -in that discrete application domain key value pairs are not managed -by this provider. + This provider manages the entire MCXSettings attribute available + to some directory services nodes. This management is 'all or nothing' + in that discrete application domain key value pairs are not managed + by this provider. -It is recommended to use WorkGroup Manager to configure Users, Groups, -Computers, or ComputerLists, then use 'ralsh mcx' to generate a puppet -manifest from the resulting configuration. + It is recommended to use WorkGroup Manager to configure Users, Groups, + Computers, or ComputerLists, then use 'ralsh mcx' to generate a puppet + manifest from the resulting configuration. -Original Author: Jeff McCune (mccune.jeff@gmail.com)" + Original Author: Jeff McCune (mccune.jeff@gmail.com) + +" # This provides a mapping of puppet types to DirectoryService # type strings. TypeMap = { :user => "Users", :group => "Groups", :computer => "Computers", :computerlist => "ComputerLists", } class MCXContentProviderException < Exception end commands :dscl => "/usr/bin/dscl" confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin # self.instances is all important. # This is the only class method, it returns # an array of instances of this class. def self.instances mcx_list = [] for ds_type in TypeMap.keys ds_path = "/Local/Default/#{TypeMap[ds_type]}" output = dscl 'localhost', '-list', ds_path member_list = output.split for ds_name in member_list content = mcxexport(ds_type, ds_name) if content.empty? Puppet.debug "/#{TypeMap[ds_type]}/#{ds_name} has no MCX data." else # This node has MCX data. rsrc = self.new(:name => "/#{TypeMap[ds_type]}/#{ds_name}", :ds_type => ds_type, :ds_name => ds_name, :content => content) mcx_list << rsrc end end end return mcx_list end private # mcxexport is used by instances, and therefore # a class method. def self.mcxexport(ds_type, ds_name) ds_t = TypeMap[ds_type] ds_n = ds_name.to_s ds_path = "/Local/Default/#{ds_t}/#{ds_n}" dscl 'localhost', '-mcxexport', ds_path end def mcximport(ds_type, ds_name, val) ds_t = TypeMap[ds_type] ds_n = ds_name.to_s ds_path = "/Local/Default/#{ds_t}/#{ds_name}" tmp = Tempfile.new('puppet_mcx') begin tmp << val tmp.flush dscl 'localhost', '-mcximport', ds_path, tmp.path ensure tmp.close tmp.unlink end end # Given the resource name string, parse ds_type out. def parse_type(name) tmp = name.split('/')[1] if ! tmp.is_a? String raise MCXContentProviderException, "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." end # De-pluralize and downcase. tmp = tmp.chop.downcase.to_sym if not TypeMap.keys.member? tmp raise MCXContentProviderException, "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." end return tmp end # Given the resource name string, parse ds_name out. def parse_name(name) ds_name = name.split('/')[2] if ! ds_name.is_a? String raise MCXContentProviderException, "Could not parse ds_name from resource name '#{name}'. Specify with ds_name parameter." end return ds_name end # Gather ds_type and ds_name from resource or # parse it out of the name. # This is a private instance method, not a class method. def get_dsparams ds_type = resource[:ds_type] if ds_type.nil? ds_type = parse_type(resource[:name]) end raise MCXContentProviderException unless TypeMap.keys.include? ds_type.to_sym ds_name = resource[:ds_name] if ds_name.nil? ds_name = parse_name(resource[:name]) end rval = { :ds_type => ds_type.to_sym, :ds_name => ds_name, } return rval end public def create self.content=(resource[:content]) end def destroy ds_parms = get_dsparams ds_t = TypeMap[ds_parms[:ds_type]] ds_n = ds_parms[:ds_name].to_s ds_path = "/Local/Default/#{ds_t}/#{ds_n}" dscl 'localhost', '-mcxdelete', ds_path end def exists? # JJM Just re-use the content method and see if it's empty. begin mcx = content rescue Puppet::ExecutionFailure => e return false end has_mcx = ! mcx.empty? return has_mcx end def content ds_parms = get_dsparams mcx = self.class.mcxexport(ds_parms[:ds_type], ds_parms[:ds_name]) return mcx end def content=(value) # dscl localhost -mcximport ds_parms = get_dsparams mcx = mcximport(ds_parms[:ds_type], ds_parms[:ds_name], resource[:content]) return mcx end end diff --git a/lib/puppet/provider/service/base.rb b/lib/puppet/provider/service/base.rb index 8964322b6..2f456886a 100755 --- a/lib/puppet/provider/service/base.rb +++ b/lib/puppet/provider/service/base.rb @@ -1,137 +1,140 @@ Puppet::Type.type(:service).provide :base do - desc "The simplest form of service support. You have to specify - enough about your service for this to work; the minimum you can specify - is a binary for starting the process, and this same binary will be - searched for in the process table to stop the service. It is - preferable to specify start, stop, and status commands, akin to how you - would do so using ``init``." + desc "The simplest form of service support. + + You have to specify enough about your service for this to work; the + minimum you can specify is a binary for starting the process, and this + same binary will be searched for in the process table to stop the + service. It is preferable to specify start, stop, and status commands, + akin to how you would do so using ``init``. + + " commands :kill => "kill" def self.instances [] end # Get the process ID for a running process. Requires the 'pattern' # parameter. def getpid unless @resource[:pattern] @resource.fail "Either a stop command or a pattern must be specified" end ps = Facter["ps"].value unless ps and ps != "" @resource.fail( "You must upgrade Facter to a version that includes 'ps'" ) end regex = Regexp.new(@resource[:pattern]) self.debug "Executing '#{ps}'" IO.popen(ps) { |table| table.each { |line| if regex.match(line) ary = line.sub(/^\s+/, '').split(/\s+/) return ary[1] end } } return nil end # How to restart the process. def restart if @resource[:restart] or self.respond_to?(:restartcmd) ucommand(:restart) else self.stop self.start end end # Check if the process is running. Prefer the 'status' parameter, # then 'statuscmd' method, then look in the process table. We give # the object the option to not return a status command, which might # happen if, for instance, it has an init script (and thus responds to # 'statuscmd') but does not have 'hasstatus' enabled. def status if @resource[:status] or ( self.respond_to?(:statuscmd) and self.statuscmd ) # Don't fail when the exit status is not 0. output = ucommand(:status, false) if $? == 0 return :running else return :stopped end elsif pid = self.getpid self.debug "PID is %s" % pid return :running else return :stopped end end # Run the 'start' parameter command, or the specified 'startcmd'. def start ucommand(:start) end # The command used to start. Generated if the 'binary' argument # is passed. def startcmd if @resource[:binary] return @resource[:binary] else raise Puppet::Error, "Services must specify a start command or a binary" end end # Stop the service. If a 'stop' parameter is specified, it # takes precedence; otherwise checks if the object responds to # a 'stopcmd' method, and if so runs that; otherwise, looks # for the process in the process table. # This method will generally not be overridden by submodules. def stop if @resource[:stop] or self.respond_to?(:stopcmd) ucommand(:stop) else pid = getpid unless pid self.info "%s is not running" % self.name return false end begin output = kill pid rescue Puppet::ExecutionFailure => detail @resource.fail "Could not kill %s, PID %s: %s" % [self.name, pid, output] end return true end end # A simple wrapper so execution failures are a bit more informative. def texecute(type, command, fof = true) begin # #565: Services generally produce no output, so squelch them. execute(command, :failonfail => fof, :squelch => true) rescue Puppet::ExecutionFailure => detail @resource.fail "Could not %s %s: %s" % [type, @resource.ref, detail] end return nil end # Use either a specified command or the default for our provider. def ucommand(type, fof = true) if c = @resource[type] cmd = [c] else cmd = self.send("%scmd" % type) end return texecute(type, cmd, fof) end end diff --git a/lib/puppet/provider/service/daemontools.rb b/lib/puppet/provider/service/daemontools.rb index 46729e1b1..e8d116a67 100644 --- a/lib/puppet/provider/service/daemontools.rb +++ b/lib/puppet/provider/service/daemontools.rb @@ -1,164 +1,164 @@ # Daemontools service management # # author Brice Figureau Puppet::Type.type(:service).provide :daemontools, :parent => :base do - desc """ -Daemontools service management. -This provider manages daemons running supervised by D.J.Bernstein daemontools. -It tries to detect the service directory, with by order of preference:: + desc """Daemontools service management. - * /service - * /etc/service - * /var/lib/svscan + This provider manages daemons running supervised by D.J.Bernstein daemontools. + It tries to detect the service directory, with by order of preference: -The daemon directory should be placed in a directory that can be -by default in:: + * /service + * /etc/service + * /var/lib/svscan - * /var/lib/service - * /etc + The daemon directory should be placed in a directory that can be + by default in: -or this can be overriden in the service resource parameters:: + * /var/lib/service + * /etc - service { - \"myservice\": - provider => \"daemontools\", path => \"/path/to/daemons\"; - } + or this can be overriden in the service resource parameters:: -This provider supports out of the box:: + service { + \"myservice\": + provider => \"daemontools\", path => \"/path/to/daemons\"; + } - * start/stop (mapped to enable/disable) - * enable/disable - * restart - * status + This provider supports out of the box: + * start/stop (mapped to enable/disable) + * enable/disable + * restart + * status -""" + + """ commands :svc => "/usr/bin/svc" commands :svstat => "/usr/bin/svstat" class << self attr_writer :defpath # this is necessary to autodetect a valid resource # default path, since there is no standard for such directory. def defpath unless defined?(@defpath) and @defpath ["/var/lib/service", "/etc"].each do |path| if FileTest.exist?(path) @defpath = path break end end raise "Could not find the daemon directory (tested [/var/lib/service,/etc])" unless @defpath end @defpath end end attr_writer :servicedir # returns all providers for all existing services in @defpath # ie enabled or not def self.instances path = self.defpath unless FileTest.directory?(path) Puppet.notice "Service path %s does not exist" % path next end # reject entries that aren't either a directory # or don't contain a run file Dir.entries(path).reject { |e| fullpath = File.join(path, e) e =~ /^\./ or ! FileTest.directory?(fullpath) or ! FileTest.exist?(File.join(fullpath,"run")) }.collect do |name| new(:name => name, :path => path) end end # returns the daemon dir on this node def self.daemondir self.defpath end # find the service dir on this node def servicedir unless defined?(@servicedir) and @servicedir ["/service", "/etc/service","/var/lib/svscan"].each do |path| if FileTest.exist?(path) @servicedir = path break end end raise "Could not find service directory" unless @servicedir end @servicedir end # returns the full path of this service when enabled # (ie in the service directory) def service File.join(self.servicedir, resource[:name]) end # returns the full path to the current daemon directory # note that this path can be overriden in the resource # definition def daemon File.join(resource[:path], resource[:name]) end def restartcmd [ command(:svc), "-t", self.service] end # The start command does nothing, service are automatically started # when enabled by svscan. But, forces an enable if necessary def start # to start make sure the sevice is enabled self.enable # start is then automatic end def status begin output = svstat self.service return :running if output =~ /\bup\b/ rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "Could not get status for service %s: %s" % [ resource.ref, detail] ) end return :stopped end # unfortunately it is not possible # to stop without disabling the service def stop self.disable end # disable by stopping the service # and removing the symlink so that svscan # doesn't restart our service behind our back def disable # should stop the service # stop the log subservice if any log = File.join(self.service, "log") texecute("stop log", [ command(:svc) , '-dx', log] ) if FileTest.directory?(log) # stop the main resource texecute("stop", [command(:svc), '-dx', self.service] ) # unlink the daemon symlink to disable it File.unlink(self.service) if FileTest.symlink?(self.service) end def enabled? FileTest.symlink?(self.service) end def enable File.symlink(self.daemon, self.service) if ! FileTest.symlink?(self.service) end end diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb index ca433cbe6..48d509871 100755 --- a/lib/puppet/provider/service/debian.rb +++ b/lib/puppet/provider/service/debian.rb @@ -1,36 +1,40 @@ # Manage debian services. Start/stop is the same as InitSvc, but enable/disable # is special. Puppet::Type.type(:service).provide :debian, :parent => :init do - desc "Debian's form of ``init``-style management. The only difference - is that this supports service enabling and disabling via ``update-rc.d``." + desc "Debian's form of ``init``-style management. + + The only difference is that this supports service enabling and disabling + via ``update-rc.d``. + + " commands :update => "/usr/sbin/update-rc.d" defaultfor :operatingsystem => [:debian, :ubuntu] def self.defpath superclass.defpath end # Remove the symlinks def disable update "-f", @resource[:name], "remove" update @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "." end def enabled? output = update "-n", "-f", @resource[:name], "remove" # If it's enabled, then it will print output showing removal of # links. if output =~ /etc\/rc[\dS].d\/S|not installed/ return :true else return :false end end def enable update "-f", @resource[:name], "remove" update @resource[:name], "defaults" end end diff --git a/lib/puppet/provider/service/freebsd.rb b/lib/puppet/provider/service/freebsd.rb index 95bde7784..356feeee4 100644 --- a/lib/puppet/provider/service/freebsd.rb +++ b/lib/puppet/provider/service/freebsd.rb @@ -1,53 +1,56 @@ # Manage FreeBSD services. Puppet::Type.type(:service).provide :freebsd, :parent => :init do - desc "FreeBSD's (and probably NetBSD?) form of ``init``-style service - management; uses ``rc.conf.d`` for service enabling and disabling." + desc "FreeBSD's (and probably NetBSD?) form of ``init``-style service management. + + Uses ``rc.conf.d`` for service enabling and disabling. + +" confine :operatingsystem => [:freebsd, :netbsd, :openbsd] defaultfor :operatingsystem => :freebsd @@rcconf_dir = '/etc/rc.conf.d' def self.defpath superclass.defpath end # remove service file from rc.conf.d to disable it def disable rcfile = File.join(@@rcconf_dir, @model[:name]) if File.exists?(rcfile) File.delete(rcfile) end end # if the service file exists in rc.conf.d then it's already enabled def enabled? rcfile = File.join(@@rcconf_dir, @model[:name]) if File.exists?(rcfile) return :true end return :false end # enable service by creating a service file under rc.conf.d with the # proper contents def enable if not File.exists?(@@rcconf_dir) Dir.mkdir(@@rcconf_dir) end rcfile = File.join(@@rcconf_dir, @model[:name]) open(rcfile, 'w') { |f| f << "%s_enable=\"YES\"\n" % @model[:name] } end # Override stop/start commands to use one's and the avoid race condition # where provider trys to stop/start the service before it is enabled def startcmd [self.initscript, :onestart] end def stopcmd [self.initscript, :onestop] end end diff --git a/lib/puppet/provider/service/gentoo.rb b/lib/puppet/provider/service/gentoo.rb index d84aaf6a8..4067dee5e 100644 --- a/lib/puppet/provider/service/gentoo.rb +++ b/lib/puppet/provider/service/gentoo.rb @@ -1,55 +1,58 @@ # Manage gentoo services. Start/stop is the same as InitSvc, but enable/disable # is special. Puppet::Type.type(:service).provide :gentoo, :parent => :init do - desc "Gentoo's form of ``init``-style service - management; uses ``rc-update`` for service enabling and disabling." + desc "Gentoo's form of ``init``-style service management. + + Uses ``rc-update`` for service enabling and disabling. + + " commands :update => "/sbin/rc-update" confine :operatingsystem => :gentoo defaultfor :operatingsystem => :gentoo def self.defpath superclass.defpath end def disable begin output = update :del, @resource[:name], :default rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable %s: %s" % [self.name, output] end end def enabled? begin output = update :show rescue Puppet::ExecutionFailure return :false end line = output.split(/\n/).find { |l| l.include?(@resource[:name]) } return :false unless line # If it's enabled then it will print output showing service | runlevel if output =~ /#{@resource[:name]}\s*\|\s*default/ return :true else return :false end end def enable begin output = update :add, @resource[:name], :default rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not enable %s: %s" % [self.name, output] end end end # $Id $ diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index 46fa2216e..cbc7afdc4 100755 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -1,133 +1,137 @@ # The standard init-based service type. Many other service types are # customizations of this module. Puppet::Type.type(:service).provide :init, :parent => :base do - desc "Standard init service management. This provider assumes that the - init script has no ``status`` command, because so few scripts do, - so you need to either provide a status command or specify via - ``hasstatus`` that one already exists in the init script." + desc "Standard init service management. + + This provider assumes that the init script has no ``status`` command, + because so few scripts do, so you need to either provide a status + command or specify via ``hasstatus`` that one already exists in the + init script. + +" class << self attr_accessor :defpath end case Facter["operatingsystem"].value when "FreeBSD": @defpath = ["/etc/rc.d", "/usr/local/etc/rc.d"] when "HP-UX": @defpath = "/sbin/init.d" else @defpath = "/etc/init.d" end # We can't confine this here, because the init path can be overridden. #confine :exists => @defpath # List all services of this type. def self.instances self.defpath = [self.defpath] unless self.defpath.is_a? Array instances = [] self.defpath.each do |path| unless FileTest.directory?(path) Puppet.debug "Service path %s does not exist" % path next end check = [:ensure] if public_method_defined? :enabled? check << :enable end Dir.entries(path).each do |name| fullpath = File.join(path, name) next if name =~ /^\./ next if not FileTest.executable?(fullpath) instances << new(:name => name, :path => path) end end instances end # Mark that our init script supports 'status' commands. def hasstatus=(value) case value when true, "true": @parameters[:hasstatus] = true when false, "false": @parameters[:hasstatus] = false else raise Puppet::Error, "Invalid 'hasstatus' value %s" % value.inspect end end # Where is our init script? def initscript if defined? @initscript return @initscript else @initscript = self.search(@resource[:name]) end end def restart if @resource[:hasrestart] == :true command = [self.initscript, :restart] texecute("restart", command) else super end end def search(name) @resource[:path].each { |path| fqname = File.join(path,name) begin stat = File.stat(fqname) rescue # should probably rescue specific errors... self.debug("Could not find %s in %s" % [name,path]) next end # if we've gotten this far, we found a valid script return fqname } @resource[:path].each { |path| fqname_sh = File.join(path,"#{name}.sh") begin stat = File.stat(fqname_sh) rescue # should probably rescue specific errors... self.debug("Could not find %s.sh in %s" % [name,path]) next end # if we've gotten this far, we found a valid script return fqname_sh } raise Puppet::Error, "Could not find init script for '%s'" % name end # The start command is just the init scriptwith 'start'. def startcmd [self.initscript, :start] end # If it was specified that the init script has a 'status' command, then # we just return that; otherwise, we return false, which causes it to # fallback to other mechanisms. def statuscmd if @resource[:hasstatus] == :true return [self.initscript, :status] else return false end end # The stop command is just the init script with 'stop'. def stopcmd [self.initscript, :stop] end end diff --git a/lib/puppet/provider/service/launchd.rb b/lib/puppet/provider/service/launchd.rb index 11d7bd2b4..891c96bd8 100644 --- a/lib/puppet/provider/service/launchd.rb +++ b/lib/puppet/provider/service/launchd.rb @@ -1,194 +1,195 @@ require 'facter/util/plist' Puppet::Type.type(:service).provide :launchd, :parent => :base do desc "launchd service management framework. This provider manages launchd jobs, the default service framework for Mac OS X, that has also been open sourced by Apple for possible use on other platforms. See: * http://developer.apple.com/macosx/launchd.html * http://launchd.macosforge.org/ This provider reads plists out of the following directories: * /System/Library/LaunchDaemons * /System/Library/LaunchAgents * /Library/LaunchDaemons * /Library/LaunchAgents and builds up a list of services based upon each plists \"Label\" entry. This provider supports: * ensure => running/stopped, * enable => true/false * status * restart Here is how the Puppet states correspond to launchd states: * stopped => job unloaded * started => job loaded * enabled => 'Disable' removed from job plist file * disabled => 'Disable' added to job plist file - + Note that this allows you to do something launchctl can't do, which is to be in a state of \"stopped/enabled\ or \"running/disabled\". - " + + " commands :launchctl => "/bin/launchctl" defaultfor :operatingsystem => :darwin confine :operatingsystem => :darwin has_feature :enableable Launchd_Paths = ["/Library/LaunchAgents", "/Library/LaunchDaemons", "/System/Library/LaunchAgents", "/System/Library/LaunchDaemons",] # returns a label => path map for either all jobs, or just a single # job if the label is specified def self.jobsearch(label=nil) label_to_path_map = {} Launchd_Paths.each do |path| if FileTest.exists?(path) Dir.entries(path).each do |f| next if f =~ /^\..*$/ next if FileTest.directory?(f) fullpath = File.join(path, f) job = Plist::parse_xml(fullpath) if job and job.has_key?("Label") if job["Label"] == label return { label => fullpath } else label_to_path_map[job["Label"]] = fullpath end end end end end # if we didn't find the job above and we should have, error. if label raise Puppet::Error.new("Unable to find launchd plist for job: #{label}") end # if returning all jobs label_to_path_map end def self.instances jobs = self.jobsearch jobs.keys.collect do |job| new(:name => job, :provider => :launchd, :path => jobs[job]) end end # finds the path for a given label and returns the path and parsed plist # as an array of [path, plist]. Note plist is really a Hash here. def plist_from_label(label) job = self.class.jobsearch(label) job_path = job[label] job_plist = Plist::parse_xml(job_path) if not job_plist raise Puppet::Error.new("Unable to parse launchd plist at path: #{job_path}") end [job_path, job_plist] end def status # launchctl list exits zero if the job is loaded # and non-zero if it isn't. Simple way to check... begin launchctl :list, resource[:name] return :running rescue Puppet::ExecutionFailure return :stopped end end # start the service. To get to a state of running/enabled, we need to # conditionally enable at load, then disable by modifying the plist file # directly. def start job_path, job_plist = plist_from_label(resource[:name]) did_enable_job = false cmds = [] cmds << :launchctl << :load if self.enabled? == :false # launchctl won't load disabled jobs cmds << "-w" did_enable_job = true end cmds << job_path begin execute(cmds) rescue Puppet::ExecutionFailure raise Puppet::Error.new("Unable to start service: %s at path: %s" % [resource[:name], job_path]) end # As load -w clears the Disabled flag, we need to add it in after if did_enable_job and resource[:enable] == :false self.disable end end def stop job_path, job_plist = plist_from_label(resource[:name]) did_disable_job = false cmds = [] cmds << :launchctl << :unload if self.enabled? == :true # keepalive jobs can't be stopped without disabling cmds << "-w" did_disable_job = true end cmds << job_path begin execute(cmds) rescue Puppet::ExecutionFailure raise Puppet::Error.new("Unable to stop service: %s at path: %s" % [resource[:name], job_path]) end # As unload -w sets the Disabled flag, we need to add it in after if did_disable_job and resource[:enable] == :true self.enable end end # launchd jobs are enabled by default. They are only disabled if the key # "Disabled" is set to true, but it can also be set to false to enable it. def enabled? job_path, job_plist = plist_from_label(resource[:name]) if job_plist.has_key?("Disabled") if job_plist["Disabled"] # inverse of disabled is enabled return :false end end return :true end # enable and disable are a bit hacky. We write out the plist with the appropriate value # rather than dealing with launchctl as it is unable to change the Disabled flag # without actually loading/unloading the job. def enable job_path, job_plist = plist_from_label(resource[:name]) if self.enabled? == :false job_plist.delete("Disabled") Plist::Emit.save_plist(job_plist, job_path) end end def disable job_path, job_plist = plist_from_label(resource[:name]) job_plist["Disabled"] = true Plist::Emit.save_plist(job_plist, job_path) end end diff --git a/lib/puppet/provider/service/redhat.rb b/lib/puppet/provider/service/redhat.rb index faa75476d..c6c3540f5 100755 --- a/lib/puppet/provider/service/redhat.rb +++ b/lib/puppet/provider/service/redhat.rb @@ -1,82 +1,85 @@ # Manage Red Hat services. Start/stop uses /sbin/service and enable/disable uses chkconfig Puppet::Type.type(:service).provide :redhat, :parent => :init do - desc "Red Hat's (and probably many others) form of ``init``-style service - management; uses ``chkconfig`` for service enabling and disabling." + desc "Red Hat's (and probably many others) form of ``init``-style service management: + + Uses ``chkconfig`` for service enabling and disabling. + + " commands :chkconfig => "/sbin/chkconfig", :service => "/sbin/service" defaultfor :operatingsystem => [:redhat, :fedora, :suse, :centos, :sles] def self.defpath superclass.defpath end # Remove the symlinks def disable begin output = chkconfig(@resource[:name], :off) rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable %s: %s" % [self.name, output] end end def enabled? begin output = chkconfig(@resource[:name]) rescue Puppet::ExecutionFailure return :false end # If it's disabled on SuSE, then it will print output showing "off" # at the end if output =~ /.* off$/ return :false end return :true end # Don't support them specifying runlevels; always use the runlevels # in the init scripts. def enable begin output = chkconfig(@resource[:name], :on) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not enable %s: %s" % [self.name, detail] end end def restart if @resource[:hasrestart] == :true service(@resource[:name], "restart") else super end end def status if @resource[:hasstatus] == :true begin service(@resource[:name], "status") return :running rescue return :stopped end else super end end def start service(@resource[:name], "start") end def stop service(@resource[:name], "stop") end end diff --git a/lib/puppet/provider/service/runit.rb b/lib/puppet/provider/service/runit.rb index e8a0da18f..1bd954207 100644 --- a/lib/puppet/provider/service/runit.rb +++ b/lib/puppet/provider/service/runit.rb @@ -1,103 +1,103 @@ # Daemontools service management # # author Brice Figureau Puppet::Type.type(:service).provide :runit, :parent => :daemontools do - desc """ -Runit service management. -This provider manages daemons running supervised by Runit. -It tries to detect the service directory, with by order of preference:: + desc """Runit service management. - * /service - * /var/service - * /etc/service + This provider manages daemons running supervised by Runit. + It tries to detect the service directory, with by order of preference: -The daemon directory should be placed in a directory that can be -by default in:: + * /service + * /var/service + * /etc/service - * /etc/sv + The daemon directory should be placed in a directory that can be + by default in: -or this can be overriden in the service resource parameters:: + * /etc/sv - service { - \"myservice\": - provider => \"runit\", path => \"/path/to/daemons\"; - } + or this can be overriden in the service resource parameters:: -This provider supports out of the box:: + service { + \"myservice\": + provider => \"runit\", path => \"/path/to/daemons\"; + } - * start/stop - * enable/disable - * restart - * status + This provider supports out of the box: + + * start/stop + * enable/disable + * restart + * status """ commands :sv => "/usr/bin/sv" class << self # this is necessary to autodetect a valid resource # default path, since there is no standard for such directory. def defpath unless defined?(@defpath) and @defpath ["/etc/sv", "/var/lib/service"].each do |path| if FileTest.exist?(path) @defpath = path break end end raise "Could not find the daemon directory (tested [/var/lib/service,/etc])" unless @defpath end @defpath end end # find the service dir on this node def servicedir unless defined?(@servicedir) and @servicedir ["/service", "/etc/service","/var/service"].each do |path| if FileTest.exist?(path) @servicedir = path break end end raise "Could not find service directory" unless @servicedir end @servicedir end def restartcmd [ command(:sv), "restart", self.service] end def status begin output = sv "status", self.daemon return :running if output =~ /^run: / rescue Puppet::ExecutionFailure => detail unless detail.message =~ /(warning: |runsv not running$)/ raise Puppet::Error.new( "Could not get status for service %s: %s" % [ resource.ref, detail] ) end end return :stopped end # relay to the stopcmd def stop ucommand( :stop ) end def stopcmd [ command(:sv), "stop", self.service] end # disable by removing the symlink so that runit # doesn't restart our service behind our back # note that runit doesn't need to perform a stop # before a disable def disable # unlink the daemon symlink to disable it File.unlink(self.service) if FileTest.symlink?(self.service) end end diff --git a/lib/puppet/provider/service/smf.rb b/lib/puppet/provider/service/smf.rb index ab1fe88c2..4010e7457 100755 --- a/lib/puppet/provider/service/smf.rb +++ b/lib/puppet/provider/service/smf.rb @@ -1,86 +1,89 @@ # Solaris 10 SMF-style services. Puppet::Type.type(:service).provide :smf, :parent => :base do - desc "Support for Sun's new Service Management Framework. Starting a service - is effectively equivalent to enabling it, so there is only support - for starting and stopping services, which also enables and disables them, - respectively." + desc "Support for Sun's new Service Management Framework. + + Starting a service is effectively equivalent to enabling it, so there is + only support for starting and stopping services, which also enables and + disables them, respectively. + + " defaultfor :operatingsystem => :solaris confine :operatingsystem => :solaris commands :adm => "/usr/sbin/svcadm", :svcs => "/usr/bin/svcs" def enable self.start end def enabled? case self.status when :running: return :true else return :false end end def disable self.stop end def restartcmd [command(:adm), :restart, @resource[:name]] end def startcmd [command(:adm), :enable, @resource[:name]] end def status if @resource[:status] super return end begin output = svcs "-l", @resource[:name] rescue Puppet::ExecutionFailure warning "Could not get status on service %s" % self.name return :stopped end output.split("\n").each { |line| var = nil value = nil if line =~ /^(\w+)\s+(.+)/ var = $1 value = $2 else Puppet.err "Could not match %s" % line.inspect next end case var when "state": case value when "online": #self.warning "matched running %s" % line.inspect return :running when "offline", "disabled", "uninitialized" #self.warning "matched stopped %s" % line.inspect return :stopped when "legacy_run": raise Puppet::Error, "Cannot manage legacy services through SMF" else raise Puppet::Error, "Unmanageable state '%s' on service %s" % [value, self.name] end end } end def stopcmd [command(:adm), :disable, @resource[:name]] end end diff --git a/lib/puppet/type/augeas.rb b/lib/puppet/type/augeas.rb index 058ea2fd9..67b62e886 100644 --- a/lib/puppet/type/augeas.rb +++ b/lib/puppet/type/augeas.rb @@ -1,150 +1,155 @@ #-- # Copyright (C) 2008 Red Hat Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Author: Bryan Kearney Puppet::Type.newtype(:augeas) do include Puppet::Util feature :parse_commands, "Parse the command string" feature :need_to_run?, "If the command should run" feature :execute_changes, "Actually make the changes" @doc = "Apply the changes (single or array of changes) to the filesystem via the augeas tool. Requires: - augeas to be installed (http://www.augeas.net) - ruby-augeas bindings - Sample usage with a string: + Sample usage with a string:: + augeas{\"test1\" : context => \"/files/etc/sysconfig/firstboot\", changes => \"set RUN_FIRSTBOOT YES\", onlyif => \"match other_value size > 0\", } - Sample usage with an array and custom lenses: + Sample usage with an array and custom lenses:: + augeas{\"jboss_conf\": context => \"/files\", changes => [ \"set /etc/jbossas/jbossas.conf/JBOSS_IP $ipaddress\", \"set /etc/jbossas/jbossas.conf/JAVA_HOME /usr\" ], load_path => \"$/usr/share/jbossas/lenses\", } + " newparam (:name) do desc "The name of this task. Used for uniqueness" isnamevar end newparam (:context) do desc "Optional context path. This value is pre-pended to the paths of all changes" defaultto "" end newparam (:onlyif) do desc "Optional augeas command and comparisons to control the execution of this type. - Supported onlyif syntax: + Supported onlyif syntax:: + get [AUGEAS_PATH] [COMPARATOR] [STRING] match [MATCH_PATH] size [COMPARATOR] [INT] match [MATCH_PATH] include [STRING] match [MATCH_PATH] == [AN_ARRAY] - where + where:: + AUGEAS_PATH is a valid path scoped by the context MATCH_PATH is a valid match synatx scoped by the context COMPARATOR is in the set [> >= != == <= <] STRING is a string INT is a number - AN_ARRAY is in the form ['a string', 'another'] " + AN_ARRAY is in the form ['a string', 'another']" defaultto "" end newparam(:changes) do desc "The changes which should be applied to the filesystem. This can be either a string which contains a command or an array of commands. - Commands supported are: - - set [PATH] [VALUE] Sets the value VALUE at loction PATH - rm [PATH] Removes the node at location PATH - remove [PATH] Synonym for rm - clear [PATH] Keeps the node at PATH, but removes the value. - ins [PATH] Inserts an empty node at PATH. - insert [PATH] Synonym for ins + Commands supported are:: + + set [PATH] [VALUE] Sets the value VALUE at loction PATH + rm [PATH] Removes the node at location PATH + remove [PATH] Synonym for rm + clear [PATH] Keeps the node at PATH, but removes the value. + ins [PATH] Inserts an empty node at PATH. + insert [PATH] Synonym for ins If the parameter 'context' is set that that value is prepended to PATH" munge do |value| provider.parse_commands(value) end end newparam(:root) do desc "A file system path; all files loaded by Augeas are loaded underneath ROOT" defaultto "/" end newparam(:load_path) do desc "Optional colon separated list of directories; these directories are searched for schema definitions" defaultto "" end newparam(:type_check) do desc "Set to true if augeas should perform typechecking. Optional, defaults to false" newvalues(:true, :false) defaultto :false end # This is the acutal meat of the code. It forces # augeas to be run and fails or not based on the augeas return # code. newproperty(:returns) do |property| include Puppet::Util desc "The expected return code from the augeas command. Should not be set" defaultto 0 # Make output a bit prettier def change_to_s(currentvalue, newvalue) return "executed successfully" end # if the onlyif resource is provided, then the value is parsed. # a return value of 0 will stop exection becuase it matches the # default value. def retrieve if @resource.provider.need_to_run?() :need_to_run else 0 end end # Actually execute the command. def sync @resource.provider.execute_changes() end end end diff --git a/lib/puppet/type/computer.rb b/lib/puppet/type/computer.rb index ccbcadf72..0888325a8 100644 --- a/lib/puppet/type/computer.rb +++ b/lib/puppet/type/computer.rb @@ -1,61 +1,62 @@ Puppet::Type.newtype(:computer) do - @doc = "Computer object management using DirectoryService on OS X. - - Note that these are distinctly different kinds of objects to 'hosts', - as they require a MAC address and can have all sorts of policy attached to - them. - - This provider only manages Computer objects in the local directory service - domain, not in remote directories. - - If you wish to manage /etc/hosts on Mac OS X, then simply use the host - type as per other platforms. - - This type primarily exists to create localhost Computer objects that MCX - policy can then be attached to." + @doc = "Computer object management using DirectoryService + on OS X. + + Note that these are distinctly different kinds of objects to 'hosts', + as they require a MAC address and can have all sorts of policy attached to + them. + + This provider only manages Computer objects in the local directory service + domain, not in remote directories. + + If you wish to manage /etc/hosts on Mac OS X, then simply use the host + type as per other platforms. + + This type primarily exists to create localhost Computer objects that MCX + policy can then be attached to." # ensurable # We autorequire the computer object in case it is being managed at the # file level by Puppet. autorequire(:file) do if self[:name] "/var/db/dslocal/nodes/Default/computers/#{self[:name]}.plist" else nil end end newproperty(:ensure, :parent => Puppet::Property::Ensure) do desc "Control the existences of this computer record. Set this attribute to ``present`` to ensure the computer record exists. Set it to ``absent`` to delete any computer records with this name" newvalue(:present) do provider.create end newvalue(:absent) do provider.delete end end newparam(:name) do desc "The authoritative 'short' name of the computer record." isnamevar end newparam(:realname) do desc "The 'long' name of the computer record." end newproperty(:en_address) do desc "The MAC address of the primary network interface. Must match en0." end newproperty(:ip_address) do desc "The IP Address of the Computer object." end -end \ No newline at end of file +end diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index d7c3a8a39..a211f8572 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -1,622 +1,623 @@ module Puppet newtype(:exec) do include Puppet::Util::Execution require 'timeout' @doc = "Executes external commands. It is critical that all commands executed using this mechanism can be run multiple times without harm, i.e., they are *idempotent*. One useful way to create idempotent commands is to use the checks like ``creates`` to avoid running the command unless some condition is met. Note also that you can restrict an ``exec`` to only run when it receives events by using the ``refreshonly`` parameter; this is a useful way to have your configuration respond to events with arbitrary commands. It is worth noting that ``exec`` is special, in that it is not currently considered an error to have multiple ``exec`` instances with the same name. This was done purely because it had to be this way in order to get certain functionality, but it complicates things. In particular, you will not be able to use ``exec`` instances that share their commands with other instances as a dependency, since Puppet has no way of knowing which instance you mean. For example:: # defined in the production class exec { \"make\": cwd => \"/prod/build/dir\", path => \"/usr/bin:/usr/sbin:/bin\" } . etc. . # defined in the test class exec { \"make\": cwd => \"/test/build/dir\", path => \"/usr/bin:/usr/sbin:/bin\" } Any other type would throw an error, complaining that you had the same instance being managed in multiple places, but these are obviously different images, so ``exec`` had to be treated specially. It is recommended to avoid duplicate names whenever possible. Note that if an ``exec`` receives an event from another resource, it will get executed again (or execute the command specified in ``refresh``, if there is one). There is a strong tendency to use ``exec`` to do whatever work Puppet can't already do; while this is obviously acceptable (and unavoidable) in the short term, it is highly recommended to migrate work from ``exec`` to native Puppet types as quickly as possible. If you find that you are doing a lot of work with ``exec``, please at least notify us at Reductive Labs what you are doing, and hopefully we can work with you to get a native resource type for the work you are doing." require 'open3' # Create a new check mechanism. It's basically just a parameter that # provides one extra 'check' method. def self.newcheck(name, &block) @checks ||= {} check = newparam(name, &block) @checks[name] = check end def self.checks @checks.keys end newproperty(:returns) do |property| include Puppet::Util::Execution munge do |value| value.to_s end defaultto "0" attr_reader :output desc "The expected return code. An error will be returned if the executed command returns something else. Defaults to 0." # Make output a bit prettier def change_to_s(currentvalue, newvalue) return "executed successfully" end # First verify that all of our checks pass. def retrieve # Default to somethinng if @resource.check return :notrun else return self.should end end # Actually execute the command. def sync olddir = nil # We need a dir to change to, even if it's just the cwd dir = self.resource[:cwd] || Dir.pwd event = :executed_command begin @output, status = @resource.run(self.resource[:command]) rescue Timeout::Error self.fail "Command exceeded timeout" % value.inspect end if log = @resource[:logoutput] case log when :true log = @resource[:loglevel] when :on_failure if status.exitstatus.to_s != self.should.to_s log = @resource[:loglevel] else log = :false end end unless log == :false @output.split(/\n/).each { |line| self.send(log, line) } end end if status.exitstatus.to_s != self.should.to_s self.fail("%s returned %s instead of %s" % [self.resource[:command], status.exitstatus, self.should.to_s]) end return event end end newparam(:command) do isnamevar desc "The actual command to execute. Must either be fully qualified or a search path for the command must be provided. If the command succeeds, any output produced will be logged at the instance's normal log level (usually ``notice``), but if the command fails (meaning its return code does not match the specified code) then any output is logged at the ``err`` log level." end newparam(:path) do desc "The search path used for command execution. Commands must be fully qualified if no path is specified. Paths can be specified as an array or as a colon-separated list." # Support both arrays and colon-separated fields. def value=(*values) @value = values.flatten.collect { |val| val.split(":") }.flatten end end newparam(:user) do desc "The user to run the command as. Note that if you use this then any error output is not currently captured. This is because of a bug within Ruby. If you are using Puppet to create this user, the exec will automatically require the user, as long as it is specified by name." # Most validation is handled by the SUIDManager class. validate do |user| unless Puppet.features.root? self.fail "Only root can execute commands as other users" end end end newparam(:group) do desc "The group to run the command as. This seems to work quite haphazardly on different platforms -- it is a platform issue not a Ruby or Puppet one, since the same variety exists when running commnands as different users in the shell." # Validation is handled by the SUIDManager class. end newparam(:cwd) do desc "The directory from which to run the command. If this directory does not exist, the command will fail." validate do |dir| unless dir =~ /^#{File::SEPARATOR}/ self.fail("CWD must be a fully qualified path") end end munge do |dir| if dir.is_a?(Array) dir = dir[0] end dir end end newparam(:logoutput) do desc "Whether to log output. Defaults to logging output at the loglevel for the ``exec`` resource. Use *on_failure* to only log the output when the command reports an error. Values are **true**, *false*, *on_failure*, and any legal log level." newvalues(:true, :false, :on_failure) end newparam(:refresh) do desc "How to refresh this command. By default, the exec is just called again when it receives an event from another resource, but this parameter allows you to define a different command for refreshing." validate do |command| @resource.validatecmd(command) end end newparam(:env) do desc "This parameter is deprecated. Use 'environment' instead." munge do |value| warning "'env' is deprecated on exec; use 'environment' instead." resource[:environment] = value end end newparam(:environment) do desc "Any additional environment variables you want to set for a command. Note that if you use this to set PATH, it will override the ``path`` attribute. Multiple environment variables should be specified as an array." validate do |values| values = [values] unless values.is_a? Array values.each do |value| unless value =~ /\w+=/ raise ArgumentError, "Invalid environment setting '%s'" % value end end end end newparam(:timeout) do desc "The maximum time the command should take. If the command takes longer than the timeout, the command is considered to have failed and will be stopped. Use any negative number to disable the timeout. The time is specified in seconds." munge do |value| value = value.shift if value.is_a?(Array) if value.is_a?(String) unless value =~ /^[-\d.]+$/ raise ArgumentError, "The timeout must be a number." end Float(value) else value end end defaultto 300 end newcheck(:refreshonly) do desc "The command should only be run as a refresh mechanism for when a dependent object is changed. It only makes sense to use this option when this command depends on some other object; it is useful for triggering an action:: # Pull down the main aliases file file { \"/etc/aliases\": source => \"puppet://server/module/aliases\" } # Rebuild the database, but only when the file changes exec { newaliases: path => [\"/usr/bin\", \"/usr/sbin\"], subscribe => File[\"/etc/aliases\"], refreshonly => true } Note that only ``subscribe`` and ``notify`` can trigger actions, not ``require``, so it only makes sense to use ``refreshonly`` with ``subscribe`` or ``notify``." newvalues(:true, :false) # We always fail this test, because we're only supposed to run # on refresh. def check(value) # We have to invert the values. if value == :true false else true end end end newcheck(:creates) do desc "A file that this command creates. If this parameter is provided, then the command will only be run if the specified file does not exist:: exec { \"tar xf /my/tar/file.tar\": cwd => \"/var/tmp\", creates => \"/var/tmp/myfile\", path => [\"/usr/bin\", \"/usr/sbin\"] } " # FIXME if they try to set this and fail, then we should probably # fail the entire exec, right? validate do |files| files = [files] unless files.is_a? Array files.each do |file| self.fail("'creates' must be set to a fully qualified path") unless file unless file =~ %r{^#{File::SEPARATOR}} self.fail "'creates' files must be fully qualified." end end end # If the file exists, return false (i.e., don't run the command), # else return true def check(value) return ! FileTest.exists?(value) end end newcheck(:unless) do desc "If this parameter is set, then this ``exec`` will run unless the command returns 0. For example:: exec { \"/bin/echo root >> /usr/lib/cron/cron.allow\": path => \"/usr/bin:/usr/sbin:/bin\", unless => \"grep root /usr/lib/cron/cron.allow 2>/dev/null\" } This would add ``root`` to the cron.allow file (on Solaris) unless ``grep`` determines it's already there. Note that this command follows the same rules as the main command, which is to say that it must be fully qualified if the path is not set. " validate do |cmds| cmds = [cmds] unless cmds.is_a? Array cmds.each do |cmd| @resource.validatecmd(cmd) end end # Return true if the command does not return 0. def check(value) begin output, status = @resource.run(value, true) rescue Timeout::Error err "Check %s exceeded timeout" % value.inspect return false end return status.exitstatus != 0 end end newcheck(:onlyif) do desc "If this parameter is set, then this ``exec`` will only run if the command returns 0. For example:: exec { \"logrotate\": path => \"/usr/bin:/usr/sbin:/bin\", onlyif => \"test `du /var/log/messages | cut -f1` -gt 100000\" } This would run ``logrotate`` only if that test returned true. Note that this command follows the same rules as the main command, which is to say that it must be fully qualified if the path is not set. - Also note that onlyif can take an array as its value, eg: + Also note that onlyif can take an array as its value, eg:: + onlyif => [\"test -f /tmp/file1\", \"test -f /tmp/file2\"] This will only run the exec if /all/ conditions in the array return true. " validate do |cmds| cmds = [cmds] unless cmds.is_a? Array cmds.each do |cmd| @resource.validatecmd(cmd) end end # Return true if the command returns 0. def check(value) begin output, status = @resource.run(value, true) rescue Timeout::Error err "Check %s exceeded timeout" % value.inspect return false end return status.exitstatus == 0 end end # Exec names are not isomorphic with the objects. @isomorphic = false validate do validatecmd(self[:command]) end # FIXME exec should autorequire any exec that 'creates' our cwd autorequire(:file) do reqs = [] # Stick the cwd in there if we have it if self[:cwd] reqs << self[:cwd] end self[:command].scan(/^(#{File::SEPARATOR}\S+)/) { |str| reqs << str } [:onlyif, :unless].each { |param| next unless tmp = self[param] tmp = [tmp] unless tmp.is_a? Array tmp.each do |line| # And search the command line for files, adding any we # find. This will also catch the command itself if it's # fully qualified. It might not be a bad idea to add # unqualified files, but, well, that's a bit more annoying # to do. reqs += line.scan(%r{(#{File::SEPARATOR}\S+)}) end } # For some reason, the += isn't causing a flattening reqs.flatten! reqs end autorequire(:user) do # Autorequire users if they are specified by name if user = self[:user] and user !~ /^\d+$/ user end end def self.instances [] end # Verify that we pass all of the checks. The argument determines whether # we skip the :refreshonly check, which is necessary because we now check # within refresh() def check(refreshing = false) self.class.checks.each { |check| next if refreshing and check == :refreshonly if @parameters.include?(check) val = @parameters[check].value val = [val] unless val.is_a? Array val.each do |value| unless @parameters[check].check(value) return false end end end } return true end # Verify that we have the executable def checkexe(cmd) if cmd =~ /^\// exe = cmd.split(/ /)[0] unless FileTest.exists?(exe) raise ArgumentError, "Could not find executable %s" % exe end unless FileTest.executable?(exe) raise ArgumentError, "%s is not executable" % exe end elsif path = self[:path] exe = cmd.split(/ /)[0] withenv :PATH => self[:path].join(":") do path = %{which #{exe}}.chomp if path == "" raise ArgumentError, "Could not find command '%s'" % exe end end else raise ArgumentError, "%s is somehow not qualified with no search path" % self[:command] end end def output if self.property(:returns).nil? return nil else return self.property(:returns).output end end # Run the command, or optionally run a separately-specified command. def refresh if self.check(true) if cmd = self[:refresh] self.run(cmd) else self.property(:returns).sync end end end # Run a command. def run(command, check = false) output = nil status = nil dir = nil checkexe(command) if dir = self[:cwd] unless File.directory?(dir) if check dir = nil else self.fail "Working directory '%s' does not exist" % dir end end end dir ||= Dir.pwd if check debug "Executing check '#{command}'" else debug "Executing '#{command}'" end begin # Do our chdir Dir.chdir(dir) do environment = {} if self[:path] environment[:PATH] = self[:path].join(":") end if envlist = self[:environment] envlist = [envlist] unless envlist.is_a? Array envlist.each do |setting| if setting =~ /^(\w+)=((.|\n)+)$/ name = $1 value = $2 if environment.include? name warning( "Overriding environment setting '%s' with '%s'" % [name, value] ) end environment[name] = value else warning "Cannot understand environment setting %s" % setting.inspect end end end withenv environment do Timeout::timeout(self[:timeout]) do output, status = Puppet::Util::SUIDManager.run_and_capture( [command], self[:user], self[:group] ) end # The shell returns 127 if the command is missing. if status.exitstatus == 127 raise ArgumentError, output end end end rescue Errno::ENOENT => detail self.fail detail.to_s end return output, status end def validatecmd(cmd) # if we're not fully qualified, require a path if cmd !~ /^\// if self[:path].nil? self.fail "'%s' is both unqualifed and specified no search path" % cmd end end end end end diff --git a/lib/puppet/type/macauthorization.rb b/lib/puppet/type/macauthorization.rb index 46e02ddae..0265242dc 100644 --- a/lib/puppet/type/macauthorization.rb +++ b/lib/puppet/type/macauthorization.rb @@ -1,142 +1,141 @@ Puppet::Type.newtype(:macauthorization) do @doc = "Manage the Mac OS X authorization database. - - See: http://developer.apple.com/documentation/Security/Conceptual/Security_Overview/Security_Services/chapter_4_section_5.html - for more information." + See: + http://developer.apple.com/documentation/Security/Conceptual/Security_Overview/Security_Services/chapter_4_section_5.html for more information." ensurable autorequire(:file) do ["/etc/authorization"] end def munge_boolean(value) case value when true, "true", :true: :true when false, "false", :false :false else raise Puppet::Error("munge_boolean only takes booleans") end end newparam(:name) do desc "The name of the right or rule to be managed. Corresponds to 'key' in Authorization Services. The key is the name of a rule. A key uses the same naming conventions as a right. The Security Server uses a rule’s key to match the rule with a right. Wildcard keys end with a ‘.’. The generic rule has an empty key value. Any rights that do not match a specific rule use the generic rule." isnamevar end newproperty(:auth_type) do desc "type - can be a 'right' or a 'rule'. 'comment' has not yet been implemented." newvalue(:right) newvalue(:rule) # newvalue(:comment) # not yet implemented. end newproperty(:allow_root, :boolean => true) do desc "Corresponds to 'allow-root' in the authorization store, renamed due to hyphens being problematic. Specifies whether a right should be allowed automatically if the requesting process is running with uid == 0. AuthorizationServices defaults this attribute to false if not specified" newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:authenticate_user, :boolean => true) do desc "Corresponds to 'authenticate-user' in the authorization store, renamed due to hyphens being problematic." newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:auth_class) do desc "Corresponds to 'class' in the authorization store, renamed due to 'class' being a reserved word." newvalue(:user) newvalue(:'evaluate-mechanisms') end newproperty(:comment) do desc "The 'comment' attribute for authorization resources." end newproperty(:group) do desc "The user must authenticate as a member of this group. This attribute can be set to any one group." end newproperty(:k_of_n) do desc "k-of-n. Built-in rights only show a value of '1' or absent, other values may be acceptable. Undocumented." end newproperty(:mechanisms, :array_matching => :all) do desc "an array of suitable mechanisms." end newproperty(:rule, :array_match => :all) do desc "The rule(s) that this right refers to." end newproperty(:session_owner, :boolean => true) do desc "Corresponds to 'session-owner' in the authorization store, renamed due to hyphens being problematic. Whether the session owner automatically matches this rule or right." newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:shared, :boolean => true) do desc "If this is set to true, then the Security Server marks the credentials used to gain this right as shared. The Security Server may use any shared credentials to authorize this right. For maximum security, set sharing to false so credentials stored by the Security Server for one application may not be used by another application." newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:timeout) do desc "The credential used by this rule expires in the specified number of seconds. For maximum security where the user must authenticate every time, set the timeout to 0. For minimum security, remove the timeout attribute so the user authenticates only once per session." end newproperty(:tries) do desc "The number of tries allowed." end end diff --git a/lib/puppet/type/mcx.rb b/lib/puppet/type/mcx.rb index ec33afd13..f92cc467d 100644 --- a/lib/puppet/type/mcx.rb +++ b/lib/puppet/type/mcx.rb @@ -1,114 +1,115 @@ #-- # Copyright (C) 2008 Jeffrey J McCune. # This program and entire repository is free software; you can # redistribute it and/or modify it under the terms of the GNU # General Public License as published by the Free Software # Foundation; either version 2 of the License, or any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Author: Jeff McCune Puppet::Type.newtype(:mcx) do @doc = "MCX object management using DirectoryService on OS X. Original Author: Jeff McCune The default provider of this type merely manages the XML plist as reported by the dscl -mcxexport command. This is similar to the content property of the file type in Puppet. The recommended method of using this type is to use Work Group Manager to manage users and groups on the local computer, record the resulting puppet manifest using the command 'ralsh mcx' then deploying this to other machines. " feature :manages_content, \ "The provider can manage MCXSettings as a string.", :methods => [:content, :content=] ensurable do desc "Create or remove the MCX setting." newvalue(:present) do provider.create end newvalue(:absent) do provider.destroy end end newparam(:name) do desc "The name of the resource being managed. - The default naming convention follows Directory Service paths: - '/Computers/localhost' - '/Groups/admin' - '/Users/localadmin' + The default naming convention follows Directory Service paths:: + + /Computers/localhost + /Groups/admin + /Users/localadmin The ds_type and ds_name type parameters are not necessary if the default naming convention is followed." isnamevar end newparam(:ds_type) do desc "The DirectoryService type this MCX setting attaches to." newvalues(:user, :group, :computer, :computerlist) end newparam(:ds_name) do desc "The name to attach the MCX Setting to. e.g. 'localhost' when ds_type => computer. This setting is not required, as it may be parsed so long as the resource name is parseable. e.g. /Groups/admin where 'group' is the dstype." end newproperty(:content, :required_features => :manages_content) do desc "The XML Plist. The value of MCXSettings in DirectoryService. This is the standard output from the system command: dscl localhost -mcxexport /Local/Default// Note that ds_type is capitalized and plural in the dscl command." end # JJM Yes, this is not DRY at all. Because of the code blocks # autorequire must be done this way. I think. def setup_autorequire(type) # value returns a Symbol name = value(:name) ds_type = value(:ds_type) ds_name = value(:ds_name) if ds_type == type rval = [ ds_name.to_s ] else rval = [ ] end rval end autorequire(:user) do setup_autorequire(:user) end autorequire(:group) do setup_autorequire(:group) end autorequire(:computer) do setup_autorequire(:computer) end end