diff options
82 files changed, 4994 insertions, 2829 deletions
diff --git a/examples/root/etc/init.d/sleeper b/examples/root/etc/init.d/sleeper index 121d19940..a4e28a2fb 100755 --- a/examples/root/etc/init.d/sleeper +++ b/examples/root/etc/init.d/sleeper @@ -7,7 +7,7 @@ path=`echo $script | sed 's/etc..*/bin/'` PATH=$PATH:$path -ps=`facter ps | cut -d ' ' -f3-` +ps=`facter ps` if [ -z "$ps" ]; then ps="ps -ef" diff --git a/lib/puppet/autoload.rb b/lib/puppet/autoload.rb new file mode 100644 index 000000000..4e5d07e31 --- /dev/null +++ b/lib/puppet/autoload.rb @@ -0,0 +1,101 @@ +# I have no idea what's going on here, but... +unless defined? MissingSourceFile + class MissingSourceFile < RuntimeError + end +end +# Autoload paths, either based on names or all at once. +class Puppet::Autoload + include Puppet::Util + + @autoloaders = {} + + attr_accessor :object, :path, :objwarn, :wrap + + + class << self + attr_reader :autoloaders + private :autoloaders + end + Puppet::Util.classproxy self, :autoloaders, "[]", "[]=", :clear + + attr_reader :loaded + private :loaded + + Puppet::Util.proxy self, :loaded, :clear + + def initialize(obj, path, options = {}) + @path = path.to_s + @object = obj + + self.class[obj] = self + + options.each do |opt, value| + opt = opt.intern if opt.is_a? String + begin + self.send(opt.to_s + "=", value) + rescue NoMethodError + raise ArgumentError, "%s is not a valid option" % opt + end + end + + unless defined? @wrap + @wrap = true + end + + @loaded = {} + end + + def load(name) + name = symbolize(name) + + path = File.join(@path, name.to_s + ".rb") + + begin + Kernel.load path, @wrap + @loaded[name] = true + return true + rescue MissingSourceFile + return false + rescue LoadError => detail + # I have no idea what's going on here, but different versions + # of ruby are raising different errors on missing files. + unless detail.to_s =~ /^no such file/i + warn "Could not autoload %s: %s" % [name, detail] + end + return false + end + end + + def loaded?(name) + name = symbolize(name) + @loaded[name] + end + + def loadall + # Load every instance of everything we can find. + $:.each do |dir| + fdir = File.join(dir, @path) + if FileTest.exists?(fdir) and FileTest.directory?(fdir) + Dir.glob("#{fdir}/*.rb").each do |file| + # Load here, rather than require, so that facts + # can be reloaded. This has some short-comings, I + # believe, but it works as long as real classes + # aren't used. + name = File.basename(file).sub(".rb", '').intern + next if @loaded.include? name + begin + Kernel.load file, @wrap + @loaded[name] = true + rescue => detail + #if Puppet[:debug] + # puts detail.backtrace + #end + warn "Could not autoload %s: %s" % [file.inspect, detail] + end + end + end + end + end +end + +# $Id$ diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb index 9ee0d55d8..cf522a994 100644 --- a/lib/puppet/client.rb +++ b/lib/puppet/client.rb @@ -26,9 +26,15 @@ module Puppet def initcerts unless self.readcert - unless self.requestcert - return nil - end + #if self.is_a? Puppet::Client::CA + unless self.requestcert + return nil + end + #else + # return nil + #end + #unless self.requestcert + #end end # unless we have a driver, we're a local client and we can't add @@ -95,6 +101,15 @@ module Puppet end end + # Are we a local client? + def local? + if defined? @local and @local + true + else + false + end + end + # A wrapper method to run and then store the last run time def runnow if self.stopping diff --git a/lib/puppet/client/master.rb b/lib/puppet/client/master.rb index 505a2109b..906f8be3c 100644 --- a/lib/puppet/client/master.rb +++ b/lib/puppet/client/master.rb @@ -508,7 +508,7 @@ class Puppet::Client::MasterClient < Puppet::Client File.basename(object[:path]).sub(".rb",'') load object[:path] rescue => detail - Puppet.warning "Could not load %s: %s" % + Puppet.warning "Could not reload plugin %s: %s" % [object[:path], detail] end end diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 13ae3f6a2..c341bf219 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -157,24 +157,45 @@ module Puppet } end + unless defined? @driver Puppet.err "Cannot request a certificate without a defined target" return false end - Puppet.info "Creating a new certificate request for %s" % @fqdn - name = OpenSSL::X509::Name.new([["CN", @fqdn]]) + unless defined? @csr + Puppet.info "Creating a new certificate request for %s" % @fqdn + name = OpenSSL::X509::Name.new([["CN", @fqdn]]) - @csr = OpenSSL::X509::Request.new - @csr.version = 0 - @csr.subject = name - @csr.public_key = @key.public_key - @csr.sign(@key, OpenSSL::Digest::MD5.new) + @csr = OpenSSL::X509::Request.new + @csr.version = 0 + @csr.subject = name + @csr.public_key = @key.public_key + @csr.sign(@key, OpenSSL::Digest::MD5.new) + end Puppet.info "Requesting certificate" + # We can only request a client with a CA client, so we need + # to create one if we don't already have one (or if we're not a CA + # server). + caclient = nil + if @driver.is_a? Puppet::Client::CA or @driver.is_a? Puppet::Server::CA + caclient = @driver + else + # Create a CA client with which to request the cert. + if @driver.local? + raise Puppet::DevError, + "Incorrect setup for a local CA request" + end + caclient = Puppet::Client::CA.new( + :Port => @driver.puppet_port, + :Server => @driver.puppet_server + ) + end + begin - cert, cacert = @driver.getcert(@csr.to_pem) + cert, cacert = caclient.getcert(@csr.to_pem) rescue => detail if Puppet[:debug] puts detail.backtrace diff --git a/lib/puppet/element.rb b/lib/puppet/element.rb index 7abc18fda..cc682f2e4 100644 --- a/lib/puppet/element.rb +++ b/lib/puppet/element.rb @@ -11,6 +11,7 @@ class Puppet::Element class << self attr_accessor :doc, :nodoc + include Puppet::Util end # all of our subclasses must respond to each of these methods... @@ -22,7 +23,7 @@ class Puppet::Element @@interface_methods.each { |method| self.send(:define_method,method) { raise Puppet::DevError, "%s has not overridden %s" % - [self.class,method] + [self.class.name,method] } } diff --git a/lib/puppet/filetype.rb b/lib/puppet/filetype.rb index 168d8e472..7a4067c9d 100755 --- a/lib/puppet/filetype.rb +++ b/lib/puppet/filetype.rb @@ -145,6 +145,10 @@ module Puppet # Handle Linux-style cron tabs. newfiletype(:crontab) do def initialize(user) + self.path = user + end + + def path=(user) begin uid = Puppet::Util.uid(user) rescue Puppet::Error => detail @@ -153,7 +157,7 @@ module Puppet # We have to have the user name, not the uid, because some # systems *cough*linux*cough* require it that way - @path = user + @path = uid end # Read a specific @path's cron tab. @@ -177,7 +181,7 @@ module Puppet private # Only add the -u flag when the @path is different. Fedora apparently - # does not think I should be allowed to set the @path to my + # does not think I should be allowed to set the @path to my own user name def cmdbase cmd = nil if @path == Process.uid diff --git a/lib/puppet/networkclient.rb b/lib/puppet/networkclient.rb index fa5a21957..82822533a 100644 --- a/lib/puppet/networkclient.rb +++ b/lib/puppet/networkclient.rb @@ -30,6 +30,7 @@ module Puppet Puppet.err "Could not load client network libs: %s" % $noclientnetworking else class NetworkClient < XMLRPC::Client + attr_accessor :puppet_server, :puppet_port @clients = {} # Create a netclient for each handler @@ -126,6 +127,9 @@ module Puppet hash[:Server] ||= "localhost" hash[:Port] ||= Puppet[:masterport] + @puppet_server = hash[:Server] + @puppet_port = hash[:Port] + @puppetserver = hash[:Server] super( diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb new file mode 100644 index 000000000..44851c16b --- /dev/null +++ b/lib/puppet/provider.rb @@ -0,0 +1,134 @@ +# The container class for implementations. +class Puppet::Provider + include Puppet::Util + + Puppet::Util.logmethods(self, true) + + class << self + # Include the util module so we have access to things like 'binary' + include Puppet::Util + attr_accessor :name, :model, :doc + end + + attr_accessor :model + + def self.command(name) + name = symbolize(name) + + if command = @commands[name] + return command + elsif superclass.respond_to? :command and command = superclass.command(name) + return command + else + raise Puppet::DevError, "No command %s defined for provider %s" % + [name, self.name] + end + end + + # Define one or more binaries we'll be using + def self.commands(hash) + hash.each do |name, path| + name = symbolize(name) + path = binary(path) unless path =~ /^\// + @commands[name] = path + confine :exists => path + end + end + + def self.confine(hash) + hash.each do |p,v| + if v.is_a? Array + @confines[p] += v + else + @confines[p] << v + end + end + end + + # Does this implementation match all of the default requirements? + def self.default? + if @defaults.find do |fact, value| + Facter[fact].value.to_s.downcase.intern != value.to_s.downcase.intern + end + return false + else + return true + end + end + + # Store how to determine defaults. + def self.defaultfor(hash) + hash.each do |d,v| + @defaults[d] = v + end + end + + def self.defaultnum + @defaults.length + end + + # Specify a documentation string. + def self.desc(str) + @doc = str + end + + def self.initvars + @defaults = {} + @commands = {} + @confines = Hash.new do |hash, key| + hash[key] = [] + end + end + + self.initvars + + # Check whether this implementation is suitable for our platform. + def self.suitable? + # A single false result is sufficient to turn the whole thing down. + @confines.each do |check, values| + case check + when :exists: + values.each do |value| + unless value and FileTest.exists? value + return false + end + end + when :true: + values.each do |v| + return false unless v + end + when :false: + values.each do |v| + return false if v + end + else # Just delegate everything else to facter + result = Facter.send(check).to_s.downcase.intern + + found = values.find do |v| + result == v.to_s.downcase.intern + end + return false unless found + end + end + + return true + end + + def command(name) + self.class.command(name) + end + + def initialize(model) + @model = model + end + + def name + @model.name + end + + def to_s + "%s(provider=%s)" % [@model.to_s, self.class.name] + end +end + +# $Id$ diff --git a/lib/puppet/provider/group/groupadd.rb b/lib/puppet/provider/group/groupadd.rb new file mode 100644 index 000000000..4c204ddf6 --- /dev/null +++ b/lib/puppet/provider/group/groupadd.rb @@ -0,0 +1,29 @@ +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" + + 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 = @model[:gid] + unless gid == :absent + cmd << flag(:gid) << "'%s'" % @model[:gid] + end + end + if @model[:allowdupe] == :true + cmd << "-o" + end + cmd << @model[:name] + + return cmd.join(" ") + end +end + +# $Id$ diff --git a/lib/puppet/provider/group/netinfo.rb b/lib/puppet/provider/group/netinfo.rb new file mode 100644 index 000000000..4be1e4f35 --- /dev/null +++ b/lib/puppet/provider/group/netinfo.rb @@ -0,0 +1,15 @@ +# 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(:group).provide :netinfo, :parent => Puppet::Provider::NameService::NetInfo do + desc "Group management using NetInfo." + NIREPORT = binary("nireport") + NIUTIL = binary("niutil") + confine :exists => NIREPORT + confine :exists => NIUTIL + + defaultfor :operatingsystem => :darwin +end + +# $Id$ diff --git a/lib/puppet/provider/group/pw.rb b/lib/puppet/provider/group/pw.rb new file mode 100644 index 000000000..48934a3c5 --- /dev/null +++ b/lib/puppet/provider/group/pw.rb @@ -0,0 +1,31 @@ +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." + + 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", @model[:name]] + if gid = @model[:gid] + unless gid == :absent + cmd << flag(:gid) << "'%s'" % @model[:gid] + end + end + + # Apparently, contrary to the man page, groupadd does + # not accept -o. + #if @parent[:allowdupe] == :true + # cmd << "-o" + #end + + return cmd.join(" ") + end +end + +# $Id$ diff --git a/lib/puppet/provider/nameservice.rb b/lib/puppet/provider/nameservice.rb new file mode 100644 index 000000000..96c1b6f4d --- /dev/null +++ b/lib/puppet/provider/nameservice.rb @@ -0,0 +1,319 @@ +require 'puppet' + +# This is the parent class of all NSS classes. They're very different in +# their backend, but they're pretty similar on the front-end. This class +# provides a way for them all to be as siilar as possible. +class Puppet::Provider::NameService < Puppet::Provider + class << self + + def list + objects = [] + listbyname do |name| + obj = nil + check = model.validstates + if obj = model[name] + obj[:check] = check + else + # unless it exists, create it as an unmanaged object + obj = model.create(:name => name, :check => check) + end + + next unless obj # In case there was an error somewhere + + objects << obj + yield obj if block_given? + end + + objects + end + + def option(name, option) + name = name.intern if name.is_a? String + if defined? @options and @options.include? name and @options[name].include? option + return @options[name][option] + else + return nil + end + end + + def options(name, hash) + unless model.validstate?(name) + raise Puppet::DevError, "%s is not a valid state for %s" % + [name, model.name] + end + @options ||= {} + @options[name] ||= {} + + # Set options individually, so we can call the options method + # multiple times. + hash.each do |param, value| + @options[name][param] = value + end + end + + # List everything out by name. Abstracted a bit so that it works + # for both users and groups. + def listbyname + names = [] + Etc.send("set%sent" % section()) + begin + while ent = Etc.send("get%sent" % section()) + names << ent.name + if block_given? + yield ent.name + end + end + ensure + Etc.send("end%sent" % section()) + end + + return names + end + + # This is annoying, but there really aren't that many options, + # and this *is* built into Ruby. + def section + unless defined? @model + raise Puppet::DevError, + "Cannot determine Etc section without a model" + + end + + if @model.name == :group + "gr" + else + "pw" + end + end + + def disabled_validate(name, value) + name = name.intern if name.is_a? String + if @checks.include? name + block = @checks[name][:block] + unless block.call(value) + raise ArgumentError, "Invalid value %s: %s" % + [value, @checks[name][:error]] + end + end + end + + def verify(name, error, &block) + name = name.intern if name.is_a? String + @checks ||= {} + @checks[name] = {:error => error, :block => block} + end + + private + + def op(state) + @ops[state.name] || ("-" + state.name) + end + end + + # Autogenerate either a uid or a gid. This is hard-coded: we can only + # generate one field type per class. + def autogen + highest = 0 + + group = method = nil + case @model.class.name + when :user: group = :passwd; method = :uid + when :group: group = :group; method = :gid + else + raise Puppet::DevError, "Invalid model name %s" % model + end + + # Make sure we don't use the same value multiple times + if defined? @@prevauto + @@prevauto += 1 + else + Etc.send(group) { |obj| + if obj.gid > highest + unless obj.send(method) > 65000 + highest = obj.send(method) + end + end + } + + @@prevauto = highest + 1 + end + + return @@prevauto + end + + def autogen_gid + autogen(@model.class.name) + end + + def autogen_uid + autogen(@model.class.name) + end + + def create + self.ensure = :present + end + + def delete + self.ensure = :absent + end + + def ensure + if exists? + :present + else + :absent + end + end + + # This is only used when creating or destroying the object. + def ensure=(value) + cmd = nil + event = nil + case value + when :absent + # we need to remove the object... + unless exists? + info "already absent" + # the object already doesn't exist + return nil + end + + # again, needs to be set by the ind. state or its + # parent + cmd = self.deletecmd + type = "delete" + when :present + if exists? + info "already exists" + # The object already exists + return nil + end + + # blah blah, define elsewhere, blah blah + cmd = self.addcmd + type = "create" + end + + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not %s %s %s: %s" % + [type, @model.class.name, @model.name, detail] + end + end + + # Does our object exist? + def exists? + if getinfo(true) + return true + else + return false + end + end + + # Retrieve a specific value by name. + def get(param) + if hash = getinfo(false) + return hash[param] + else + return nil + end + end + + # Retrieve what we can about our object + def getinfo(refresh) + if @objectinfo.nil? or refresh == true + @etcmethod ||= ("get" + self.class.section().to_s + "nam").intern + begin + @objectinfo = Etc.send(@etcmethod, @model[:name]) + rescue ArgumentError => detail + @objectinfo = nil + end + end + + # Now convert our Etc struct into a hash. + if @objectinfo + return info2hash(@objectinfo) + else + return nil + end + end + + # The list of all groups the user is a member of. Different + # user mgmt systems will need to override this method. + def groups + groups = [] + + # Reset our group list + Etc.setgrent + + user = @model[:name] + + # Now iterate across all of the groups, adding each one our + # user is a member of + while group = Etc.getgrent + members = group.mem + + if members.include? user + groups << group.name + end + end + + # We have to close the file, so each listing is a separate + # reading of the file. + Etc.endgrent + + groups.join(",") + end + + # Convert the Etc struct into a hash. + def info2hash(info) + hash = {} + self.class.model.validstates.each do |param| + method = posixmethod(param) + if info.respond_to? method + hash[param] = info.send(posixmethod(param)) + end + end + + return hash + end + + def initialize(model) + super + + @objectinfo = nil + end + + # + def method_missing(name, *args) + name = name.to_s + + # Make sure it's a valid state. We go up our class structure instead of + # our model's because the model is fake during testing. + unless self.class.model.validstate?(name.sub("=",'')) + raise Puppet::DevError, "%s is not a valid %s state" % + [name, @model.class.name] + end + + # Each class has to override this manually + if name =~ /=/ + set(name.to_s.sub("=", ''), *args) + else + return get(name.intern) || :absent + end + end + + def set(param, value) + #self.class.validate(param, value) + cmd = modifycmd(param, value) + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set %s on %s[%s]: %s" % + [param, @model.class.name, @model.name, detail] + end + end +end + +# $Id$ diff --git a/lib/puppet/provider/nameservice/netinfo.rb b/lib/puppet/provider/nameservice/netinfo.rb new file mode 100644 index 000000000..d3a51bcf0 --- /dev/null +++ b/lib/puppet/provider/nameservice/netinfo.rb @@ -0,0 +1,211 @@ +# Manage NetInfo POSIX objects. Probably only used on OS X, but I suppose +# it could be used elsewhere. + +require 'puppet' +require 'puppet/provider/nameservice' + +class Puppet::Provider::NameService +class NetInfo < Puppet::Provider::NameService + # Attempt to flush the database, but this doesn't seem to work at all. + def self.flush + begin + output = execute("/usr/sbin/lookupd -flushcache 2>&1") + rescue Puppet::ExecutionFailure + # Don't throw an error; it's just a failed cache flush + Puppet.err "Could not flush lookupd cache: %s" % output + end + end + + # Similar to posixmethod, what key do we use to get data? Defaults + # to being the object name. + def self.netinfodir + if defined? @netinfodir + return @netinfodir + else + return @model.name.to_s + "s" + end + end + + def self.finish + case self.name + when :uid: + noautogen + when :gid: + noautogen + end + end + + # How to add an object. + def addcmd + creatorcmd("-create") + end + + def creatorcmd(arg) + cmd = ["niutil"] + cmd << arg + + cmd << "/" << "/%s/%s" % + [self.class.netinfodir(), @model[:name]] + return cmd.join(" ") + end + + def deletecmd + creatorcmd("-destroy") + end + + def ensure=(arg) + super + + # Because our stupid type can't create the whole thing at once, + # we have to do this hackishness. Yay. + if arg == :present + if @model.class.name == :user + notice modifycmd(:groups, @model[:groups]).inspect + end + # We need to generate the id if it's missing. + @model.class.validstates.each do |name| + next if name == :ensure + unless val = @model[name] + if (@model.class.name == :user and name == :uid) or + (@model.class.name == :group and name == :gid) + val = autogen() + else + # No value, and it's not required, so skip it. + info "No value for %s" % name + next + end + end + self.send(name.to_s + "=", val) + end + end + end + + #def exists? + # if self.report(:name) + # return true + # else + # return false + # end + #end + + # Retrieve a specific value by name. + def get(param) + hash = getinfo(false) + if hash + return hash[param] + else + return :absent + end + end + + # Retrieve everything about this object at once, instead of separately. + def getinfo(refresh = false) + if refresh or (! defined? @infohash or ! @infohash) + states = [:name] + self.class.model.validstates + states.delete(:ensure) if states.include? :ensure + @infohash = report(*states) + end + + return @infohash + end + + def modifycmd(param, value) + cmd = ["niutil"] + + cmd << "-createprop" << "/" << "/%s/%s" % + [self.class.netinfodir, @model[:name]] + + if key = netinfokey(param) + cmd << key << "'%s'" % value + else + raise Puppet::DevError, + "Could not find netinfokey for state %s" % + self.class.name + end + cmd.join(" ") + end + + # Determine the flag to pass to our command. + def netinfokey(name) + name = symbolize(name) + self.class.option(name, :key) || name + end + + # Retrieve the data, yo. + # FIXME This should retrieve as much information as possible, + # rather than retrieving it one at a time. + def report(*params) + dir = self.class.netinfodir() + cmd = ["nireport", "/", "/%s" % dir] + + # We require the name in order to know if we match. There's no + # way to just report on our individual object, we have to get the + # whole list. + params.unshift :name unless params.include? :name + + params.each do |param| + if key = netinfokey(param) + cmd << key.to_s + else + raise Puppet::DevError, + "Could not find netinfokey for state %s" % + self.class.name + end + end + self.debug "Executing %s" % cmd.join(" ").inspect + + %x{#{cmd.join(" ")} 2>&1}.split("\n").each { |line| + values = line.split(/\t/) + + hash = {} + params.zip(values).each do |param, value| + next if value == '#NoValue#' + hash[param] = if value =~ /^[-0-9]+$/ + Integer(value) + else + value + end + end + + if hash[:name] == @model[:name] + return hash + else + next + end + +# +# if line =~ /^(\w+)\s+(.+)$/ +# name = $1 +# value = $2.sub(/\s+$/, '') +# +# if name == @model[:name] +# if value =~ /^[-0-9]+$/ +# return Integer(value) +# else +# return value +# end +# end + } + + return nil + end + + def retrieve + raise "wtf?" + @is = report() || :absent + end + + def setuserlist(group, list) + cmd = "niutil -createprop / /groups/%s users %s" % + [group, list.join(",")] + begin + output = execute(cmd) + rescue Puppet::Execution::Failure => detail + raise Puppet::Error, "Failed to set user list on %s: %s" % + [group, detail] + end + end +end +end + +# $Id$ diff --git a/lib/puppet/provider/nameservice/objectadd.rb b/lib/puppet/provider/nameservice/objectadd.rb new file mode 100644 index 000000000..056ac5e84 --- /dev/null +++ b/lib/puppet/provider/nameservice/objectadd.rb @@ -0,0 +1,45 @@ +require 'puppet/provider/nameservice' + +class Puppet::Provider::NameService +class ObjectAdd < Puppet::Provider::NameService + # Does the object already exist? + def self.exists?(obj) + if obj.getinfo(true) + return true + else + return false + end + end + + def deletecmd + [command(:delete), @model[:name]].join(" ") + end + + # Determine the flag to pass to our command. + def flag(name) + name = name.intern if name.is_a? String + self.class.option(name, :flag) || "-" + name.to_s[0,1] + end + + def modifycmd(param, value) + cmd = [command(:modify), + flag(param), + "'%s'" % value] + if @model[:allowdupe] == :true + cmd << "-o" + end + cmd << @model[:name] + + return cmd.join(" ") + end + + def posixmethod(name) + name = name.intern if name.is_a? String + method = self.class.option(name, :method) || name + + return method + end +end +end + +# $Id$ diff --git a/lib/puppet/provider/nameservice/pw.rb b/lib/puppet/provider/nameservice/pw.rb new file mode 100644 index 000000000..e151a258d --- /dev/null +++ b/lib/puppet/provider/nameservice/pw.rb @@ -0,0 +1,22 @@ +require 'puppet/provider/nameservice/objectadd' + +class Puppet::Provider::NameService +class PW < ObjectAdd + def deletecmd + "#{command(:pw)} #{@model.class.name.to_s}del %s" % @model[:name] + end + + def modifycmd(param, value) + cmd = [ + command(:pw), + "#{@model.class.name.to_s}mod", + @model[:name], + flag(param), + "'%s'" % value + ] + return cmd.join(" ") + end +end +end + +# $Id$ diff --git a/lib/puppet/provider/package/apple.rb b/lib/puppet/provider/package/apple.rb new file mode 100755 index 000000000..a9a86652d --- /dev/null +++ b/lib/puppet/provider/package/apple.rb @@ -0,0 +1,55 @@ +# OS X Packaging sucks. We can install packages, but that's about it. +Puppet::Type.type(:package).provide :apple do + desc "Package management based on OS X's builtin packaging system. This is + essentially the simplest and least functional package system in existence -- + it only supports installation; no deletion or upgrades." + + confine :exists => "/Library/Receipts" + confine :exists => "/usr/sbin/installer" + + defaultfor :operatingsystem => :darwin + + def self.listbyname + Dir.entries("/Library/Receipts").find { |f| + f =~ /\.pkg$/ + }.collect { |f| + name = f.sub(/\.pkg/, '') + yield name if block_given? + + name + } + end + + def self.list + listbyname.collect do |name| + Puppet.type(:package).installedpkg( + :name => name, + :provider => :apple, + :ensure => :installed + ) + end + end + + def query + if FileTest.exists?("/Library/Receipts/#{@model[:name]}.pkg") + return {:name => @model[:name], :ensure => :present} + else + return nil + end + end + + def install + source = nil + unless source = @model[:source] + self.fail "Mac OS X packages must specify a package source" + end + + begin + output = execute("/usr/sbin/installer -pkg #{source} -target / 2>&1") + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/apt.rb b/lib/puppet/provider/package/apt.rb new file mode 100755 index 000000000..7f2cef375 --- /dev/null +++ b/lib/puppet/provider/package/apt.rb @@ -0,0 +1,112 @@ +Puppet::Type.type(:package).provide :apt, :parent => :dpkg do + desc "Package management via ``apt-get``." + + APT = "/usr/bin/apt-get" + + confine :exists => APT + + defaultfor :operatingsystem => :debian + + ENV['DEBIAN_FRONTEND'] = "noninteractive" + + # A derivative of DPKG; this is how most people actually manage + # Debian boxes, and the only thing that differs is that it can + # install packages from remote sites. + + def checkforcdrom + unless defined? @@checkedforcdrom + if FileTest.exists? "/etc/apt/sources.list" + if File.read("/etc/apt/sources.list") =~ /^[^#]*cdrom:/ + @@checkedforcdrom = true + else + @@checkedforcdrom = false + end + else + # This is basically a pathalogical case, but we'll just + # ignore it + @@checkedforcdrom = false + end + end + + if @@checkedforcdrom and @model[:allowcdrom] != :true + raise Puppet::Error, + "/etc/apt/sources.list contains a cdrom source; not installing. Use 'allowcdrom' to override this failure." + end + end + + # Install a package using 'apt-get'. This function needs to support + # installing a specific version. + def install + should = @model.should(:ensure) + + checkforcdrom() + + str = @model[:name] + case should + when true, false, Symbol + # pass + else + # Add the package version + str += "=%s" % should + end + cmd = "/usr/bin/apt-get -q -y install %s" % str + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end + + # What's the latest package version available? + def latest + cmd = "/usr/bin/apt-cache showpkg %s" % @model[:name] + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + + if output =~ /Versions:\s*\n((\n|.)+)^$/ + versions = $1 + version = versions.split(/\n/).collect { |version| + if version =~ /^([^\(]+)\(/ + $1 + else + self.warning "Could not match version '%s'" % version + nil + end + }.reject { |vers| vers.nil? }.sort[-1] + + unless version + self.debug "No latest version" + if Puppet[:debug] + print output + end + end + + return version + else + self.err "Could not match string" + end + end + + def update + self.install + end + + def uninstall + cmd = "/usr/bin/apt-get -y -q remove %s" % @model[:name] + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end + + def versionable? + true + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/blastwave.rb b/lib/puppet/provider/package/blastwave.rb new file mode 100755 index 000000000..b6c449415 --- /dev/null +++ b/lib/puppet/provider/package/blastwave.rb @@ -0,0 +1,138 @@ +# Packaging using Blastwave's pkg-get program. +Puppet::Type.type(:package).provide :blastwave, :parent => :sun do + desc "Package management using Blastwave.org's ``pkg-get`` command on Solaris." + if FileTest.executable?("/opt/csw/bin/pkg-get") + PKGGET = "/opt/csw/bin/pkg-get" + elsif pkgget = binary("pkg-get") + PKGGET = pkgget + else + PKGGET = nil + end + + confine :exists => PKGGET + + # This is so stupid, but then, so is blastwave. + ENV["PAGER"] = "/usr/bin/cat" + + def self.extended(mod) + unless PKGGET + raise Puppet::Error, + "The pkg-get command is missing; blastwave packaging unavailable" + end + + unless FileTest.exists?("/var/pkg-get/admin") + Puppet.notice "It is highly recommended you create '/var/pkg-get/admin'." + Puppet.notice "See /var/pkg-get/admin-fullauto" + end + end + + def self.list(hash = {}) + blastlist(hash).each do |bhash| + bhash.delete(:avail) + Puppet::Type.type(:package).installedpkg(bhash) + end + end + + # Turn our blastwave listing into a bunch of hashes. + def self.blastlist(hash) + command = "#{PKGGET} -c" + + if hash[:justme] + command += " " + hash[:justme] + end + + begin + output = execute(command) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not get package listing: %s" % + detail + end + + list = output.split("\n").collect do |line| + next if line =~ /^#/ + next if line =~ /^WARNING/ + next if line =~ /localrev\s+remoterev/ + + blastsplit(line) + end.reject { |h| h.nil? } + + if hash[:justme] + return list[0] + else + list.reject! { |h| + h[:ensure] == :absent + } + return list + end + + end + + # Split the different lines into hashes. + def self.blastsplit(line) + if line =~ /\s*(\S+)\s+((\[Not installed\])|(\S+))\s+(\S+)/ + hash = {} + hash[:name] = $1 + hash[:ensure] = if $2 == "[Not installed]" + :absent + else + $2 + end + hash[:avail] = $5 + + if hash[:avail] == "SAME" + hash[:avail] = hash[:ensure] + end + hash[:provider] = :blastwave + + return hash + else + Puppet.warning "Cannot match %s" % line + return nil + end + end + + def install + begin + execute("#{PKGGET} -f install #{@model[:name]}") + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, + "Could not install %s: %s" % + [@model[:name], detail] + end + end + + # Retrieve the version from the current package file. + def latest + hash = self.class.blastlist(:justme => @model[:name]) + hash[:avail] + end + + def query + hash = self.class.blastlist(:justme => @model[:name]) + + {:ensure => hash[:ensure]} + end + + # Remove the old package, and install the new one + def update + begin + execute("#{PKGGET} -f upgrade #{@model[:name]}") + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, + "Could not upgrade %s: %s" % + [@model[:name], detail] + end + end + + def uninstall + begin + execute("#{PKGGET} -f remove #{@model[:name]}") + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, + "Could not remove %s: %s" % + [@model[:name], detail] + end + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/darwinport.rb b/lib/puppet/provider/package/darwinport.rb new file mode 100755 index 000000000..ccf0ae82d --- /dev/null +++ b/lib/puppet/provider/package/darwinport.rb @@ -0,0 +1,95 @@ +Puppet::Type.type(:package).provide :darwinport do + desc "Package management using DarwinPorts on OS X." + + PORT = "/opt/local/bin/port" + confine :exists => PORT, :operatingsystem => "Darwin" + + def self.eachpkgashash + # list out all of the packages + open("| #{PORT} list installed") { |process| + regex = %r{(\S+)\s+@(\S+)\s+(\S+)} + fields = [:name, :version, :location] + hash = {} + + # now turn each returned line into a package object + process.each { |line| + hash.clear + + if match = regex.match(line) + fields.zip(match.captures) { |field,value| + hash[field] = value + } + + hash.delete :location + hash[:ensure] = hash[:version] + yield hash.dup + else + raise Puppet::DevError, + "Failed to match dpkg line %s" % line + end + } + } + end + + def self.list + packages = [] + + eachpkgashash do |hash| + pkg = Puppet.type(:package).installedpkg(hash) + packages << pkg + end + + return packages + end + + def install + should = @model[:ensure] + + # Seems like you can always say 'upgrade' + cmd = "#{PORT} upgrade #{@model[:name]}" + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end + + def query + version = nil + self.class.eachpkgashash do |hash| + if hash[:name] == @model[:name] + return hash + end + end + + return nil + end + + def latest + info = %x{#{PORT} search '^#{@model[:name]}$' 2>/dev/null} + + if $? != 0 or info =~ /^Error/ + return nil + end + + ary = info.split(/\s+/) + version = ary[2].sub(/^@/, '') + + return version + end + + def uninstall + cmd = "#{PORT} uninstall #{@model[:name]}" + output = %x{#{cmd} 2>&1} + if $? != 0 + raise Puppet::PackageError.new(output) + end + end + + def update + return install() + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/dpkg.rb b/lib/puppet/provider/package/dpkg.rb new file mode 100755 index 000000000..a747a884c --- /dev/null +++ b/lib/puppet/provider/package/dpkg.rb @@ -0,0 +1,115 @@ +Puppet::Type.type(:package).provide :dpkg do + desc "Package management via ``dpkg``. Because this only uses ``dpkg`` + and not ``apt``, you must specify the source of any packages you want + to manage." + + DPKG = "/usr/bin/dpkg" + + confine :exists => DPKG + + def self.list + packages = [] + + # dpkg only prints as many columns as you have available + # which means we don't get all of the info + # stupid stupid + oldcol = ENV["COLUMNS"] + ENV["COLUMNS"] = "500" + + # list out all of the packages + open("| #{DPKG} -l") { |process| + # our regex for matching dpkg output + regex = %r{^(\S+)\s+(\S+)\s+(\S+)\s+(.+)$} + fields = [:status, :name, :version, :description] + hash = {} + + 5.times { process.gets } # throw away the header + + # now turn each returned line into a package object + process.each { |line| + if match = regex.match(line) + hash.clear + + fields.zip(match.captures) { |field,value| + hash[field] = value + } + + hash[:provider] = self.name + + packages.push Puppet.type(:package).installedpkg(hash) + else + raise Puppet::DevError, + "Failed to match dpkg line %s" % line + end + } + } + ENV["COLUMNS"] = oldcol + + return packages + end + + def query + packages = [] + + # dpkg only prints as many columns as you have available + # which means we don't get all of the info + # stupid stupid + oldcol = ENV["COLUMNS"] + ENV["COLUMNS"] = "500" + fields = [:desired, :status, :error, :name, :version, :description] + + hash = {} + # list out our specific package + open("| #{DPKG} -l %s 2>/dev/null" % @model[:name]) { |process| + # our regex for matching dpkg output + regex = %r{^(.)(.)(.)\s(\S+)\s+(\S+)\s+(.+)$} + + # we only want the last line + lines = process.readlines + # we've got four header lines, so we should expect all of those + # plus our output + if lines.length < 5 + return nil + end + + line = lines[-1] + + if match = regex.match(line) + fields.zip(match.captures) { |field,value| + hash[field] = value + } + #packages.push Puppet.type(:package).installedpkg(hash) + else + raise Puppet::DevError, + "failed to match dpkg line %s" % line + end + } + ENV["COLUMNS"] = oldcol + + if hash[:error] != " " + raise Puppet::PackageError.new( + "Package %s, version %s is in error state: %s" % + [hash[:name], hash[:version], hash[:error]] + ) + end + + if hash[:status] == "i" + hash[:ensure] = :present + else + hash[:ensure] = :absent + end + + return hash + end + + def uninstall + cmd = "#{DPKG} -r %s" % @model[:name] + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/freebsd.rb b/lib/puppet/provider/package/freebsd.rb new file mode 100755 index 000000000..cd484d47a --- /dev/null +++ b/lib/puppet/provider/package/freebsd.rb @@ -0,0 +1,52 @@ +Puppet::Type.type(:package).provide :freebsd, :parent => :openbsd do + desc "The specific form of package management on FreeBSD. This is an + extremely quirky packaging system, in that it freely mixes between + ports and packages. Apparently all of the tools are written in Ruby, + so there are plans to rewrite this support to directly use those + libraries." + + commands :info => "/usr/sbin/pkg_info", + :add => "/usr/sbin/pkg_add", + :delete => "/usr/sbin/pkg_delete" + + def self.listcmd + command(:info) + end + + def install + should = @model[:ensure] + + if @model[:source] + return super + end + + cmd = command(:add) + " -r " + @model[:name] + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end + + def query + self.class.list + + if @model[:version] + return :listed + else + return nil + end + end + + def uninstall + cmd = "#{command(:delete)} %s-%s" % [@model[:name], @model[:version]] + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb new file mode 100755 index 000000000..fba8b4702 --- /dev/null +++ b/lib/puppet/provider/package/gem.rb @@ -0,0 +1,108 @@ +# Ruby gems support. +Puppet::Type.type(:package).provide :gem do + desc "Ruby Gem support. By default uses remote gems, but you can specify + the path to a local gem via ``source``." + GEM = binary("gem") + + confine :exists => GEM + + def self.gemlist(hash) + command = "#{GEM} list " + + if hash[:local] + command += "--local " + else + command += "--remote " + end + + if name = hash[:justme] + command += name + end + begin + list = execute(command).split("\n\n").collect do |set| + if gemhash = gemsplit(set) + gemhash[:provider] = :gem + gemhash[:ensure] = gemhash[:version][0] + gemhash + else + nil + end + end.reject { |p| p.nil? } + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not list gems: %s" % detail + end + + if hash[:justme] + return list.shift + else + return list + end + end + + def self.gemsplit(desc) + case desc + when /^\*\*\*/: return nil + when /^(\S+)\s+\((.+)\)\n/ + name = $1 + version = $2.split(/,\s*/) + return { + :name => name, + :version => version + } + else + Puppet.warning "Could not match %s" % desc + nil + end + end + + def self.list(justme = false) + gemlist(:local => true).each do |hash| + Puppet::Type.type(:package).installedpkg(hash) + end + end + + def install(useversion = true) + command = "#{GEM} install " + if @model[:version] and useversion + command += "-v %s " % @model[:version] + end + if source = @model[:source] + command += source + else + command += @model[:name] + end + begin + execute(command) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not install %s: %s" % + [@model[:name], detail] + end + end + + def latest + # This always gets the latest version available. + hash = self.class.gemlist(:justme => @model[:name]) + + return hash[:version][0] + end + + def query + self.class.gemlist(:justme => @model[:name], :local => true) + end + + def uninstall + begin + # Remove everything, including the binaries. + execute("#{GEM} uninstall -x -a #{@model[:name]}") + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not uninstall %s: %s" % + [@model[:name], detail] + end + end + + def update + self.install(false) + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/openbsd.rb b/lib/puppet/provider/package/openbsd.rb new file mode 100755 index 000000000..05dee8656 --- /dev/null +++ b/lib/puppet/provider/package/openbsd.rb @@ -0,0 +1,110 @@ +# Packaging on OpenBSD. Doesn't work anywhere else that I know of. +Puppet::Type.type(:package).provide :openbsd do + desc "OpenBSD's form of ``pkg_add`` support." + + commands :info => "pkg_info", :add => "pkg_add", :delete => "pkg_delete" + + defaultfor :operatingsystem => :openbsd + + def self.list + packages = [] + + begin + execpipe(listcmd()) do |process| + # our regex for matching pkg_info output + regex = %r{^(\S+)-([^-\s]+)\s+(.+)} + fields = [:name, :version, :description] + hash = {} + + # now turn each returned line into a package object + process.each { |line| + hash.clear + if match = regex.match(line) + fields.zip(match.captures) { |field,value| + hash[field] = value + } + yup = nil + name = hash[:name] + hash[:ensure] = :present + + hash[:provider] = self.name + + pkg = Puppet.type(:package).installedpkg(hash) + packages << pkg + else + # Print a warning on lines we can't match, but move + # on, since it should be non-fatal + warning("Failed to match line %s" % line) + end + } + end + # Mark as absent any packages we didn't find + Puppet.type(:package).each do |pkg| + unless packages.include? pkg + pkg.is = [:ensure, :absent] + end + end + + return packages + rescue Puppet::ExecutionFailure + return nil + end + end + + def self.listcmd + "#{command(:info)} -a" + end + + def install + should = @model[:ensure] + + unless @model[:source] + raise Puppet::Error, + "You must specify a package source for BSD packages" + end + + cmd = command(:add) + " " + @model[:source] + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end + + def query + hash = {} + begin + # list out our specific package + info = execute("#{command(:info)} #{@model[:name]}") + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(info) + end + + # Search for the version info + if info =~ /Information for #{@model[:name]}-(\S+)/ + hash[:version] = $1 + hash[:ensure] = :present + else + return nil + end + + # And the description + if info =~ /Comment:\s*\n(.+)/ + hash[:description] = $1 + end + + return hash + end + + def uninstall + cmd = "#{command(:delete)} %s" % @model[:name] + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/ports.rb b/lib/puppet/provider/package/ports.rb new file mode 100755 index 000000000..6a46dcc03 --- /dev/null +++ b/lib/puppet/provider/package/ports.rb @@ -0,0 +1,104 @@ +Puppet::Type.type(:package).provide :ports, :parent => :freebsd do + desc "Support for FreeBSD's ports. Again, this still mixes packages + and ports." + commands :upgrade => "/usr/local/sbin/portupgrade", + :version => "/usr/local/sbin/portversion", + :uninstall => "/usr/local/sbin/pkg_deinstall", + :info => "/usr/sbin/pkg_info" + + defaultfor :operatingsystem => :freebsd + + # I hate ports + %w{INTERACTIVE UNAME}.each do |var| + if ENV.include?(var) + ENV.delete(var) + end + end + + def install + # -p: create a package + # -N: install if the package is missing, otherwise upgrade + # -P: prefer binary packages + cmd = "#{command(:upgrade)} -p -N -P #{@model[:name]}" + + begin + output = execute(cmd) + if output =~ /\*\* No such / + raise Puppet::PackageError, "Could not find package %s" % @model[:name] + end + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + end + + # If there are multiple packages, we only use the last one + def latest + cmd = "#{command(:version)} -v #{@model[:name]}" + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + line = output.split("\n").pop + + unless line =~ /^(\S+)\s+(\S)\s+(.+)$/ + # There's no "latest" version, so just return a placeholder + return :latest + end + + pkgstuff = $1 + match = $2 + info = $3 + + unless pkgstuff =~ /^(\w+)-([0-9].+)$/ + raise Puppet::PackageError, + "Could not match package info '%s'" % pkgstuff + end + + name, version = $1, $2 + + if match == "=" or match == ">" + # we're up to date or more recent + return version + end + + # Else, we need to be updated; we need to pull out the new version + + unless info =~ /\((\w+) has (.+)\)/ + raise Puppet::PackageError, + "Could not match version info '%s'" % info + end + + source, newversion = $1, $2 + + debug "Newer version in %s" % source + return newversion + end + + def query + self.class.list + + if @model[:version] and @model.is(:ensure) != :absent + return :listed + else + return nil + end + end + + def uninstall + cmd = "#{command(:uninstall)} #{@model[:name]}" + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::PackageError.new(output) + end + + end + + def update + install() + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb new file mode 100755 index 000000000..25ae0385b --- /dev/null +++ b/lib/puppet/provider/package/rpm.rb @@ -0,0 +1,120 @@ +# RPM packaging. Should work anywhere that has rpm installed. +Puppet::Type.type(:package).provide :rpm do + desc "RPM packaging support; should work anywhere with a working ``rpm`` + binary." + + VERSIONSTRING = "%{VERSION}-%{RELEASE}" + + commands :rpm => "rpm" + + def self.list + packages = [] + + # list out all of the packages + begin + execpipe("#{command(:rpm)} -q -a --qf '%{NAME} #{VERSIONSTRING}\n'") { |process| + # our regex for matching dpkg output + regex = %r{^(\S+)\s+(\S+)} + fields = [:name, :ensure] + hash = {} + + # now turn each returned line into a package object + process.each { |line| + if match = regex.match(line) + hash.clear + + fields.zip(match.captures) { |field,value| + hash[field] = value + } + hash[:provider] = self.name + packages.push Puppet.type(:package).installedpkg(hash) + else + raise "failed to match rpm line %s" % line + end + } + } + rescue Puppet::ExecutionFailure + raise Puppet::Error, "Failed to list packages" + end + + return packages + end + + def query + fields = { + :name => "NAME", + :version => "VERSION", + :description => "DESCRIPTION" + } + + cmd = "#{command(:rpm)} -q #{@model[:name]} --qf '%s\n'" % + "%{NAME} #{VERSIONSTRING}" + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + return nil + end + + regex = %r{^(\S+)\s+(\S+)} + #fields = [:name, :ensure, :description] + fields = [:name, :version] + hash = {} + if match = regex.match(output) + fields.zip(match.captures) { |field,value| + hash[field] = value + } + else + raise Puppet::DevError, + "Failed to match rpm output '%s'" % + output + end + + hash[:ensure] = :present + + return hash + end + + # Here we just retrieve the version from the file specified in the source. + def latest + unless source = @model[:source] + @model.fail "RPMs must specify a package source" + end + + cmd = "#{command(:rpm)} -q --qf '#{VERSIONSTRING}' -p #{@model[:source]}" + version = execfail(cmd, Puppet::Error) + + return version + end + + def install + source = nil + unless source = @model[:source] + @model.fail "RPMs must specify a package source" + end + + flag = "-i" + if @model.is(:ensure) != :absent + flag = "-U" + end + output = %x{#{command(:rpm)} #{flag} #{source} 2>&1} + + unless $? == 0 + raise Puppet::PackageError.new(output) + end + end + + def uninstall + cmd = "#{command(:rpm)} -e %s" % @model[:name] + output = %x{#{cmd}} + if $? != 0 + raise output + end + end + + def update + self.install + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/sun.rb b/lib/puppet/provider/package/sun.rb new file mode 100755 index 000000000..590e8f84a --- /dev/null +++ b/lib/puppet/provider/package/sun.rb @@ -0,0 +1,179 @@ +# Sun packaging. No one else uses these package tools, AFAIK. + +Puppet::Type.type(:package).provide :sun do + desc "Sun's packaging system. Requires that you specify the source for + the packages you're managing." + commands :info => "/usr/bin/pkginfo", + :add => "/usr/sbin/pkgadd", + :rm => "/usr/sbin/pkgrm" + + defaultfor :operatingsystem => :solaris + + def self.list + packages = [] + hash = {} + names = { + "PKGINST" => :name, + "NAME" => nil, + "CATEGORY" => :category, + "ARCH" => :platform, + "VERSION" => :ensure, + "BASEDIR" => :root, + "HOTLINE" => nil, + "EMAIL" => nil, + "VENDOR" => :vendor, + "DESC" => :description, + "PSTAMP" => nil, + "INSTDATE" => nil, + "STATUS" => nil, + "FILES" => nil + } + + cmd = "#{command(:info)} -l" + + # list out all of the packages + execpipe(cmd) { |process| + # we're using the long listing, so each line is a separate + # piece of information + process.each { |line| + case line + when /^$/: + hash[:provider] = :sun + + packages.push Puppet.type(:package).installedpkg(hash) + hash.clear + when /\s*(\w+):\s+(.+)/: + name = $1 + value = $2 + if names.include?(name) + unless names[name].nil? + hash[names[name]] = value + end + else + raise "Could not find %s" % name + end + when /\s+\d+.+/: + # nothing; we're ignoring the FILES info + end + } + } + return packages + end + + # Get info on a package, optionally specifying a device. + def info2hash(device = nil) + names = { + "PKGINST" => :name, + "NAME" => nil, + "CATEGORY" => :category, + "ARCH" => :platform, + "VERSION" => :ensure, + "BASEDIR" => :root, + "HOTLINE" => nil, + "EMAIL" => nil, + "VSTOCK" => nil, + "VENDOR" => :vendor, + "DESC" => :description, + "PSTAMP" => nil, + "INSTDATE" => nil, + "STATUS" => nil, + "FILES" => nil + } + + hash = {} + cmd = "#{command(:info)} -l" + if device + cmd += " -d #{device}" + end + cmd += " #{@model[:name]}" + + begin + # list out all of the packages + execpipe(cmd) { |process| + # we're using the long listing, so each line is a separate + # piece of information + process.each { |line| + case line + when /^$/: # ignore + when /\s*([A-Z]+):\s+(.+)/: + name = $1 + value = $2 + if names.include?(name) + unless names[name].nil? + hash[names[name]] = value + end + end + when /\s+\d+.+/: + # nothing; we're ignoring the FILES info + end + } + } + return hash + rescue Puppet::ExecutionFailure + return nil + end + end + + def install + unless @model[:source] + raise Puppet::Error, "Sun packages must specify a package source" + end + cmd = [command(:add)] + + if @model[:adminfile] + cmd << " -a " + @model[:adminfile] + end + + if @model[:responsefile] + cmd << " -r " + @model[:responsefile] + end + + cmd += ["-d", @model[:source]] + cmd += ["-n", @model[:name]] + cmd = cmd.join(" ") + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::PackageError.new(output) + end + end + + # Retrieve the version from the current package file. + def latest + hash = info2hash(@model[:source]) + hash[:ensure] + end + + def query + info2hash() + end + + def uninstall + command = "#{command(:rm)} -n " + + if @model[:adminfile] + command += " -a " + @model[:adminfile] + end + + command += " " + @model[:name] + begin + execute(command) + rescue ExecutionFailure => detail + raise Puppet::Error, + "Could not uninstall %s: %s" % + [@model[:name], detail] + end + end + + # Remove the old package, and install the new one. This will probably + # often fail. + def update + if @model.is(:ensure) != :absent + self.uninstall + end + self.install + end +end + +# $Id$ diff --git a/lib/puppet/provider/package/sunfreeware.rb b/lib/puppet/provider/package/sunfreeware.rb new file mode 100755 index 000000000..e6c3870f7 --- /dev/null +++ b/lib/puppet/provider/package/sunfreeware.rb @@ -0,0 +1,15 @@ +# At this point, it's an exact copy of the Blastwave stuff. +Puppet::Type.type(:package).provide :sunfreeware, :parent => :blastwave do + desc "Package management using sunfreeware.com's ``pkg-get`` command on Solaris. + At this point, support is exactly the same as ``blastwave`` support and + has not actually been tested." + if pkgget = binary("pkg-get") + PKGGET = pkgget + else + PKGGET = nil + end + + confine :exists => PKGGET +end + +# $Id$ diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb new file mode 100755 index 000000000..587b1911e --- /dev/null +++ b/lib/puppet/provider/package/yum.rb @@ -0,0 +1,53 @@ +Puppet::Type.type(:package).provide :yum, :parent => :rpm do + desc "Support via ``yum``." + commands :yum => "yum", :rpm => "rpm" + + defaultfor :operatingsystem => :fedora + + # Install a package using 'yum'. + def install + cmd = "#{command(:yum)} -y install %s" % @model[:name] + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::PackageError.new(detail) + end + + unless self.query + raise Puppet::PackageError.new( + "Could not find package %s" % self.name + ) + end + end + + # What's the latest package version available? + def latest + cmd = "#{command(:yum)} list available %s" % @model[:name] + + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::PackageError.new(detail) + end + + if output =~ /#{@model[:name]}\S+\s+(\S+)\s/ + return $1 + else + # Yum didn't find updates, pretend the current + # version is the latest + return @model[:version] + end + end + + def update + # Install in yum can be used for update, too + self.install + end + + def versionable? + false + end +end + +# $Id$ diff --git a/lib/puppet/provider/service/base.rb b/lib/puppet/provider/service/base.rb new file mode 100755 index 000000000..eedcac4ae --- /dev/null +++ b/lib/puppet/provider/service/base.rb @@ -0,0 +1,135 @@ +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``." + + # Execute a command. Basically just makes sure it exits with a 0 + # code. + def execute(type, cmd) + self.debug "Executing %s" % cmd.inspect + output = %x(#{cmd} 2>&1) + unless $? == 0 + @model.fail "Could not %s %s: %s" % + [type, self.name, output.chomp] + end + end + + # Get the process ID for a running process. Requires the 'pattern' + # parameter. + def getpid + unless @model[:pattern] + @model.fail "Either a stop command or a pattern must be specified" + end + ps = Facter["ps"].value + warning ps.inspect + unless ps and ps != "" + @model.fail( + "You must upgrade Facter to a version that includes 'ps'" + ) + end + regex = Regexp.new(@model[: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 + + # Basically just a synonym for restarting. Used to respond + # to events. + def refresh + self.restart + end + + # How to restart the process. + def restart + if @model[:restart] or self.respond_to?(:restartcmd) + cmd = @model[:restart] || self.restartcmd + self.execute("restart", cmd) + 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 @model[:status] or ( + self.respond_to?(:statuscmd) and self.statuscmd + ) + cmd = @model[:status] || self.statuscmd + self.debug "Executing %s" % cmd.inspect + output = %x(#{cmd} 2>&1) + self.debug "%s status returned %s" % + [self.name, output.inspect] + 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 + cmd = @model[:start] || self.startcmd + self.execute("start", cmd) + end + + # The command used to start. Generated if the 'binary' argument + # is passed. + def startcmd + if @model[:binary] + return @model[: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 @model[:stop] + return @model[:stop] + elsif self.respond_to?(:stopcmd) + self.execute("stop", self.stopcmd) + else + pid = getpid + unless pid + self.info "%s is not running" % self.name + return false + end + output = %x(kill #{pid} 2>&1) + if $? != 0 + @model.fail "Could not kill %s, PID %s: %s" % + [self.name, pid, output] + end + return true + end + end +end + +# $Id$ diff --git a/lib/puppet/type/service/debian.rb b/lib/puppet/provider/service/debian.rb index 88fa598bf..b8acbf326 100755 --- a/lib/puppet/type/service/debian.rb +++ b/lib/puppet/provider/service/debian.rb @@ -1,11 +1,15 @@ -require 'puppet/type/service/init' - # Manage debian services. Start/stop is the same as InitSvc, but enable/disable # is special. -Puppet.type(:service).newsvctype(:debian, :init) do +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``." + + commands :update => "/usr/sbin/update-rc.d" + defaultfor :operatingsystem => :debian + # Remove the symlinks def disable - cmd = %{update-rc.d -f #{self[:name]} remove 2>&1} + cmd = %{#{command(:update)} -f #{@model[:name]} remove 2>&1} self.debug "Executing '%s'" % cmd output = %x{#{cmd}} @@ -16,7 +20,7 @@ Puppet.type(:service).newsvctype(:debian, :init) do end def enabled? - cmd = %{update-rc.d -n -f #{self[:name]} remove 2>&1} + cmd = %{#{command(:update)} -n -f #{@model[:name]} remove 2>&1} self.debug "Executing 'enabled' test: '%s'" % cmd output = %x{#{cmd}} unless $? == 0 @@ -34,7 +38,7 @@ Puppet.type(:service).newsvctype(:debian, :init) do end def enable - cmd = %{update-rc.d #{self[:name]} defaults 2>&1} + cmd = %{#{command(:update)} #{@model[:name]} defaults 2>&1} self.debug "Executing '%s'" % cmd output = %x{#{cmd}} @@ -44,3 +48,5 @@ Puppet.type(:service).newsvctype(:debian, :init) do end end end + +# $Id$ diff --git a/lib/puppet/type/service/init.rb b/lib/puppet/provider/service/init.rb index 4a9f52a31..76cac2763 100755 --- a/lib/puppet/type/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -1,23 +1,34 @@ # The standard init-based service type. Many other service types are # customizations of this module. -Puppet.type(:service).newsvctype(:init) do - def self.defpath - case Facter["operatingsystem"].value - when "FreeBSD": - "/etc/rc.d" - else - "/etc/init.d" - end +Puppet::Type.type(:service).provide :init, :parent => :base do + desc "Standard init service management. This provider assumes that the + init script has not ``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" + else + @defpath = "/etc/init.d" end - Puppet.type(:service).newpath(:init, defpath()) + #confine :exists => @defpath + + if self.suitable? + # Add it to the search paths + Puppet.type(:service).newpath(:init, defpath()) - # Set the default init directory. - Puppet.type(:service).attrclass(:path).defaultto defpath() + # Set the default init directory. + Puppet.type(:service).attrclass(:path).defaultto defpath() + end - # List all services of this type. This has to be an instance method - # so that it's inherited by submodules. - def list(name) + # List all services of this type. + def self.list(name) # We need to find all paths specified for our type or any parent types paths = Puppet.type(:service).paths(name) @@ -94,12 +105,12 @@ Puppet.type(:service).newsvctype(:init) do if defined? @initscript return @initscript else - @initscript = self.search(self[:name]) + @initscript = self.search(@model[:name]) end end def search(name) - self[:path].each { |path| + @model[:path].each { |path| fqname = File.join(path,name) begin stat = File.stat(fqname) @@ -124,7 +135,7 @@ Puppet.type(:service).newsvctype(:init) do # we just return that; otherwise, we return false, which causes it to # fallback to other mechanisms. def statuscmd - if self[:hasstatus] + if @model[:hasstatus] return self.initscript + " status" else return false diff --git a/lib/puppet/type/service/redhat.rb b/lib/puppet/provider/service/redhat.rb index 24272cd0b..e599e63f1 100755 --- a/lib/puppet/type/service/redhat.rb +++ b/lib/puppet/provider/service/redhat.rb @@ -1,13 +1,18 @@ -require 'puppet/type/service/init' - # Manage debian services. Start/stop is the same as InitSvc, but enable/disable # is special. -Puppet.type(:service).newsvctype(:redhat, :init) do +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." + + confine :exists => "/sbin/chkconfig" + + defaultfor :operatingsystem => [:redhat, :fedora] + # Remove the symlinks def disable begin - output = util_execute("/sbin/chkconfig #{self[:name]} off 2>&1") - output += util_execute("/sbin/chkconfig --del #{self[:name]} 2>&1") + output = util_execute("/sbin/chkconfig #{@model[:name]} off 2>&1") + output += util_execute("/sbin/chkconfig --del #{@model[:name]} 2>&1") rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable %s: %s" % [self.name, output] @@ -16,7 +21,7 @@ Puppet.type(:service).newsvctype(:redhat, :init) do def enabled? begin - output = util_execute("/sbin/chkconfig #{self[:name]} 2>&1").chomp + output = util_execute("/sbin/chkconfig #{@model[:name]} 2>&1").chomp rescue Puppet::ExecutionFailure return :false end @@ -28,11 +33,13 @@ Puppet.type(:service).newsvctype(:redhat, :init) do # in the init scripts. def enable begin - output = util_execute("/sbin/chkconfig --add #{self[:name]} 2>&1") - output += util_execute("/sbin/chkconfig #{self[:name]} on 2>&1") + output = util_execute("/sbin/chkconfig --add #{@model[:name]} 2>&1") + output += util_execute("/sbin/chkconfig #{@model[:name]} on 2>&1") rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not enable %s: %s" % [self.name, output] end end end + +# $Id$ diff --git a/lib/puppet/type/service/smf.rb b/lib/puppet/provider/service/smf.rb index 71dc22ff4..becd6fa3f 100755 --- a/lib/puppet/type/service/smf.rb +++ b/lib/puppet/provider/service/smf.rb @@ -1,5 +1,14 @@ # Solaris 10 SMF-style services. -Puppet.type(:service).newsvctype(:smf) do +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." + + defaultfor :operatingsystem => :solaris + + commands :adm => "/usr/sbin/svcadm", :svcs => "/usr/bin/svcs" + def enable self.start end @@ -18,19 +27,26 @@ Puppet.type(:service).newsvctype(:smf) do end def restartcmd - "svcadm restart %s" % self[:name] + "#{command(:adm)} restart %s" % @model[:name] end def startcmd - "svcadm enable %s" % self[:name] + "#{command(:adm)} enable %s" % @model[:name] end def status - if self[:status] + if @model[:status] super return end - %x{/usr/bin/svcs -l #{self[:name]} 2>/dev/null}.split("\n").each { |line| + begin + output = Puppet::Util.execute("#{command(:svcs)} -l #{@model[: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+(.+)/ @@ -58,16 +74,10 @@ Puppet.type(:service).newsvctype(:smf) do end end } - - if $? != 0 - #raise Puppet::Error, - warning "Could not get status on service %s" % self.name - return :stopped - end end def stopcmd - "svcadm disable %s" % self[:name] + "#{command(:adm)} disable %s" % @model[:name] end end diff --git a/lib/puppet/provider/user/netinfo.rb b/lib/puppet/provider/user/netinfo.rb new file mode 100644 index 000000000..e453c4582 --- /dev/null +++ b/lib/puppet/provider/user/netinfo.rb @@ -0,0 +1,91 @@ +# 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(:user).provide :netinfo, :parent => Puppet::Provider::NameService::NetInfo do + desc "User management in NetInfo." + + NIREPORT = binary("nireport") + NIUTIL = binary("niutil") + confine :exists => NIREPORT + confine :exists => NIUTIL + + options :comment, :key => "realname" + + defaultfor :operatingsystem => :darwin + + # The list of all groups the user is a member of. Different + # user mgmt systems will need to override this method. + def groups + groups = [] + + user = @model[:name] + # Retrieve them all from netinfo + open("| nireport / /groups name users") do |file| + file.each do |line| + name, members = line.split(/\s+/) + next unless members + next if members =~ /NoValue/ + members = members.split(",") + + if members.include? user + groups << name + end + end + end + + groups.join(",") + end + + # This is really lame. We have to iterate over each + # of the groups and add us to them. + def groups=(groups) + groups = groups.split(/\s*,\s*/) + # Get just the groups we need to modify + diff = groups - (@is || []) + + data = {} + open("| nireport / /groups name users") do |file| + file.each do |line| + name, members = line.split(/\s+/) + + if members.nil? or members =~ /NoValue/ + data[name] = [] + else + # Add each diff group's current members + data[name] = members.split(/,/) + end + end + end + + user = @model[:name] + data.each do |name, members| + if members.include? user and groups.include? name + # I'm in the group and should be + next + elsif members.include? user + # I'm in the group and shouldn't be + setuserlist(name, members - [user]) + elsif groups.include? name + # I'm not in the group and should be + setuserlist(name, members + [user]) + else + # I'm not in the group and shouldn't be + next + end + end + end + + def setuserlist(group, list) + cmd = "niutil -createprop / /groups/%s users %s" % + [group, list.join(",")] + begin + output = execute(cmd) + rescue Puppet::ExecutionFailure + raise Puppet::Error, + "Failed to set groups: %s" % output + end + end +end + +# $Id$ diff --git a/lib/puppet/provider/user/pw.rb b/lib/puppet/provider/user/pw.rb new file mode 100644 index 000000000..7342c42fe --- /dev/null +++ b/lib/puppet/provider/user/pw.rb @@ -0,0 +1,41 @@ +require 'puppet/provider/nameservice/pw' + +Puppet::Type.type(:user).provide :pw, :parent => Puppet::Provider::NameService::PW do + desc "User management via ``pw`` on FreeBSD." + + commands :pw => "pw" + + defaultfor :operatingsystem => :freebsd + + options :home, :flag => "-d", :method => :dir + options :comment, :method => :gecos + options :groups, :flag => "-G" + + verify :gid, "GID must be an integer" do |value| + value.is_a? Integer + end + + verify :groups, "Groups must be comma-separated" do |value| + value !~ /\s/ + end + + def addcmd + cmd = [command(:pw), "useradd", @model[:name]] + @model.class.validstates.each do |state| + next if name == :ensure + # the value needs to be quoted, mostly because -c might + # have spaces in it + if value = @model[state] and value != "" + cmd << flag(state) << "'%s'" % @model[state] + end + end + + if @model[:allowdupe] == :true + cmd << "-o" + end + + return cmd.join(" ") + end +end + +# $Id$ diff --git a/lib/puppet/provider/user/useradd.rb b/lib/puppet/provider/user/useradd.rb new file mode 100644 index 000000000..edd5ec6e3 --- /dev/null +++ b/lib/puppet/provider/user/useradd.rb @@ -0,0 +1,46 @@ +require 'puppet/provider/nameservice/objectadd' + +Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameService::ObjectAdd do + desc "User management via ``useradd`` and its ilk." + + commands :add => "useradd", :delete => "userdel", :modify => "usermod" + + options :home, :flag => "-d", :method => :dir + options :comment, :method => :gecos + options :groups, :flag => "-G" + + verify :gid, "GID must be an integer" do |value| + value.is_a? Integer + end + + verify :groups, "Groups must be comma-separated" do |value| + value !~ /\s/ + end + + def addcmd + cmd = [command(:add)] + @model.class.validstates.each do |state| + next if name == :ensure + # the value needs to be quoted, mostly because -c might + # have spaces in it + if value = @model[state] and value != "" + cmd << flag(state) << "'%s'" % @model[state] + end + end + # stupid fedora + case Facter["operatingsystem"].value + when "Fedora", "RedHat": + cmd << "-M" + else + end + if @model[:allowdupe] == :true + cmd << "-o" + end + + cmd << @model[:name] + + cmd.join(" ") + end +end + +# $Id$ diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 7a3b761b7..4207992a4 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -76,6 +76,9 @@ class Transaction # event if they want events = [change.forward].flatten.reject { |e| e.nil? } rescue => detail + if Puppet[:debug] + puts detail.backtrace + end change.state.err "change from %s to %s failed: %s" % [change.state.is_to_s, change.state.should_to_s, detail] @failures[child] += 1 @@ -299,7 +302,7 @@ class Transaction # but a chmod failed? how would i handle that error? dern end - collecttargets(events) + collecttargets(events) if events # Now check to see if there are any events for this child. # Kind of hackish, since going backwards goes a change at a diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 91b75e9d4..824dd33f1 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -6,11 +6,14 @@ require 'puppet/metric' require 'puppet/type/state' require 'puppet/parameter' require 'puppet/util' +require 'puppet/autoload' + # see the bottom of the file for the rest of the inclusions module Puppet # The type is unknown class UnknownTypeError < Puppet::Error; end +class UnknownProviderError < Puppet::Error; end class Type < Puppet::Element # Types (which map to elements in the languages) are entirely composed of @@ -22,6 +25,7 @@ class Type < Puppet::Element # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. attr_accessor :children + attr_accessor :provider attr_accessor :file, :line attr_reader :tags, :parent @@ -35,17 +39,6 @@ class Type < Puppet::Element end include Enumerable - - # a little fakery, since Puppet itself isn't a type - # I don't think this is used any more, now that the language can't - # call methods - #@name = :puppet - - # set it to something to silence the tests, but otherwise not used - #@namevar = :notused - - # again, silence the tests; the :notused has to be there because it's - # the namevar # class methods dealing with Type management @@ -54,27 +47,10 @@ class Type < Puppet::Element # the Type class attribute accessors class << self attr_reader :name, :states + attr_accessor :providerloader + attr_writer :defaultprovider include Enumerable - - #def inspect - # "Type(%s)" % self.name - #end - - # This class is aggregatable, meaning that instances are defined on - # one system but instantiated on another - def isaggregatable - @aggregatable = true - end - - # Is this one aggregatable? - def aggregatable? - if defined? @aggregatable - return @aggregatable - else - return false - end - end end # iterate across all of the subclasses of Type @@ -91,9 +67,9 @@ class Type < Puppet::Element # can easily call it and create their own 'ensure' values. def self.ensurable(&block) if block_given? - self.newstate(:ensure, Puppet::State::Ensure, &block) + self.newstate(:ensure, :parent => Puppet::State::Ensure, &block) else - self.newstate(:ensure, Puppet::State::Ensure) do + self.newstate(:ensure, :parent => Puppet::State::Ensure) do self.defaultvalues end end @@ -119,11 +95,16 @@ class Type < Puppet::Element @objects = Hash.new @aliases = Hash.new + @providers = Hash.new + @defaults = {} + unless defined? @parameters @parameters = [] end @validstates = {} + @parameters = [] + @paramhash = {} @paramdoc = Hash.new { |hash,key| if key.is_a?(String) @@ -161,7 +142,7 @@ class Type < Puppet::Element Puppet.info "loaded %s" % file return true rescue LoadError => detail - Puppet.info "Could not load %s: %s" % + Puppet.info "Could not load plugin %s: %s" % [file, detail] return false end @@ -223,6 +204,14 @@ class Type < Puppet::Element end end + # Now set up autoload any providers that might exist for this type. + t.providerloader = Puppet::Autoload.new(t, + "puppet/provider/#{t.name.to_s}" + ) + + # We have to load everything so that we can figure out the default type. + t.providerloader.loadall() + t end @@ -406,12 +395,15 @@ class Type < Puppet::Element def self.namevar unless defined? @namevar return nil unless defined? @parameters and ! @parameters.empty? - @namevar = @parameters.find { |name, param| + namevarparam = @parameters.find { |param| param.isnamevar? - unless param - raise Puppet::DevError, "huh? %s" % name - end - }[0].value + } + + if namevarparam + @namevar = namevarparam.name + else + raise Puppet::DevError, "No namevar for %s" % self.name + end end @namevar end @@ -425,10 +417,7 @@ class Type < Puppet::Element unless param raise Puppet::DevError, "Class %s has no param %s" % [klass, name] end - @parameters ||= [] @parameters << param - - @paramhash ||= {} @parameters.each { |p| @paramhash[name] = p } if param.isnamevar? @@ -462,6 +451,179 @@ class Type < Puppet::Element @@metaparams.each { |p| yield p.name } end + # Find the default provider. + def self.defaultprovider + unless defined? @defaultprovider and @defaultprovider + suitable = suitableprovider() + + max = 0 + suitable.each do |provider| + if provider.defaultnum > max + max = provider.defaultnum + end + end + + defaults = suitable.find_all do |provider| + provider.defaultnum == max + end + + retval = nil + if defaults.length > 1 + Puppet.warning( + "Found multiple default providers for %s: %s; using %s" % + [self.name, defaults.collect { |i| i.name.to_s }.join(", "), + defaults[0].name] + ) + retval = defaults.shift + elsif defaults.length == 1 + retval = defaults.shift + else + raise Puppet::DevError, "Could not find a default provider for %s" % + self.name + end + + @defaultprovider = retval + end + + return @defaultprovider + end + + # Retrieve a provider by name. + def self.provider(name) + name = Puppet::Util.symbolize(name) + + # If we don't have it yet, try loading it. + unless @providers.has_key?(name) + @providerloader.load(name) + end + return @providers[name] + end + + # Just list all of the providers. + def self.providers + @providers.keys + end + + def self.validprovider?(name) + name = Puppet::Util.symbolize(name) + + return (@providers.has_key?(name) && @providers[name].suitable?) + end + + # Create a new provider of a type. This method must be called + # directly on the type that it's implementing. + def self.provide(name, options = {}, &block) + name = Puppet::Util.symbolize(name) + model = self + + parent = if pname = options[:parent] + if pname.is_a? Class + pname + else + if provider = self.provider(pname) + provider + else + raise Puppet::DevError, + "Could not find parent provider %s of %s" % + [pname, name] + end + end + else + Puppet::Type::Provider + end + + self.providify + + provider = Class.new(parent) + provider.name = name + provider.model = model + provider.initvars + + const_set "Provider%s" % name.to_s.capitalize, provider + + provider.class_eval(&block) + @providers[name] = provider + + return provider + end + + # Make sure we have a :use parameter defined. Only gets called if there + # are providers. + def self.providify + return if @paramhash.has_key? :provider + newparam(:provider) do + desc "The specific backend for #{self.name.to_s} to use. You will + seldom need to specify this -- Puppet will usually discover the + appropriate provider for your platform." + + # We need to add documentation for each provider. + def self.doc + @doc + "Available providers are:\n" + @model.providers.sort { |a,b| + a.to_s <=> b.to_s + }.each { |i| + "* **%s**: %s" % [i, self.provider(i).doc] + } + end + + defaultto { @parent.class.defaultprovider.name } + + validate do |value| + value = value[0] if value.is_a? Array + if provider = @parent.class.provider(value) + unless provider.suitable? + raise ArgumentError, + "Provider '%s' is not functional on this platform" % + [value] + end + else + raise ArgumentError, "Invalid %s provider '%s'" % + [@parent.class.name, value] + end + end + + munge do |provider| + provider = provider[0] if provider.is_a? Array + if provider.is_a? String + provider = provider.intern + end + @parent.provider = provider + provider + end + end + end + + def self.unprovide(name) + puts "wtf?" + if @providers.has_key? name + p "Removing provider %s" % name + if @defaultprovider and @defaultprovider.name == name + @defaultprovider = nil + end + @providers.delete(name) + else + p name + p @providers + end + end + + # Return an array of all of the suitable providers. + def self.suitableprovider + @providers.find_all { |name, provider| + provider.suitable? + }.collect { |name, provider| + provider + } + end + + def provider=(name) + if klass = self.class.provider(name) + @provider = klass.new(self) + else + raise UnknownProviderError, "Could not find %s provider of %s" % + [name, self.class.name] + end + end + # Create a new parameter. Requires a block and a name, stores it in the # @parameters array, and does some basic checking on it. def self.newparam(name, &block) @@ -475,10 +637,7 @@ class Type < Puppet::Element param.element = self param.class_eval(&block) const_set("Parameter" + name.to_s.capitalize,param) - @parameters ||= [] @parameters << param - - @paramhash ||= {} @parameters.each { |p| @paramhash[name] = p } # These might be enabled later. @@ -497,9 +656,22 @@ class Type < Puppet::Element return param end - # Create a new state. - def self.newstate(name, parent = nil, &block) - parent ||= Puppet::State + # Create a new state. The first parameter must be the name of the state; + # this is how users will refer to the state when creating new instances. + # The second parameter is a hash of options; the options are: + # * <tt>:parent</tt>: The parent class for the state. Defaults to Puppet::State. + # * <tt>:retrieve</tt>: The method to call on the provider or @parent object (if + # the provider is not set) to retrieve the current value. + def self.newstate(name, options = {}, &block) + name = symbolize(name) + + # This is here for types that might still have the old method of defining + # a parent class. + unless options.is_a? Hash + raise Puppet::DevError, "Options must be a hash, not %s" % options.inspect + end + + parent = options[:parent] || Puppet::State if @validstates.include?(name) raise Puppet::DevError, "Class %s already has a state named %s" % [self.name, name] @@ -508,10 +680,22 @@ class Type < Puppet::Element @name = name end + # If they've passed a retrieve method, then override the retrieve method + # on the class. + if options[:retrieve] + s.send(:define_method, :retrieve) do + instance_variable_set( + "@is", provider.send(options[:retrieve]) + ) + end + end + s.initvars const_set("State" + name.to_s.capitalize,s) - s.class_eval(&block) + if block_given? + s.class_eval(&block) + end @states ||= [] # If it's the 'ensure' state, always put it first. @@ -598,6 +782,7 @@ class Type < Puppet::Element # does the name reflect a valid state? def self.validstate?(name) + name = name.intern if name.is_a? String unless @validstates.length == @states.length self.buildstatehash end @@ -1710,21 +1895,6 @@ class Type < Puppet::Element end } - # if they're not using :name for the namevar but we got :name (probably - # from the parser) -# if namevar != :name and hash.include?(:name) and ! hash[:name].nil? -# #self[namevar] = hash[:name] -# hash[namevar] = hash[:name] -# hash.delete(:name) -# # else if we got the namevar -# elsif hash.has_key?(namevar) and ! hash[namevar].nil? -# #self[namevar] = hash[namevar] -# #hash.delete(namevar) -# # else something's screwy -# else -# # they didn't specify anything related to names -# end - return hash end @@ -2423,6 +2593,7 @@ end # Puppet::Type end require 'puppet/statechange' +require 'puppet/provider' require 'puppet/type/component' require 'puppet/type/cron' require 'puppet/type/exec' diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb index 10c362848..03ac24d29 100755 --- a/lib/puppet/type/cron.rb +++ b/lib/puppet/type/cron.rb @@ -78,10 +78,6 @@ module Puppet def should_to_s if @should - unless @should.is_a?(Array) - fail "wtf?" - end - if self.name == :command or @should[0].is_a? Symbol @should[0] else @@ -158,9 +154,9 @@ module Puppet # Override 'newstate' so that all states default to having the # correct parent type - def self.newstate(name, parent = nil, &block) - parent ||= Puppet::State::CronParam - super(name, parent, &block) + def self.newstate(name, options = {}, &block) + options[:parent] ||= Puppet::State::CronParam + super(name, options, &block) end # Somewhat uniquely, this state does not actually change anything -- it @@ -170,7 +166,7 @@ module Puppet # # Note that this means that managing many cron jobs for a given user # could currently result in multiple write sessions for that user. - newstate(:command, CronParam) do + newstate(:command, :parent => CronParam) do desc "The command to execute in the cron job. The environment provided to the command varies by local system rules, and it is best to always provide a fully qualified command. The user's @@ -193,7 +189,7 @@ module Puppet end end - newstate(:special, Puppet::State::ParsedParam) do + newstate(:special, :parent => Puppet::State::ParsedParam) do desc "Special schedules only supported on FreeBSD." def specials @@ -208,19 +204,19 @@ module Puppet end end - newstate(:minute, CronParam) do + newstate(:minute) do self.boundaries = [0, 59] desc "The minute at which to run the cron job. Optional; if specified, must be between 0 and 59, inclusive." end - newstate(:hour, CronParam) do + newstate(:hour) do self.boundaries = [0, 23] desc "The hour at which to run the cron job. Optional; if specified, must be between 0 and 23, inclusive." end - newstate(:weekday, CronParam) do + newstate(:weekday) do def alpha %w{sunday monday tuesday wednesday thursday friday saturday} end @@ -230,7 +226,7 @@ module Puppet 0 being Sunday, or must be the name of the day (e.g., Tuesday)." end - newstate(:month, CronParam) do + newstate(:month) do def alpha %w{january february march april may june july august september october november december} @@ -240,13 +236,13 @@ module Puppet must be between 1 and 12 or the month name (e.g., December)." end - newstate(:monthday, CronParam) do + newstate(:monthday) do self.boundaries = [1, 31] desc "The day of the month on which to run the command. Optional; if specified, must be between 1 and 31." end - newstate(:environment, Puppet::State::ParsedParam) do + newstate(:environment, :parent => Puppet::State::ParsedParam) do desc "Any environment settings associated with this cron job. They will be stored between the header and the job in the crontab. There can be no guarantees that other, earlier settings will not also @@ -462,8 +458,8 @@ module Puppet def self.list # Look for cron jobs for each user - Puppet::Type.type(:user).list.each { |user| - self.retrieve(user.name) + Puppet::Type.type(:user).list_by_name.each { |user| + self.retrieve(user, false) } self.collect { |c| c } @@ -594,12 +590,14 @@ module Puppet # Retrieve a given user's cron job, using the @filetype's +retrieve+ # method. Returns nil if there was no cron job; else, returns the # number of cron instances found. - def self.retrieve(user) - # First make sure the user exists - begin - Puppet::Util.uid(user) - rescue ArgumentError - raise Puppet::Error, "User %s not found" % user + def self.retrieve(user, checkuser = true) + # First make sure the user exists, unless told not to + if checkuser + begin + Puppet::Util.uid(user) + rescue ArgumentError + raise Puppet::Error, "User %s not found" % user + end end @tabs[user] ||= @filetype.new(user) diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index b2c5161cf..b03f57907 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -12,7 +12,7 @@ require 'puppet/type/state' require 'puppet/type/nameservice' module Puppet - newtype(:group, Puppet::Type::NSSType) do + newtype(:group) do @doc = "Manage groups. This type can only create groups. Group membership must be managed on individual users. This element type uses the prescribed native tools for creating groups and generally @@ -23,32 +23,23 @@ module Puppet for Mac OS X, NetInfo is used. This is currently unconfigurable, but if you desperately need it to be so, please contact us." - case Facter["operatingsystem"].value - when "Darwin": - @parentstate = Puppet::NameService::NetInfo::NetInfoState - @parentmodule = Puppet::NameService::NetInfo - when "FreeBSD": - @parentstate = Puppet::NameService::PW::PWGroup - @parentmodule = Puppet::NameService::PW - else - @parentstate = Puppet::NameService::ObjectAdd::ObjectAddGroup - @parentmodule = Puppet::NameService::ObjectAdd - end + newstate(:ensure) do + desc "The basic state that the object should be in." - newstate(:ensure, @parentstate) do newvalue(:present) do - self.syncname(:present) + provider.create + + :group_created end newvalue(:absent) do - self.syncname(:absent) - end + provider.delete - desc "The basic state that the object should be in." + :group_removed + end # If they're talking about the thing at all, they generally want to # say it should exist. - #defaultto :present defaultto do if @parent.managed? :present @@ -77,7 +68,7 @@ module Puppet end def retrieve - if @parent.exists? + if provider.exists? @is = :present else @is = :absent @@ -104,7 +95,7 @@ module Puppet end - newstate(:gid, @parentstate) do + newstate(:gid) do desc "The group ID. Must be specified numerically. If not specified, a number will be picked, which can result in ID differences across systems and thus is not recommended. The @@ -131,6 +122,19 @@ module Puppet return @@prevauto end + def retrieve + @is = provider.gid + end + + def sync + if self.should == :absent + raise Puppet::DevError, "GID cannot be deleted" + else + provider.gid = self.should + :group_modified + end + end + munge do |gid| case gid when String @@ -140,28 +144,15 @@ module Puppet self.fail "Invalid GID %s" % gid end when Symbol - unless gid == :auto or gid == :absent + unless gid == :absent self.devfail "Invalid GID %s" % gid end - if gid == :auto - # FIXME this should be done at sync time, not - # here. - unless self.class.autogen? - gid = autogen() - end - end end return gid end end - class << self - attr_accessor :netinfodir - end - - @netinfodir = "groups" - newparam(:name) do desc "The group name. While naming limitations vary by system, it is advisable to keep the name to the degenerate @@ -172,7 +163,8 @@ module Puppet end newparam(:allowdupe) do - desc "Whether to allow duplicate GIDs." + desc "Whether to allow duplicate GIDs. This option does not work on + FreeBSD (contract to the ``pw`` man page)." newvalues(:true, :false) @@ -190,35 +182,14 @@ module Puppet return groups end - def exists? - self.class.parentmodule.exists?(self) - end - - def getinfo(refresh = false) - if @groupinfo.nil? or refresh == true - begin - @groupinfo = Etc.getgrnam(self[:name]) - rescue ArgumentError => detail - @groupinfo = nil - end - end - - @groupinfo - end - - def initialize(hash) - @groupinfo = nil - super - end - def retrieve - if self.exists? + if @provider.exists? super else # the group does not exist - unless @states.include?(:gid) - self[:gid] = :auto - end + #unless @states.include?(:gid) + # self[:gid] = :auto + #end @states.each { |name, state| state.is = :absent diff --git a/lib/puppet/type/nameservice.rb b/lib/puppet/type/nameservice.rb index 34a5a08e8..6db2ff083 100755 --- a/lib/puppet/type/nameservice.rb +++ b/lib/puppet/type/nameservice.rb @@ -128,33 +128,6 @@ class State end end - # The list of all groups the user is a member of. Different - # user mgmt systems will need to override this method. - def grouplist - groups = [] - - # Reset our group list - Etc.setgrent - - user = @parent[:name] - - # Now iterate across all of the groups, adding each one our - # user is a member of - while group = Etc.getgrent - members = group.mem - - if members.include? user - groups << group.name - end - end - - # We have to close the file, so each listing is a separate - # reading of the file. - Etc.endgrent - - groups - end - # Sync the information. def sync event = nil @@ -195,57 +168,6 @@ class State return "#{@parent.class.name}_modified".intern end end - - # This is only used when creating or destroying the object. - def syncname(value) - cmd = nil - event = nil - case value - when :absent - # we need to remove the object... - unless @parent.exists? - self.info "already absent" - # the object already doesn't exist - return nil - end - - # again, needs to be set by the ind. state or its - # parent - cmd = self.deletecmd - type = "delete" - when :present - if @parent.exists? - self.info "already exists" - # The object already exists - return nil - end - - # blah blah, define elsewhere, blah blah - cmd = self.addcmd - type = "create" - end - self.debug "Executing %s" % cmd.inspect - - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::Error, "Could not %s %s %s: %s" % - [type, @parent.class.name, @parent.name, output] - end - - # we want object creation to show up as one event, - # not many - unless self.class.allatonce? - Puppet.debug "%s is not allatonce" % @parent.class.name - if type == "create" - @parent.eachstate { |state| - next if state.name == :ensure - state.sync - state.retrieve - } - end - end - end end end end diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index e75f6c288..f7ae0f0d4 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -20,97 +20,6 @@ module Puppet using the ``type`` parameter; obviously, if you specify that you want to use ``rpm`` then the ``rpm`` tools must be available." - # Create a new packaging type - def self.newpkgtype(name, parent = nil, &block) - @pkgtypes ||= {} - - if @pkgtypes.include?(name) - raise Puppet::DevError, "Package type %s already defined" % name - end - - mod = Module.new - const_set("PkgType" + name.to_s.capitalize,mod) - - # Add our parent, if it exists - if parent - unless parenttype = pkgtype(parent) - raise Puppet::DevError, - "No parent type %s for package type %s" % - [parent, name] - end - mod.send(:include, parenttype) - end - - # And now define the support methods - code = %{ - def self.name - "#{name}" - end - - def self.to_s - "PkgType(#{name})" - end - - def pkgtype - "#{name}" - end - } - - mod.module_eval(code) - - mod.module_eval(&block) - - class << mod - include Puppet::Util - end - - # It's at least conceivable that a module would not define this method - # "module_function" makes the :list method private, so if the parent - # method also called module_function, then it's already private - if mod.public_method_defined? :list or mod.private_method_defined? :list - mod.send(:module_function, :list) - end - - # Add it to our list - @pkgtypes[name] = mod - - # And mark it as a valid type - unless defined? @typeparam - @typeparam = @parameters.find { |p| p.name == :type } - - unless @typeparam - Puppet.warning @parameters.inspect - raise Puppet::DevError, "Could not package type parameter" - end - end - @typeparam.newvalues(name) - end - - # Autoload the package types, if they're not already defined. - def self.pkgtype(name) - #name = name[0] if name.is_a? Array - name = name.intern if name.is_a? String - @pkgtypes ||= {} - unless @pkgtypes.include? name - begin - require "puppet/type/package/#{name}" - - unless @pkgtypes.include? name - Puppet.warning @pkgtypes.keys - raise Puppet::DevError, - "Loaded %s but pkgtype was not created" % name.inspect - end - rescue LoadError - raise Puppet::Error, "Could not load package type %s" % name - end - end - @pkgtypes[name] - end - - def self.pkgtypes - @pkgtypes.keys - end - ensurable do desc "What state the package should be in. *latest* only makes sense for those packaging formats that can @@ -119,22 +28,22 @@ module Puppet attr_accessor :latest - newvalue(:present) do - @parent.install + newvalue(:present, :event => :package_installed) do + provider.install end - newvalue(:absent) do - @parent.uninstall + newvalue(:absent, :event => :package_removed) do + provider.uninstall end # Alias the 'present' value. aliasvalue(:installed, :present) newvalue(:latest) do - unless @parent.respond_to?(:latest) + unless provider.respond_to?(:latest) self.fail( - "Package type %s does not support specifying 'latest'" % - @parent[:type] + "Package provider %s does not support specifying 'latest'" % + @parent[:provider] ) end @@ -143,13 +52,13 @@ module Puppet # to compare against later. current = self.is begin - @parent.update + provider.update rescue => detail self.fail "Could not update: %s" % detail end if current == :absent - return :package_created + return :package_installed else return :package_changed end @@ -178,10 +87,10 @@ module Puppet return false end - unless @parent.respond_to?(:latest) + unless provider.respond_to?(:latest) self.fail( "Package type %s does not support specifying 'latest'" % - @parent[:type] + @parent[:provider] ) end @@ -190,7 +99,7 @@ module Puppet #self.debug "Skipping latest check" else begin - @latest = @parent.latest + @latest = provider.latest @lateststamp = Time.now.to_i rescue => detail self.fail "Could not get latest version: %s" % detail @@ -224,7 +133,7 @@ module Puppet # This retrieves the current state def retrieve - @parent.retrieve + @is = @parent.retrieve end def sync @@ -238,15 +147,12 @@ module Puppet super else #self.info "updating from %s" % value - @parent.update + provider.update + :package_updated end end end - # Packages are complicated because each package format has completely - # different commands. - attr_reader :pkgtype - newparam(:name) do desc "The package name. This is the name that the packaging system uses internally, which is sometimes (especially on Solaris) @@ -286,32 +192,6 @@ module Puppet isnamevar end - newparam(:type) do - desc "The package format. You will seldom need to specify this -- - Puppet will discover the appropriate format for your platform." - - defaultto { @parent.class.default } - - - validate do |value| - value = value[0] if value.is_a? Array - unless @parent.class.pkgtype(value) - raise ArgumentError, "Invalid package type '%s'" % value - end - end - - - munge do |type| - type = type[0] if type.is_a? Array - if type.is_a? String - type = type.intern - end - @parent.type2module(type) - type - end - - end - newparam(:source) do desc "From where to retrieve the package." @@ -330,6 +210,17 @@ module Puppet desc "A read-only parameter set by the package." end + newparam(:type) do + desc "Deprecated form of ``use``." + + munge do |value| + warning "'type' is deprecated; use 'use' instead" + @parent[:provider] = value + + @parent[:provider] + end + end + newparam(:adminfile) do desc "A file containing package defaults for installing packages. This is currently only used on Solaris. The value will be @@ -406,9 +297,6 @@ module Puppet @allowedmethods = [:types] - @default = nil - @platform = nil - class << self attr_reader :listed end @@ -418,53 +306,10 @@ module Puppet super end - # Cache and return the default package type for our current - # platform. - def self.default - if @default.nil? - self.init - end - - return @default - end - - # Figure out what the default package type is for the platform - # on which we're running. - def self.init - unless @platform = Facter["operatingsystem"].value.downcase - raise Puppet::DevError.new( - "Must know platform for package management" - ) - end - case @platform - when "solaris": @default = :sun - when "gentoo": - Puppet.notice "No support for gentoo yet" - @default = nil - when "debian": @default = :apt - when "centos": @default = :rpm - when "fedora": @default = :yum - when "redhat": @default = :rpm - when "freebsd": @default = :ports - when "openbsd": @default = :openbsd - when "darwin": @default = :apple - else - if Facter["kernel"] == "Linux" - Puppet.warning "Defaulting to RPM for %s" % - Facter["operatingsystem"].value - @default = :rpm - else - Puppet.warning "No default package system for %s" % - Facter["operatingsystem"].value - @default = nil - end - end - end - # Create a new package object from listed information def self.installedpkg(hash) - unless hash.include? :type - raise Puppet::DevError, "Got installed package with no type" + unless hash.include? :provider + raise Puppet::DevError, "Got installed package with no provider" end # this is from code, so we don't have to do as much checking name = hash[:name] @@ -478,7 +323,15 @@ module Puppet # List all package instances def self.list - pkgtype(default).list() + # XXX For now, just list the default provider, but we should list + # all suitables or something, but we need to not list a parent + # type if a child type gets listed. + #suitableprovider.each do |provider| + # p provider.name + # provider.list + #end + + defaultprovider.list self.collect do |pkg| pkg @@ -490,7 +343,7 @@ module Puppet def self.markabsent(pkgtype, packages) # Mark any packages we didn't find as absent self.each do |pkg| - next unless packages[:type] == pkgtype + next unless packages[:provider] == pkgtype unless packages.include? pkg pkg.is = [:ensure, :absent] end @@ -505,7 +358,7 @@ module Puppet # The 'query' method returns a hash of info if the package # exists and returns nil if it does not. def exists? - self.query + @provider.query end # okay, there are two ways that a package could be created... @@ -515,16 +368,16 @@ module Puppet def initialize(hash) self.initvars type = nil - [:type, "type"].each { |label| + [:provider, "use"].each { |label| if hash.include?(label) type = hash[label] hash.delete(label) end } if type - self[:type] = type + self[:provider] = type else - self.setdefaults(:type) + self.setdefaults(:provider) end super @@ -534,8 +387,8 @@ module Puppet # self[:ensure] = true #end - unless @parameters.include?(:type) - self[:type] = self.class.default + unless @parameters.include?(:provider) + raise Puppet::DevError, "No package type set" end end @@ -543,7 +396,7 @@ module Puppet # If the package is installed, then retrieve all of the information # about it and set it appropriately. #@states[:ensure].retrieve - if hash = self.query + if hash = @provider.query if hash == :listed # Mmmm, hackalicious return end @@ -574,17 +427,6 @@ module Puppet end } end - - # Extend the package with the appropriate package type. - def type2module(typename) - if type = self.class.pkgtype(typename) - self.extend(type) - - return type - else - self.fail "Invalid package type %s" % typename - end - end end # Puppet.type(:package) end diff --git a/lib/puppet/type/package/apple.rb b/lib/puppet/type/package/apple.rb deleted file mode 100755 index 563449c2e..000000000 --- a/lib/puppet/type/package/apple.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Puppet - # OS X Packaging sucks. We can install packages, but that's about it. - Puppet.type(:package).newpkgtype(:apple) do - def query - if FileTest.exists?("/Library/Receipts/#{self[:name]}.pkg") - return {:name => self[:name], :ensure => :present} - else - return nil - end - end - - def install - source = nil - unless source = self[:source] - self.fail "Mac OS X packages must specify a package source" - end - - output = %x{/usr/sbin/installer -pkg #{source} -target / 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - end - - def list - packages = [] - - Dir.entries("/Library/Receipts").find { |f| - f =~ /\.pkg$/ - }.collect { |f| - Puppet.type(:package).installedpkg( - :name => f.sub(/\.pkg/, ''), - :type => :apple, - :ensure => :installed - ) - } - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/apt.rb b/lib/puppet/type/package/apt.rb deleted file mode 100755 index cc1e26a06..000000000 --- a/lib/puppet/type/package/apt.rb +++ /dev/null @@ -1,107 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:apt, :dpkg) do - ENV['DEBIAN_FRONTEND'] = "noninteractive" - - - - # A derivative of DPKG; this is how most people actually manage - # Debian boxes, and the only thing that differs is that it can - # install packages from remote sites. - - def checkforcdrom - unless defined? @@checkedforcdrom - if FileTest.exists? "/etc/apt/sources.list" - if File.read("/etc/apt/sources.list") =~ /^[^#]*cdrom:/ - @@checkedforcdrom = true - else - @@checkedforcdrom = false - end - else - # This is basically a pathalogical case, but we'll just - # ignore it - @@checkedforcdrom = false - end - end - - if @@checkedforcdrom and self[:allowcdrom] != :true - raise Puppet::Error, - "/etc/apt/sources.list contains a cdrom source; not installing. Use 'allowcdrom' to override this failure." - end - end - - # Install a package using 'apt-get'. This function needs to support - # installing a specific version. - def install - should = self.should(:ensure) - - checkforcdrom() - - str = self[:name] - case should - when true, false, Symbol - # pass - else - # Add the package version - str += "=%s" % should - end - cmd = "/usr/bin/apt-get -q -y install %s" % str - - self.info "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - end - - # What's the latest package version available? - def latest - cmd = "/usr/bin/apt-cache showpkg %s" % self[:name] - self.info "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - - if output =~ /Versions:\s*\n((\n|.)+)^$/ - versions = $1 - version = versions.split(/\n/).collect { |version| - if version =~ /^([^\(]+)\(/ - $1 - else - self.warning "Could not match version '%s'" % version - nil - end - }.reject { |vers| vers.nil? }.sort[-1] - - unless version - self.debug "No latest version" - if Puppet[:debug] - print output - end - end - - return version - else - self.err "Could not match string" - end - end - - def update - self.install - end - - def uninstall - cmd = "/usr/bin/apt-get -y -q remove %s" % self[:name] - output = %x{#{cmd} 2>&1} - if $? != 0 - raise Puppet::PackageError.new(output) - end - end - - def versionable? - true - end - end -end diff --git a/lib/puppet/type/package/blastwave.rb b/lib/puppet/type/package/blastwave.rb deleted file mode 100755 index 7ef91eca4..000000000 --- a/lib/puppet/type/package/blastwave.rb +++ /dev/null @@ -1,136 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:blastwave, :sun) do - if pkgget = %x{which pkg-get 2>/dev/null}.chomp and pkgget != "" - @@pkgget = pkgget - else - @@pkgget = nil - end - - # This is so stupid - ENV["PAGER"] = "/usr/bin/cat" - - def self.extended(mod) - unless @@pkgget - raise Puppet::Error, - "The pkg-get command is missing; blastwave packaging unavailable" - end - - unless FileTest.exists?("/var/pkg-get/admin") - Puppet.notice "It is highly recommended you create '/var/pkg-get/admin'." - Puppet.notice "See /var/pkg-get/admin-fullauto" - end - end - - # Turn our blastwave listing into a bunch of hashes. - def blastlist(hash) - command = "#{@@pkgget} -c" - - if hash[:justme] - command += " " + self[:name] - end - - begin - output = execute(command) - rescue ExecutionFailure => detail - raise Puppet::Error, "Could not get package listing: %s" % - detail - end - - list = output.split("\n").collect do |line| - next if line =~ /^#/ - next if line =~ /^WARNING/ - next if line =~ /localrev\s+remoterev/ - - blastsplit(line) - end.reject { |h| h.nil? } - - if hash[:justme] - return list[0] - else - list.reject! { |h| - h[:ensure] == :absent - } - return list - end - - end - - # Split the different lines into hashes. - def blastsplit(line) - if line =~ /\s*(\S+)\s+((\[Not installed\])|(\S+))\s+(\S+)/ - hash = {} - hash[:name] = $1 - hash[:ensure] = if $2 == "[Not installed]" - :absent - else - $2 - end - hash[:avail] = $5 - - if hash[:avail] == "SAME" - hash[:avail] = hash[:ensure] - end - hash[:type] = :blastwave - - return hash - else - Puppet.warning "Cannot match %s" % line - return nil - end - end - - module_function :blastlist, :blastsplit - - def install - begin - execute("#{@@pkgget} -f install #{self[:name]}") - rescue ExecutionFailure => detail - raise Puppet::Error, - "Could not install %s: %s" % - [self[:name], detail] - end - end - - # Retrieve the version from the current package file. - def latest - hash = blastlist(:justme => true) - hash[:avail] - end - - def list(hash = {}) - blastlist(hash).each do |bhash| - bhash.delete(:avail) - Puppet::Type.type(:package).installedpkg(bhash) - end - end - - def query - hash = blastlist(:justme => true) - - {:ensure => hash[:ensure]} - end - - # Remove the old package, and install the new one - def update - begin - execute("#{@@pkgget} -f upgrade #{self[:name]}") - rescue ExecutionFailure => detail - raise Puppet::Error, - "Could not upgrade %s: %s" % - [self[:name], detail] - end - end - - def uninstall - begin - execute("#{@@pkgget} -f remove #{self[:name]}") - rescue ExecutionFailure => detail - raise Puppet::Error, - "Could not remove %s: %s" % - [self[:name], detail] - end - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/darwinport.rb b/lib/puppet/type/package/darwinport.rb deleted file mode 100755 index 0d8fc288d..000000000 --- a/lib/puppet/type/package/darwinport.rb +++ /dev/null @@ -1,97 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:darwinport) do - def port - "/opt/local/bin/port" - end - - def eachpkgashash - # list out all of the packages - open("| #{port} list installed") { |process| - regex = %r{(\S+)\s+@(\S+)\s+(\S+)} - fields = [:name, :version, :location] - hash = {} - - # now turn each returned line into a package object - process.each { |line| - hash.clear - - if match = regex.match(line) - fields.zip(match.captures) { |field,value| - hash[field] = value - } - - hash.delete :location - hash[:ensure] = hash[:version] - yield hash.dup - else - raise Puppet::DevError, - "Failed to match dpkg line %s" % line - end - } - } - end - - def install - should = self.should(:ensure) - - # Seems like you can always say 'upgrade' - cmd = "#{port()} upgrade #{self[:name]}" - - self.info "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - end - - def list - packages = [] - - eachpkgashash do |hash| - pkg = Puppet.type(:package).installedpkg(hash) - packages << pkg - end - - return packages - end - - def query - version = nil - eachpkgashash do |hash| - if hash[:name] == self[:name] - return hash - end - end - - return nil - end - - def latest - info = %x{#{port()} search '^#{self[:name]}$' 2>/dev/null} - - if $? != 0 or info =~ /^Error/ - return nil - end - - ary = info.split(/\s+/) - version = ary[2].sub(/^@/, '') - - return version - end - - def uninstall - cmd = "#{port()} uninstall #{self[:name]}" - output = %x{#{cmd} 2>&1} - if $? != 0 - raise Puppet::PackageError.new(output) - end - end - - def update - return install() - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/dpkg.rb b/lib/puppet/type/package/dpkg.rb deleted file mode 100755 index c66897b8c..000000000 --- a/lib/puppet/type/package/dpkg.rb +++ /dev/null @@ -1,113 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:dpkg) do - def query - packages = [] - - # dpkg only prints as many columns as you have available - # which means we don't get all of the info - # stupid stupid - oldcol = ENV["COLUMNS"] - ENV["COLUMNS"] = "500" - fields = [:desired, :status, :error, :name, :version, :description] - - hash = {} - # list out our specific package - open("| /usr/bin/dpkg -l %s 2>/dev/null" % self[:name]) { |process| - # our regex for matching dpkg output - regex = %r{^(.)(.)(.)\s(\S+)\s+(\S+)\s+(.+)$} - - # we only want the last line - lines = process.readlines - # we've got four header lines, so we should expect all of those - # plus our output - if lines.length < 5 - return nil - end - - line = lines[-1] - - if match = regex.match(line) - fields.zip(match.captures) { |field,value| - hash[field] = value - } - #packages.push Puppet.type(:package).installedpkg(hash) - else - raise Puppet::DevError, - "failed to match dpkg line %s" % line - end - } - ENV["COLUMNS"] = oldcol - - if hash[:error] != " " - raise Puppet::PackageError.new( - "Package %s, version %s is in error state: %s" % - [hash[:name], hash[:version], hash[:error]] - ) - end - - if hash[:status] == "i" - hash[:ensure] = :present - else - hash[:ensure] = :absent - end - - return hash - end - - def list - packages = [] - - # dpkg only prints as many columns as you have available - # which means we don't get all of the info - # stupid stupid - oldcol = ENV["COLUMNS"] - ENV["COLUMNS"] = "500" - - # list out all of the packages - open("| /usr/bin/dpkg -l") { |process| - # our regex for matching dpkg output - regex = %r{^(\S+)\s+(\S+)\s+(\S+)\s+(.+)$} - fields = [:status, :name, :version, :description] - hash = {} - - 5.times { process.gets } # throw away the header - - # now turn each returned line into a package object - process.each { |line| - if match = regex.match(line) - hash.clear - - fields.zip(match.captures) { |field,value| - hash[field] = value - } - - if self.is_a? Puppet::Type and type = self[:type] - hash[:type] = type - elsif self.is_a? Module and self.respond_to? :name - hash[:type] = self.name - else - raise Puppet::DevError, "Cannot determine package type" - end - packages.push Puppet.type(:package).installedpkg(hash) - else - raise Puppet::DevError, - "Failed to match dpkg line %s" % line - end - } - } - ENV["COLUMNS"] = oldcol - - return packages - end - - def uninstall - cmd = "/usr/bin/dpkg -r %s" % self[:name] - output = %x{#{cmd} 2>&1} - if $? != 0 - raise Puppet::PackageError.new(output) - end - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/freebsd.rb b/lib/puppet/type/package/freebsd.rb deleted file mode 100755 index bb36f7650..000000000 --- a/lib/puppet/type/package/freebsd.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:freebsd, :openbsd) do - def listcmd - "pkg_info" - end - - def query - list - - if self[:version] - return :listed - else - return nil - end - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/gem.rb b/lib/puppet/type/package/gem.rb deleted file mode 100755 index 31d6ad226..000000000 --- a/lib/puppet/type/package/gem.rb +++ /dev/null @@ -1,119 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:gem) do - if gem = %x{which gem 2>/dev/null}.chomp and gem != "" and gem !~ /^no / - @@gem = gem - else - @@gem = nil - end - def self.extended(mod) - unless @@gem - raise Puppet::Error, - "The gem command is missing; gems unavailable" - end - end - - def gemlist(hash) - command = "#{@@gem} list " - - if hash[:local] - command += "--local " - else - command += "--remote " - end - - if hash[:justme] - command += self[:name] - end - begin - list = execute(command).split("\n\n").collect do |set| - if gemhash = gemsplit(set) - gemhash[:type] = :gem - gemhash[:ensure] = gemhash[:version][0] - gemhash - else - nil - end - end.reject { |p| p.nil? } - rescue ExecutionFailure => detail - raise Puppet::Error, "Could not list gems: %s" % detail - end - - if hash[:justme] - return list.shift - else - return list - end - end - - module_function :gemlist - - def gemsplit(desc) - case desc - when /^\*\*\*/: return nil - when /^(\S+)\s+\((.+)\)\n/ - name = $1 - version = $2.split(/,\s*/) - return { - :name => name, - :version => version - } - else - Puppet.warning "Could not match %s" % desc - nil - end - end - - module_function :gemsplit - - def install(useversion = true) - command = "#{@@gem} install " - if self[:version] and useversion - command += "-v %s " % self[:version] - end - if source = self[:source] - command += source - else - command += self[:name] - end - begin - execute(command) - rescue ExecutionFailure => detail - raise Puppet::Error, "Could not install %s: %s" % - [self[:name], detail] - end - end - - def latest - # This always gets the latest version available. - hash = gemlist(:justme => true) - - return hash[:version][0] - end - - def list(justme = false) - gemlist(:local => true).each do |hash| - Puppet::Type.type(:package).installedpkg(hash) - end - end - - def query - gemlist(:justme => true, :local => true) - end - - def uninstall - begin - # Remove everything, including the binaries. - execute("#{@@gem} uninstall -x -a #{self[:name]}") - rescue ExecutionFailure => detail - raise Puppet::Error, "Could not uninstall %s: %s" % - [self[:name], detail] - end - end - - def update - self.install(false) - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/openbsd.rb b/lib/puppet/type/package/openbsd.rb deleted file mode 100755 index 441ff83d2..000000000 --- a/lib/puppet/type/package/openbsd.rb +++ /dev/null @@ -1,112 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:openbsd) do - def listcmd - "pkg_info -a" - end - - module_function :listcmd - - def install - should = self.should(:ensure) - - unless self[:source] - raise Puppet::Error, - "You must specify a package source for BSD packages" - end - - cmd = "pkg_add #{self[:source]}" - - self.info "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - end - - def query - hash = {} - # list out our specific package - info = %x{pkg_info #{self[:name]} 2>/dev/null} - - # Search for the version info - if info =~ /Information for #{self[:name]}-(\S+)/ - hash[:version] = $1 - hash[:ensure] = :present - else - return nil - end - - # And the description - if info =~ /Comment:\s*\n(.+)/ - hash[:description] = $1 - end - - return hash - end - - def list - packages = [] - - if self.is_a? Puppet::Type - debug "Executing %s" % listcmd().inspect - else - Puppet.debug "Executing %s" % listcmd().inspect - end - # list out all of the packages - open("| #{listcmd()}") { |process| - # our regex for matching pkg_info output - regex = %r{^(\S+)-([^-\s]+)\s+(.+)} - fields = [:name, :version, :description] - hash = {} - - # now turn each returned line into a package object - process.each { |line| - hash.clear - if match = regex.match(line) - fields.zip(match.captures) { |field,value| - hash[field] = value - } - yup = nil - name = hash[:name] - hash[:ensure] = :present - - if self.is_a? Puppet::Type and type = self[:type] - hash[:type] = type - elsif self.is_a? Module and self.respond_to? :name - hash[:type] = self.name - else - raise Puppet::DevError, "Cannot determine package type" - end - - pkg = Puppet.type(:package).installedpkg(hash) - packages << pkg - else - # Print a warning on lines we can't match, but move - # on, since it should be non-fatal - warning("Failed to match line %s" % line) - end - } - } - - # Mark as absent any packages we didn't find - Puppet.type(:package).each do |pkg| - unless packages.include? pkg - pkg.is = [:ensure, :absent] - end - end - - return packages - end - - def uninstall - cmd = "pkg_delete %s" % self[:name] - output = %x{#{cmd} 2>&1} - if $? != 0 - raise Puppet::PackageError.new(output) - end - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/ports.rb b/lib/puppet/type/package/ports.rb deleted file mode 100755 index 972aa90be..000000000 --- a/lib/puppet/type/package/ports.rb +++ /dev/null @@ -1,103 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:ports, :openbsd) do - # I hate ports - %w{INTERACTIVE UNAME}.each do |var| - if ENV.include?(var) - ENV.delete(var) - end - end - def install - # -p: create a package - # -N: install if the package is missing, otherwise upgrade - # -P: prefer binary packages - cmd = "/usr/local/sbin/portupgrade -p -N -P #{self[:name]}" - - self.debug "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>&1 1>/dev/null} - - if output =~ /\*\* No such / - raise Puppet::PackageError, "Could not find package %s" % self[:name] - end - #output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - end - - # If there are multiple packages, we only use the last one - def latest - cmd = "/usr/local/sbin/portversion -v #{self[:name]}" - - self.debug "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>/dev/null}.chomp - line = output.split("\n").pop - - unless line =~ /^(\S+)\s+(\S)\s+(.+)$/ - # There's no "latest" version, so just return a placeholder - return :latest - end - - pkgstuff = $1 - match = $2 - info = $3 - - unless pkgstuff =~ /^(\w+)-([0-9].+)$/ - raise Puppet::PackageError, - "Could not match package info '%s'" % pkgstuff - end - - name, version = $1, $2 - - if match == "=" or match == ">" - # we're up to date or more recent - return version - end - - # Else, we need to be updated; we need to pull out the new version - - unless info =~ /\((\w+) has (.+)\)/ - raise Puppet::PackageError, - "Could not match version info '%s'" % info - end - - source, newversion = $1, $2 - - debug "Newer version in %s" % source - return newversion - end - - def listcmd - "pkg_info" - end - - module_function :listcmd - - def query - list - - if self[:version] and @states[:ensure].is != :absent - return :listed - else - return nil - end - end - - def uninstall - cmd = "/usr/local/sbin/pkg_deinstall #{self[:name]}" - self.debug "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - - end - - def update - install() - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/rpm.rb b/lib/puppet/type/package/rpm.rb deleted file mode 100755 index 153f9167b..000000000 --- a/lib/puppet/type/package/rpm.rb +++ /dev/null @@ -1,121 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:rpm) do - VERSIONSTRING = "%{VERSION}-%{RELEASE}" - def query - fields = { - :name => "NAME", - :version => "VERSION", - :description => "DESCRIPTION" - } - - cmd = "rpm -q #{self[:name]} --qf '%s\n'" % - "%{NAME} #{VERSIONSTRING}" - - self.debug "Executing %s" % cmd.inspect - # list out all of the packages - output = %x{#{cmd} 2>/dev/null}.chomp - - if $? != 0 - #if Puppet[:debug] - # puts output - #end - return nil - end - - regex = %r{^(\S+)\s+(\S+)} - #fields = [:name, :ensure, :description] - fields = [:name, :version] - hash = {} - if match = regex.match(output) - fields.zip(match.captures) { |field,value| - hash[field] = value - } - else - raise Puppet::DevError, - "Failed to match rpm output '%s'" % - output - end - - hash[:ensure] = :present - - return hash - end - - # Here we just retrieve the version from the file specified in the source. - def latest - unless source = self[:source] - self.fail "RPMs must specify a package source" - end - - cmd = "rpm -q --qf '#{VERSIONSTRING}' -p #{self[:source]}" - self.debug "Executing %s" % cmd.inspect - version = %x{#{cmd}} - - return version - end - - def list - packages = [] - - # list out all of the packages - open("| rpm -q -a --qf '%{NAME} #{VERSIONSTRING}\n'") { |process| - # our regex for matching dpkg output - regex = %r{^(\S+)\s+(\S+)} - fields = [:name, :ensure] - hash = {} - - # now turn each returned line into a package object - process.each { |line| - if match = regex.match(line) - hash.clear - - fields.zip(match.captures) { |field,value| - hash[field] = value - } - if self.is_a? Puppet::Type and type = self[:type] - hash[:type] = type - elsif self.is_a? Module and self.respond_to? :name - hash[:type] = self.name - else - raise Puppet::DevError, "Cannot determine package type" - end - packages.push Puppet.type(:package).installedpkg(hash) - else - raise "failed to match rpm line %s" % line - end - } - } - - return packages - end - - def install - source = nil - unless source = self[:source] - self.fail "RPMs must specify a package source" - end - - flag = "-i" - if @states[:ensure].is != :absent - flag = "-U" - end - output = %x{rpm #{flag} #{source} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - end - - def uninstall - cmd = "rpm -e %s" % self[:name] - output = %x{#{cmd}} - if $? != 0 - raise output - end - end - - def update - self.install - end - end -end diff --git a/lib/puppet/type/package/sun.rb b/lib/puppet/type/package/sun.rb deleted file mode 100755 index 1b419f4b9..000000000 --- a/lib/puppet/type/package/sun.rb +++ /dev/null @@ -1,174 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:sun) do - # Get info on a package, optionally specifying a device. - def info2hash(device = nil) - names = { - "PKGINST" => :name, - "NAME" => nil, - "CATEGORY" => :category, - "ARCH" => :platform, - "VERSION" => :ensure, - "BASEDIR" => :root, - "HOTLINE" => nil, - "EMAIL" => nil, - "VSTOCK" => nil, - "VENDOR" => :vendor, - "DESC" => :description, - "PSTAMP" => nil, - "INSTDATE" => nil, - "STATUS" => nil, - "FILES" => nil - } - - hash = {} - cmd = "pkginfo -l" - if device - cmd += " -d #{device}" - end - cmd += " #{self[:name]} 2>/dev/null" - - # list out all of the packages - open("| #{cmd}") { |process| - # we're using the long listing, so each line is a separate - # piece of information - process.each { |line| - case line - when /^$/: # ignore - when /\s*([A-Z]+):\s+(.+)/: - name = $1 - value = $2 - if names.include?(name) - unless names[name].nil? - hash[names[name]] = value - end - else - self.notice "Ignoring unknown name %s" % name - end - when /\s+\d+.+/: - # nothing; we're ignoring the FILES info - end - } - } - - if hash.empty? - return nil - else - return hash - end - end - - def install - unless self[:source] - raise Puppet::Error, "Sun packages must specify a package source" - end - cmd = ["pkgadd"] - - if self[:adminfile] - cmd << " -a " + self[:adminfile] - end - - if self[:responsefile] - cmd << " -r " + self[:responsefile] - end - - cmd += ["-d", self[:source]] - cmd += ["-n", self[:name]] - cmd << "2>&1" - cmd = cmd.join(" ") - - self.debug "Executing %s" % cmd.inspect - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::PackageError.new(output) - end - end - - # Retrieve the version from the current package file. - def latest - hash = info2hash(self[:source]) - hash[:ensure] - end - - def list - packages = [] - hash = {} - names = { - "PKGINST" => :name, - "NAME" => nil, - "CATEGORY" => :category, - "ARCH" => :platform, - "VERSION" => :ensure, - "BASEDIR" => :root, - "HOTLINE" => nil, - "EMAIL" => nil, - "VENDOR" => :vendor, - "DESC" => :description, - "PSTAMP" => nil, - "INSTDATE" => nil, - "STATUS" => nil, - "FILES" => nil - } - - # list out all of the packages - open("| pkginfo -l 2>&1") { |process| - # we're using the long listing, so each line is a separate - # piece of information - process.each { |line| - case line - when /^$/: - hash[:type] = :sun - - packages.push Puppet.type(:package).installedpkg(hash) - hash.clear - when /\s*(\w+):\s+(.+)/: - name = $1 - value = $2 - if names.include?(name) - unless names[name].nil? - hash[names[name]] = value - end - else - raise "Could not find %s" % name - end - when /\s+\d+.+/: - # nothing; we're ignoring the FILES info - end - } - } - return packages - end - - def query - info2hash() - end - - def uninstall - command = "/usr/sbin/pkgrm -n " - - if self[:adminfile] - command += " -a " + self[:adminfile] - end - - command += " " + self[:name] - begin - execute(command) - rescue ExecutionFailure => detail - raise Puppet::Error, - "Could not uninstall %s: %s" % - [self[:name], detail] - end - end - - # Remove the old package, and install the new one. This will probably - # often fail. - def update - if @states[:ensure].is != :absent - self.uninstall - end - self.install - end - end -end - -# $Id$ diff --git a/lib/puppet/type/package/sunfreeware.rb b/lib/puppet/type/package/sunfreeware.rb deleted file mode 100755 index 8778d37bb..000000000 --- a/lib/puppet/type/package/sunfreeware.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Puppet - # At this point, it's an exact copy of the Blastwave stuff. - Puppet.type(:package).newpkgtype(:sunfreeware, :blastwave) do - end -end - -# $Id$ diff --git a/lib/puppet/type/package/yum.rb b/lib/puppet/type/package/yum.rb deleted file mode 100755 index fa214c7d6..000000000 --- a/lib/puppet/type/package/yum.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Puppet - Puppet.type(:package).newpkgtype(:yum, :rpm) do - include Puppet::Util - # Install a package using 'yum'. - def install - cmd = "yum -y install %s" % self[:name] - - begin - output = execute(cmd) - rescue Puppet::ExecutionFailure => detail - raise Puppet::PackageError.new(detail) - end - - @states[:ensure].retrieve - if @states[:ensure].is == :absent - raise Puppet::PackageError.new( - "Could not find package %s" % self.name - ) - end - end - - # What's the latest package version available? - def latest - cmd = "yum list available %s" % self[:name] - - begin - output = execute(cmd) - rescue Puppet::ExecutionFailure => detail - raise Puppet::PackageError.new(detail) - end - - if output =~ /#{self[:name]}\S+\s+(\S+)\s/ - return $1 - else - # Yum didn't find updates, pretend the current - # version is the latest - return self[:version] - end - end - - def update - # Install in yum can be used for update, too - self.install - end - - def versionable? - false - end - end -end - -# $Id$ diff --git a/lib/puppet/type/parsedtype.rb b/lib/puppet/type/parsedtype.rb index 842346e1c..0bb4353ef 100755 --- a/lib/puppet/type/parsedtype.rb +++ b/lib/puppet/type/parsedtype.rb @@ -112,9 +112,9 @@ module Puppet # Override 'newstate' so that all states default to having the # correct parent type - def self.newstate(name, parent = nil, &block) - parent ||= Puppet::State::ParsedParam - super(name, parent, &block) + def self.newstate(name, options = {}, &block) + options[:parent] ||= Puppet::State::ParsedParam + super(name, options, &block) end # Add another type var. diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index b65a8cd54..427460c35 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -26,28 +26,28 @@ module Puppet wherever possible, it relies on local tools to enable or disable a given service. *true*/*false*/*runlevels*" - newvalue(:true) do - unless @parent.respond_to?(:enable) + newvalue(:true, :event => :service_enabled) do + unless provider.respond_to?(:enable) raise Puppet::Error, "Service %s does not support enabling" % @parent.name end - @parent.enable + provider.enable end - newvalue(:false) do - unless @parent.respond_to?(:disable) + newvalue(:false, :event => :service_disabled) do + unless provider.respond_to?(:disable) raise Puppet::Error, "Service %s does not support enabling" % @parent.name end - @parent.disable + provider.disable end def retrieve - unless @parent.respond_to?(:enabled?) + unless provider.respond_to?(:enabled?) raise Puppet::Error, "Service %s does not support enabling" % @parent.name end - @is = @parent.enabled? + @is = provider.enabled? end validate do |value| @@ -80,13 +80,13 @@ module Puppet case self.should when :true if @runlevel - @parent.enable(@runlevel) + provider.enable(@runlevel) else - @parent.enable() + provider.enable() end return :service_enabled when :false - @parent.disable + provider.disable return :service_disabled end end @@ -96,34 +96,34 @@ module Puppet newstate(:ensure) do desc "Whether a service should be running. **true**/*false*" - newvalue(:stopped) do - @parent.stop + newvalue(:stopped, :event => :service_stopped) do + provider.stop end - newvalue(:running) do - @parent.start + newvalue(:running, :event => :service_started) do + provider.start end aliasvalue(:false, :stopped) aliasvalue(:true, :running) def retrieve - self.is = @parent.status + self.is = provider.status end def sync - event = nil - case self.should - when :running - @parent.start - event = :service_started - when :stopped - @parent.stop - event = :service_stopped - else - self.debug "Not running '%s' and shouldn't be running" % - self - end + event = super() +# case self.should +# when :running +# provider.start +# event = :service_started +# when :stopped +# provider.stop +# event = :service_stopped +# else +# self.debug "Not running '%s' and shouldn't be running" % +# self +# end if state = @parent.state(:enable) state.retrieve @@ -149,41 +149,13 @@ module Puppet end end - newparam(:type) do - desc "The service type. For most platforms, it does not make - sense to set this parameter, as the default is based on - the builtin service facilities. The service types available are: - - * **base**: You must specify everything. - * **init**: Assumes ``start`` and ``stop`` commands exist, but you - must specify everything else. - * **debian**: Debian's own specific version of ``init``. - * **smf**: Solaris 10's new Service Management Facility. - " - - defaultto { @parent.class.defaulttype } - - # Make sure we've got the actual module, not just a string - # representing the module. - munge do |type| - if type.is_a?(String) - type = type.intern - end - if type.is_a?(Symbol) - typeklass = @parent.class.svctype(type) - end - @parent.extend(typeklass) - - # Return the name, not the object - return type - 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 @@ -202,6 +174,16 @@ module Puppet is in." isnamevar end + + newparam(:type) do + desc "Deprecated form of ``provder``." + + munge do |value| + warning "'type' is deprecated; use 'provider' instead" + @parent[:provider] = value + end + end + newparam(:path) do desc "The search path for finding init scripts." @@ -270,7 +252,7 @@ module Puppet end # Retrieve the default type for the current platform. - def self.defaulttype + def self.disableddefaulttype unless defined? @defsvctype @defsvctype = nil os = Facter["operatingsystem"].value @@ -342,139 +324,6 @@ module Puppet @paths[type].dup end - # Create new subtypes of service management. - def self.newsvctype(name, parent = nil, &block) - if parent - parent = self.svctype(parent) - end - svcname = name - mod = Module.new - const_set("SvcType" + name.to_s.capitalize,mod) - - # Add our parent, if it exists - if parent - mod.send(:include, parent) - end - - # And now define the support methods - code = %{ - def self.name - "#{svcname}" - end - - def self.inspect - "SvcType(#{svcname})" - end - - def self.to_s - "SvcType(#{svcname})" - end - - def svctype - "#{svcname}" - end - } - - mod.module_eval(code) - - mod.module_eval(&block) - - # Extend the service type with the util stuff - mod.send(:include, Puppet::Util) - - #unless mod.respond_to? :list - # Puppet.debug "Service type %s has no list method" % name - #end - - # "module_function" makes the :list method private, so if the parent - # method also called module_function, then it's already private - if mod.public_method_defined? :list or mod.private_method_defined? :list - mod.send(:module_function, :list) - end - - # mark it as a valid type - unless defined? @typeparam - @typeparam = @parameters.find { |p| p.name == :type } - - unless @typeparam - raise Puppet::DevError, "Could not package type parameter" - end - end - @typeparam.newvalues(name) - - # And add it to our list of types - @modules ||= Hash.new do |hash, key| - if key.is_a?(String) - key = key.intern - end - - if hash.include?(key) - hash[key] - else - nil - end - end - @modules[name] = mod - end - - # Retrieve a service type. - def self.svctype(name) - name = name.intern if name.is_a? String - - # Try autoloading lacking service types. - unless @modules.include? name - begin - require "puppet/type/service/#{name}" - unless @modules.include? name - Puppet.warning( - "Loaded puppet/type/service/#{name} but " + - "service type was not created" - ) - end - rescue LoadError - # nothing - end - end - @modules[name] - end - - # Execute a command. Basically just makes sure it exits with a 0 - # code. - def execute(type, cmd) - self.debug "Executing %s" % cmd.inspect - output = %x(#{cmd} 2>&1) - unless $? == 0 - self.fail "Could not %s %s: %s" % - [type, self.name, output.chomp] - end - end - - # Get the process ID for a running process. Requires the 'pattern' - # parameter. - def getpid - unless self[:pattern] - self.fail "Either a stop command or a pattern must be specified" - end - ps = Facter["ps"].value - unless ps and ps != "" - self.fail( - "You must upgrade Facter to a version that includes 'ps'" - ) - end - regex = Regexp.new(self[: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 - # Initialize the service. This is basically responsible for merging # in the right module. def initialize(hash) @@ -485,95 +334,7 @@ module Puppet self.configchk end end - - # Retrieve the service type. - def type2module(type) - self.class.svctype(type) - end - - # Basically just a synonym for restarting. Used to respond - # to events. - def refresh - self.restart - end - - # How to restart the process. - def restart - if self[:restart] or self.respond_to?(:restartcmd) - cmd = self[:restart] || self.restartcmd - self.execute("restart", cmd) - 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 self[:status] or ( - self.respond_to?(:statuscmd) and self.statuscmd - ) - cmd = self[:status] || self.statuscmd - self.debug "Executing %s" % cmd.inspect - output = %x(#{cmd} 2>&1) - self.debug "%s status returned %s" % - [self.name, output.inspect] - 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 - cmd = self[:start] || self.startcmd - self.execute("start", cmd) - 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 self[:stop] - return self[:stop] - elsif self.respond_to?(:stopcmd) - self.execute("stop", self.stopcmd) - else - pid = getpid - unless pid - self.info "%s is not running" % self.name - return false - end - output = %x(kill #{pid} 2>&1) - if $? != 0 - self.fail "Could not kill %s, PID %s: %s" % - [self.name, pid, output] - end - return true - end - end end end -# Load all of the different service types. We could probably get away with -# loading less here, but it's not a big deal to do so. -require 'puppet/type/service/base' -require 'puppet/type/service/init' -require 'puppet/type/service/debian' -require 'puppet/type/service/redhat' -require 'puppet/type/service/smf' - # $Id$ diff --git a/lib/puppet/type/service/base.rb b/lib/puppet/type/service/base.rb deleted file mode 100755 index f84fa85dc..000000000 --- a/lib/puppet/type/service/base.rb +++ /dev/null @@ -1,12 +0,0 @@ -Puppet.type(:service).newsvctype(:base) do - # The command used to start. Generated if the 'binary' argument - # is passed. - def startcmd - if self[:binary] - return self[:binary] - else - raise Puppet::Error, - "Services must specify a start command or a binary" - end - end -end diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index faa5353d0..c0330c13e 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -36,18 +36,40 @@ class State < Puppet::Parameter end end + # Only retrieve the event, don't autogenerate one. + def self.event(value) + if hash = @parameteroptions[value] + hash[:event] + else + nil + end + end + # Create the value management variables. def self.initvars @parametervalues = {} @aliasvalues = {} @parameterregexes = {} + @parameteroptions = {} end - # Parameters just use 'newvalues', since there's no work associated with them, - # but states have blocks associated with their allowed values. - def self.newvalue(name, &block) + # Define a new valid value for a state. You must provide the value itself, + # usually as a symbol, or a regex to match the value. + # + # The first argument to the method is either the value itself or a regex. + # The second argument is an option hash; valid options are: + # * <tt>:event</tt>: The event that should be returned when this value is set. + def self.newvalue(name, options = {}, &block) name = name.intern if name.is_a? String + @parameteroptions[name] = {} + paramopts = @parameteroptions[name] + + # Symbolize everything + options.each do |opt, val| + paramopts[symbolize(opt)] = symbolize(val) + end + case name when Symbol if @parametervalues.include?(name) @@ -55,8 +77,11 @@ class State < Puppet::Parameter end @parametervalues[name] = block - define_method("set_" + name.to_s, &block) + method = "set_" + name.to_s + settor = paramopts[:settor] || (self.name.to_s + "=") + define_method(method, &block) when Regexp + # The regexes are handled in parameter.rb @parameterregexes[name] = block else raise ArgumentError, "Invalid value %s of type %s" % @@ -64,58 +89,24 @@ class State < Puppet::Parameter end end - # Call the method associated with a given value. - def set - if self.insync? - self.log "already in sync" - return nil - end - - value = self.should - method = "set_" + value.to_s - event = nil - if self.respond_to?(method) - self.debug "setting %s (currently %s)" % [value, self.is] - - begin - event = self.send(method) - rescue Puppet::Error - raise - rescue => detail - if Puppet[:debug] - puts detail.backtrace - end - self.fail "Could not set %s on %s: %s" % - [value, self.class.name, detail] - end - elsif ary = self.class.match?(value) - # FIXME It'd be better here to define a method, so that - # the blocks could return values. - event = self.instance_eval(&ary[1]) - else - self.fail "%s is not a valid value for %s" % - [value, self.class.name] - end - - if event and event.is_a?(Symbol) - if event == :nochange - return nil - else - return event - end - else - # Return the appropriate event. - event = case self.should - when :present: (@parent.class.name.to_s + "_created").intern - when :absent: (@parent.class.name.to_s + "_removed").intern + # How should a state change be printed as a string? + def change_to_s + begin + if @is == :absent + return "defined '%s' as '%s'" % + [self.name, self.should_to_s] + elsif self.should == :absent or self.should == [:absent] + return "undefined %s from '%s'" % + [self.name, self.is_to_s] else - (@parent.class.name.to_s + "_changed").intern + return "%s changed '%s' to '%s'" % + [self.name, self.is_to_s, self.should_to_s] end - - #self.log "made event %s because 'should' is %s, 'is' is %s" % - # [event, self.should.inspect, self.is.inspect] - - return event + rescue Puppet::Error, Puppet::DevError + raise + rescue => detail + raise Puppet::DevError, "Could not convert change %s to string: %s" % + [self.name, detail] end end @@ -186,9 +177,17 @@ class State < Puppet::Parameter return false end + # because the @should and @is vars might be in weird formats, + # we need to set up a mechanism for pretty printing of the values + # default to just the values, but this way individual states can + # override these methods + def is_to_s + @is + end + + # Send a log message. def log(msg) unless @parent[:loglevel] - p @parent self.devfail "Parent %s has no loglevel" % @parent.name end @@ -227,6 +226,82 @@ class State < Puppet::Parameter end end + # Retrieve the parent's provider. Some types don't have providers, in which + # case we return the parent object itself. + def provider + @parent.provider || @parent + end + + # By default, call the method associated with the state name on our + # provider. In other words, if the state name is 'gid', we'll call + # 'provider.gid' to retrieve the current value. + def retrieve + @is = provider.send(self.class.name) + end + + # Call the method associated with a given value. + def set + if self.insync? + self.log "already in sync" + return nil + end + + value = self.should + method = "set_" + value.to_s + event = nil + if self.respond_to?(method) + self.debug "setting %s (currently %s)" % [value, self.is] + + begin + event = self.send(method) + rescue Puppet::Error + raise + rescue => detail + if Puppet[:debug] + puts detail.backtrace + end + self.fail "Could not set %s on %s: %s" % + [value, self.class.name, detail] + end + elsif ary = self.class.match?(value) + # FIXME It'd be better here to define a method, so that + # the blocks could return values. + event = self.instance_eval(&ary[1]) + else + if provider.respond_to?(self.class.name.to_s + "=") + provider.send(self.class.name.to_s + "=", self.should) + else + self.fail "%s is not a valid value for %s" % + [value, self.class.name] + end + end + + if setevent = self.class.event(value) + return setevent + else + if event and event.is_a?(Symbol) + if event == :nochange + return nil + else + return event + end + else + # Return the appropriate event. + event = case self.should + when :present: (@parent.class.name.to_s + "_created").intern + when :absent: (@parent.class.name.to_s + "_removed").intern + else + (@parent.class.name.to_s + "_changed").intern + end + + #self.log "made event %s because 'should' is %s, 'is' is %s" % + # [event, self.should.inspect, self.is.inspect] + + return event + end + end + end + # Only return the first value def should if defined? @should @@ -262,6 +337,14 @@ class State < Puppet::Parameter end end + def should_to_s + if defined? @should + @should.join(" ") + else + return nil + end + end + # The default 'sync' method only selects among a list of registered # values. def sync @@ -280,43 +363,6 @@ class State < Puppet::Parameter self.set end - # How should a state change be printed as a string? - def change_to_s - begin - if @is == :absent - return "defined '%s' as '%s'" % - [self.name, self.should_to_s] - elsif self.should == :absent or self.should == [:absent] - return "undefined %s from '%s'" % - [self.name, self.is_to_s] - else - return "%s changed '%s' to '%s'" % - [self.name, self.is_to_s, self.should_to_s] - end - rescue Puppet::Error, Puppet::DevError - raise - rescue => detail - raise Puppet::DevError, "Could not convert change %s to string: %s" % - [self.name, detail] - end - end - - # because the @should and @is vars might be in weird formats, - # we need to set up a mechanism for pretty printing of the values - # default to just the values, but this way individual states can - # override these methods - def is_to_s - @is - end - - def should_to_s - if defined? @should - @should.join(" ") - else - return nil - end - end - def to_s return "%s(%s)" % [@parent.name,self.name] end diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 4aec9efc3..3a78d9669 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -4,47 +4,35 @@ require 'puppet/type/state' require 'puppet/type/nameservice' module Puppet - newtype(:user, Puppet::Type::NSSType) do - case Facter["operatingsystem"].value - when "Darwin": - @parentstate = Puppet::NameService::NetInfo::NetInfoState - @parentmodule = Puppet::NameService::NetInfo - when "FreeBSD": - @parentstate = Puppet::NameService::PW::PWUser - @parentmodule = Puppet::NameService::PW - else - @parentstate = Puppet::NameService::ObjectAdd::ObjectAddUser - @parentmodule = Puppet::NameService::ObjectAdd - end - - newstate(:ensure, @parentstate) do - newvalue(:present) do + newtype(:user) do + newstate(:ensure) do + newvalue(:present, :event => :user_created) do # Verify that they have provided everything necessary, if we # are trying to manage the user - if @parent.managed? - @parent.class.states.each { |state| - next if stateobj = @parent.state(state.name) - next if state.name == :ensure - - unless state.autogen? or state.isoptional? - if state.method_defined?(:autogen) - @parent[state.name] = :auto - else - @parent.fail "Users require a value for %s" % - state.name - end - end - } - - #if @states.empty? - # @parent[:comment] = @parent[:name] - #end - end - self.syncname(:present) +# if @parent.managed? +# @parent.class.states.each { |state| +# next if stateobj = @parent.state(state.name) +# next if state.name == :ensure +# +# unless state.autogen? or state.isoptional? +# if state.method_defined?(:autogen) +# @parent[state.name] = :auto +# else +# @parent.fail "Users require a value for %s" % +# state.name +# end +# end +# } +# +# #if @states.empty? +# # @parent[:comment] = @parent[:name] +# #end +# end + provider.create end - newvalue(:absent) do - self.syncname(:absent) + newvalue(:absent, :event => :user_removed) do + provider.delete end desc "The basic state that the object should be in." @@ -80,12 +68,13 @@ module Puppet end def retrieve - if @parent.exists? + if provider.exists? @is = :present else @is = :absent end end + # The default 'sync' method only selects among a list of registered # values. def sync @@ -106,28 +95,13 @@ module Puppet end - newstate(:uid, @parentstate) do + newstate(:uid) do desc "The user ID. Must be specified numerically. For new users being created, if no user ID is specified then one will be chosen automatically, which will likely result in the same user having different IDs on different systems, which is not recommended." - isautogen - - def autogen - highest = 0 - Etc.passwd { |user| - if user.uid > highest - unless user.uid > 65000 - highest = user.uid - end - end - } - - return highest + 1 - end - munge do |value| case value when String @@ -148,12 +122,10 @@ module Puppet end end - newstate(:gid, @parentstate) do + newstate(:gid) do desc "The user's primary group. Can be specified numerically or by name." - isautogen - munge do |gid| method = :getgrgid case gid @@ -197,37 +169,27 @@ module Puppet end - newstate(:comment, @parentstate) do + newstate(:comment) do desc "A description of the user. Generally is a user's full name." - - isoptional - - @posixmethod = :gecos end - newstate(:home, @parentstate) do + newstate(:home) do desc "The home directory of the user. The directory must be created separately and is not currently checked for existence." - - isautogen - @posixmethod = :dir end - newstate(:shell, @parentstate) do + newstate(:shell) do desc "The user's login shell. The shell must exist and be executable." - isautogen end - newstate(:groups, @parentstate) do + newstate(:groups) do desc "The groups of which the user is a member. The primary group should not be listed. Multiple groups should be specified as an array." - isoptional - def should_to_s - self.should + self.should.join(",") end def is_to_s @@ -244,18 +206,22 @@ module Puppet @should ||= [] if @parent[:membership] == :inclusive - @should.sort.join(",") + @should.sort else members = @should if @is.is_a?(Array) members += @is end - members.uniq.sort.join(",") + members.uniq.sort end end def retrieve - @is = grouplist() + if tmp = provider.groups + @is = tmp.split(",") + else + @is = :absent + end end def insync? @@ -278,14 +244,8 @@ module Puppet end def sync - if respond_to? :setgrouplist - # Pass them the group list, so that the :membership logic - # is all in this class, not in parent classes. - setgrouplist(self.should) - return :user_modified - else - super - end + provider.groups = self.should.join(",") + :user_changed end end @@ -293,25 +253,23 @@ module Puppet # so i'm disabling them for now # FIXME Puppet::State::UserLocked is currently non-functional - #newstate(:locked, @parentstate) do + #newstate(:locked) do # desc "The expected return code. An error will be returned if the # executed command returns something else." #end # FIXME Puppet::State::UserExpire is currently non-functional - #newstate(:expire, @parentstate) do + #newstate(:expire) do # desc "The expected return code. An error will be returned if the # executed command returns something else." # @objectaddflag = "-e" - # isautogen #end # FIXME Puppet::State::UserInactive is currently non-functional - #newstate(:inactive, @parentstate) do + #newstate(:inactive) do # desc "The expected return code. An error will be returned if the # executed command returns something else." # @objectaddflag = "-f" - # isautogen #end newparam(:name) do @@ -353,8 +311,6 @@ module Puppet for Mac OS X, NetInfo is used. This is currently unconfigurable, but if you desperately need it to be so, please contact us." - @netinfodir = "users" - # Autorequire the group, if it's around autorequire(:group) do #return nil unless @states.include?(:gid) @@ -384,7 +340,7 @@ module Puppet } end - if @states.include?(:groups) and groups = @states[:groups].should.split(",") + if @states.include?(:groups) and groups = @states[:groups].should autos += groups end @@ -400,55 +356,45 @@ module Puppet end end - # List all found users - def self.listbyname + def self.list_by_name users = [] - Etc.setpwent - while ent = Etc.getpwent - users << ent.name + defaultprovider.listbyname do |user| + users << user end - Etc.endpwent - return users end - def exists? - self.class.parentmodule.exists?(self) - end - - def getinfo(refresh = false) - if @userinfo.nil? or refresh == true - begin - @userinfo = Etc.getpwnam(self[:name]) - rescue ArgumentError => detail - @userinfo = nil - end - end - - @userinfo - end - - def initialize(hash) - @userinfo = nil - super + def self.list + defaultprovider.list - unless defined? @states - raise "wtf?" + self.collect do |user| + user end end def retrieve - info = self.getinfo(true) - - if info.nil? - # the user does not exist - @states.each { |name, state| + absent = false + states().each { |state| + if absent state.is = :absent - } - return - else - super - end + else + state.retrieve + end + + if state.name == :ensure and state.is == :absent + absent = true + next + end + } + #if provider.exists? + # super + #else + # # the user does not exist + # @states.each { |name, state| + # state.is = :absent + # } + # return + #end end end end diff --git a/lib/puppet/type/yumrepo.rb b/lib/puppet/type/yumrepo.rb index bb07b93b3..92f83b384 100644 --- a/lib/puppet/type/yumrepo.rb +++ b/lib/puppet/type/yumrepo.rb @@ -209,7 +209,7 @@ module Puppet isnamevar end - newstate(:descr, Puppet::IniState) do + newstate(:descr, :parent => Puppet::IniState) do desc "A human readable description of the repository. #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } @@ -217,7 +217,7 @@ module Puppet inikey "name" end - newstate(:mirrorlist, Puppet::IniState) do + newstate(:mirrorlist, :parent => Puppet::IniState) do desc "The URL that holds the list of mirrors for this repository. #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } @@ -225,21 +225,21 @@ module Puppet newvalue(/.*/) { } end - newstate(:baseurl, Puppet::IniState) do + newstate(:baseurl, :parent => Puppet::IniState) do desc "The URL for this repository.\n#{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } # Should really check that it's a valid URL newvalue(/.*/) { } end - newstate(:enabled, Puppet::IniState) do + newstate(:enabled, :parent => Puppet::IniState) do desc "Whether this repository is enabled or disabled. Possible values are '0', and '1'.\n#{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(%r{(0|1)}) { } end - newstate(:gpgcheck, Puppet::IniState) do + newstate(:gpgcheck, :parent => Puppet::IniState) do desc "Whether to check the GPG signature on packages installed from this repository. Possible values are '0', and '1'. \n#{ABSENT_DOC}" @@ -247,7 +247,7 @@ module Puppet newvalue(%r{(0|1)}) { } end - newstate(:gpgkey, Puppet::IniState) do + newstate(:gpgkey, :parent => Puppet::IniState) do desc "The URL for the GPG key with which packages from this repository are signed.\n#{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } @@ -255,14 +255,14 @@ module Puppet newvalue(/.*/) { } end - newstate(:include, Puppet::IniState) do + newstate(:include, :parent => Puppet::IniState) do desc "A URL from which to include the config.\n#{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } # Should really check that it's a valid URL newvalue(/.*/) { } end - newstate(:exclude, Puppet::IniState) do + newstate(:exclude, :parent => Puppet::IniState) do desc "List of shell globs. Matching packages will never be considered in updates or installs for this repo. #{ABSENT_DOC}" @@ -270,7 +270,7 @@ module Puppet newvalue(/.*/) { } end - newstate(:includepkgs, Puppet::IniState) do + newstate(:includepkgs, :parent => Puppet::IniState) do desc "List of shell globs. If this is set, only packages matching one of the globs will be considered for update or install.\n#{ABSENT_DOC}" @@ -278,7 +278,7 @@ module Puppet newvalue(/.*/) { } end - newstate(:enablegroups, Puppet::IniState) do + newstate(:enablegroups, :parent => Puppet::IniState) do desc "Determines whether yum will allow the use of package groups for this repository. Possible values are '0', and '1'.\n#{ABSENT_DOC}" @@ -286,27 +286,27 @@ module Puppet newvalue(%r{(0|1)}) { } end - newstate(:failovermethod, Puppet::IniState) do + newstate(:failovermethod, :parent => Puppet::IniState) do desc "Either 'roundrobin' or 'priority'.\n#{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(%r(roundrobin|priority)) { } end - newstate(:keepalive, Puppet::IniState) do + newstate(:keepalive, :parent => Puppet::IniState) do desc "Either '1' or '0'. This tells yum whether or not HTTP/1.1 keepalive should be used with this repository.\n#{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(%r{(0|1)}) { } end - newstate(:timeout, Puppet::IniState) do + newstate(:timeout, :parent => Puppet::IniState) do desc "Number of seconds to wait for a connection before timing out.\n#{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(%r{[0-9]+}) { } end - newstate(:metadata_expire, Puppet::IniState) do + newstate(:metadata_expire, :parent => Puppet::IniState) do desc "Number of seconds after which the metadata will expire. #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb index 4fb6fe174..82656e068 100644 --- a/lib/puppet/type/zone.rb +++ b/lib/puppet/type/zone.rb @@ -178,7 +178,7 @@ Puppet::Type.newtype(:zone) do and cannot be changed." end - newstate(:ip, ZoneMultiConfigState) do + newstate(:ip, :parent => ZoneMultiConfigState) do require 'ipaddr' desc "The IP address of the zone. IP addresses must be specified @@ -225,7 +225,7 @@ end end end - newstate(:autoboot, ZoneConfigState) do + newstate(:autoboot, :parent => ZoneConfigState) do desc "Whether the zone should automatically boot." defaultto true @@ -238,7 +238,7 @@ end end end - newstate(:pool, ZoneConfigState) do + newstate(:pool, :parent => ZoneConfigState) do desc "The resource pool for this zone." def configtext @@ -246,7 +246,7 @@ end end end - newstate(:inherit, ZoneMultiConfigState) do + newstate(:inherit, :parent => ZoneMultiConfigState) do desc "The list of directories that the zone inherits from the global zone. All directories must be fully qualified." diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index 03ef93d21..5086e6cfc 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -31,8 +31,8 @@ module Util # If they're running as a normal user, then just execute as that same # user. unless Process.uid == 0 - yield - return + retval = yield + return retval end begin @@ -82,7 +82,6 @@ module Util Puppet.warning "Could not retrieve UID for %s" % user end end - retval = yield ensure if olduid @@ -278,6 +277,29 @@ module Util } end + # Proxy a bunch of methods to another object. + def self.classproxy(klass, objmethod, *methods) + classobj = class << klass; self; end + methods.each do |method| + classobj.send(:define_method, method) do |*args| + obj = self.send(objmethod) + + obj.send(method, *args) + end + end + end + + # Proxy a bunch of methods to another object. + def self.proxy(klass, objmethod, *methods) + methods.each do |method| + klass.send(:define_method, method) do |*args| + obj = self.send(objmethod) + + obj.send(method, *args) + end + end + end + # XXX this should all be done using puppet objects, not using # normal mkdir def self.recmkdir(dir,mode = 0755) @@ -301,15 +323,6 @@ module Util end end - def self.symbolize(value) - case value - when String: value = value.intern - when Symbol: value - else - raise ArgumentError, "'%s' must be a string or symbol" % value - end - end - # Execute a given chunk of code with a new umask. def self.withumask(mask) cur = File.umask(mask) @@ -354,6 +367,54 @@ module Util end end + def binary(bin) + if bin =~ /^\// + if FileTest.exists? bin + return true + else + return nil + end + else + ENV['PATH'].split(":").each do |dir| + if FileTest.exists? File.join(dir, bin) + return File.join(dir, bin) + end + end + return nil + end + end + module_function :binary + + # Execute the provided command in a pipe, yielding the pipe object. + def execpipe(command, failonfail = true) + if respond_to? :debug + debug "Executing '%s'" % command + else + Puppet.debug "Executing '%s'" % command + end + + output = open("| #{command} 2>&1") do |pipe| + yield pipe + end + + if failonfail + unless $? == 0 + raise ExecutionFailure, output + end + end + + return output + end + + def execfail(command, exception) + begin + output = execute(command) + return output + rescue ExecutionFailure + raise exception, output + end + end + # Execute the desired command, and return the status and output. def execute(command, failonfail = true) if respond_to? :debug @@ -372,6 +433,8 @@ module Util return output end + module_function :execute + # Create an exclusive lock. def threadlock(resource, type = Sync::EX) Puppet::Util.sync(resource).synchronize(type) do @@ -400,6 +463,16 @@ module Util end end + def symbolize(value) + case value + when String: value = value.intern + when Symbol: value + else + raise ArgumentError, "'%s' must be a string or symbol" % value + end + end + module_function :symbolize + # Just benchmark, with no logging. def thinmark seconds = Benchmark.realtime { diff --git a/test/other/autoload.rb b/test/other/autoload.rb new file mode 100644 index 000000000..95fafbda2 --- /dev/null +++ b/test/other/autoload.rb @@ -0,0 +1,111 @@ +#!/usr/bin/env ruby + +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/autoload' +require 'puppettest' +require 'test/unit' + +class TestAutoload < Test::Unit::TestCase + include TestPuppet + @things = [] + def self.newthing(name) + @things << name + end + + def self.thing?(name) + @things.include? name + end + + def self.clear + @things.clear + end + + def mkfile(name, path) + # Now create a file to load + File.open(path, "w") do |f| + f.puts %{ +TestAutoload.newthing(:#{name.to_s}) + } + end + end + + def teardown + super + self.class.clear + end + + def test_load + dir = tempfile() + $: << dir + cleanup do + $:.delete(dir) + end + + Dir.mkdir(dir) + + rbdir = File.join(dir, "yayness") + + Dir.mkdir(rbdir) + + # An object for specifying autoload + klass = self.class + + loader = nil + assert_nothing_raised { + loader = Puppet::Autoload.new(klass, :yayness) + } + + assert_equal(loader.object_id, Puppet::Autoload[klass].object_id, + "Did not retrieve loader object by class") + + # Make sure we don't fail on missing files + assert_nothing_raised { + assert_equal(false, loader.load(:mything), + "got incorrect return on failed load") + } + + # Now create a couple of files for testing + path = File.join(rbdir, "mything.rb") + mkfile(:mything, path) + opath = File.join(rbdir, "othing.rb") + mkfile(:othing, opath) + + # Now try to actually load it. + assert_nothing_raised { + assert_equal(true, loader.load(:mything), + "got incorrect return on failed load") + } + + assert(loader.loaded?(:mything), "Not considered loaded") + + assert(klass.thing?(:mything), + "Did not get loaded thing") + + # Now clear everything, and test loadall + assert_nothing_raised { + loader.clear + } + + self.class.clear + + assert_nothing_raised { + loader.loadall + } + + [:mything, :othing].each do |thing| + assert(loader.loaded?(thing), "#{thing.to_s} not considered loaded") + + assert(klass.thing?(thing), + "Did not get loaded #{thing.to_s}") + end + end + + def test_loadall + end +end diff --git a/test/other/provider.rb b/test/other/provider.rb new file mode 100644 index 000000000..fc04b9926 --- /dev/null +++ b/test/other/provider.rb @@ -0,0 +1,165 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/provider' +require 'puppettest' +require 'test/unit' + +class TestImpl < Test::Unit::TestCase + include TestPuppet + + def setup + super + @type = newtype(@method_name.to_s + "type") + + # But create a new provider for every method. + @provider = newprovider(@method_name.to_s + "provider") + end + + def newtype(name) + # First create a fake type + return Puppet::Type.newtype(name) { + newparam(:name) { isnamevar } + } + end + + def newprovider(name, type = nil) + type ||= @type + provider = nil + assert_nothing_raised("Could not create provider") do + provider = type.provide(name) {} + end + return provider + end + + # Just a quick run-through to see if the basics work + def test_newprovider + assert_nothing_raised do + @provider.confine :operatingsystem => Facter["operatingsystem"].value + @provider.defaultfor :operatingsystem => Facter["operatingsystem"].value + end + + assert(@provider.suitable?, "Implementation was not considered suitable") + assert(@provider.default?, "Implementation was not considered a default") + + assert_equal(@provider, @type.defaultprovider, + "Did not correctly find default provider") + + end + + def test_provider_false_confine + assert_nothing_raised do + @provider.confine :false => false + end + + assert(@provider.suitable?, "False did not check correctly") + end + + def test_provider_true_confine + assert_nothing_raised do + @provider.confine :true => true + end + + assert(@provider.suitable?, "True did not check correctly") + + # Now check whether we multiple true things work + assert_nothing_raised do + @provider.confine :true => false + @provider.confine :true => true + end + assert(! @provider.suitable?, "One truth overrode another") + end + + def test_provider_exists_confine + file = tempfile() + + assert_nothing_raised do + @provider.confine :exists => file + end + + assert(! @provider.suitable?, "Exists did not check correctly") + File.open(file, "w") { |f| f.puts "" } + assert(@provider.suitable?, "Exists did not find file correctly") + end + + def test_provider_facts_confine + # Now check for multiple platforms + assert_nothing_raised do + @provider.confine :operatingsystem => [Facter["operatingsystem"].value, :yayos] + @provider.confine :operatingsystem => [:fakeos, :otheros] + end + + assert(@provider.suitable?, "Implementation not considered suitable") + end + + def test_provider_default + nondef = nil + assert_nothing_raised { + nondef = newprovider(:nondefault) + } + + assert_nothing_raised do + @provider.defaultfor :operatingsystem => Facter["operatingsystem"].value + end + + assert_equal(@provider.name, @type.defaultprovider.name, "Did not get right provider") + + @type.suitableprovider + end + + def test_subclassconfines + parent = newprovider("parentprovider") + + # Now make a bad confine on the parent + parent.confine :exists => "/this/file/definitely/does/not/exist" + + child = nil + assert_nothing_raised { + child = @type.provide("child", :parent => parent.name) {} + } + + assert(child.suitable?, "Parent ruled out child") + end + + def test_commands + parent = newprovider("parentprovider") + + child = nil + assert_nothing_raised { + child = @type.provide("child", :parent => parent.name) {} + } + + assert_nothing_raised { + child.commands :which => "which" + } + + assert(child.command(:which), "Did not find 'which' command") + + assert(child.command(:which) =~ /^\//, + "Command did not become fully qualified") + assert(FileTest.exists?(child.command(:which)), + "Did not find actual 'which' binary") + + assert_raise(Puppet::DevError) do + child.command(:nosuchcommand) + end + + # Now create a parent command + assert_nothing_raised { + parent.commands :sh => Puppet::Util.binary('sh') + } + + assert(parent.command(:sh), "Did not find 'sh' command") + + assert(child.command(:sh), "Did not find parent's 'sh' command") + + assert(FileTest.exists?(child.command(:sh)), + "Somehow broke path to sh") + end +end + +# $Id$ diff --git a/test/providers/group.rb b/test/providers/group.rb new file mode 100755 index 000000000..364d1750d --- /dev/null +++ b/test/providers/group.rb @@ -0,0 +1,246 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../.." +end + +require 'etc' +require 'puppet/type' +require 'puppettest' +require 'test/unit' + +class TestGroupProvider < Test::Unit::TestCase + include TestPuppet + def setup + super + @@tmpgroups = [] + @provider = nil + assert_nothing_raised { + @provider = Puppet::Type.type(:group).defaultprovider + } + + assert(@provider, "Could not find default group provider") + assert(@provider.name != :fake, "Got a fake provider") + end + + def teardown + super + Puppet.type(:group).clear + @@tmpgroups.each { |group| + unless missing?(group) + remove(group) + end + } + end + + def mkgroup(name, hash = {}) + fakemodel = fakemodel(:group, name) + group = nil + assert_nothing_raised { + group = @provider.new(fakemodel) + } + hash.each do |name, val| + fakemodel[name] = val + end + assert(group, "Could not create provider group") + + return group + end + + case Facter["operatingsystem"].value + when "Darwin": + def missing?(group) + output = %x{nidump -r /groups/#{group} / 2>/dev/null}.chomp + + if output == "" + return true + else + return false + end + + assert_equal("", output, "Group %s is present:\n%s" % [group, output]) + end + + def gid(name) + %x{nireport / /groups name gid}.split("\n").each { |line| + group, id = line.chomp.split(/\s+/) + assert(id =~ /^-?\d+$/, "Group id %s for %s is not a number" % + [id, group]) + if group == name + return Integer(id) + end + } + + return nil + end + + def remove(group) + system("niutil -destroy / /groups/%s" % group) + end + else + def missing?(group) + begin + obj = Etc.getgrnam(group) + return false + rescue ArgumentError + return true + end + end + + def gid(name) + assert_nothing_raised { + obj = Etc.getgrnam(name) + return obj.gid + } + + return nil + end + + def remove(group) + system("groupdel %s" % group) + end + end + + def groupnames + %x{groups}.chomp.split(/ /) + end + + def groupids + Process.groups + end + + def attrtest_ensure(group) + old = group.ensure + assert_nothing_raised { + group.ensure = :absent + } + + assert(!group.exists?, "Group was not deleted") + + assert_nothing_raised { + group.ensure = :present + } + assert(group.exists?, "Group was not created") + + unless old == :present + assert_nothing_raised { + group.ensure = old + } + end + end + + def attrtest_gid(group) + old = gid(group.name) + + newgid = old + while true + newgid += 1 + + if newgid - old > 1000 + $stderr.puts "Could not find extra test UID" + return + end + begin + Etc.getgrgid(newgid) + rescue ArgumentError => detail + break + end + end + + assert_nothing_raised("Failed to change group id") { + group.gid = newgid + } + + curgid = nil + assert_nothing_raised { + curgid = gid(group.name) + } + + assert_equal(newgid, curgid, "GID was not changed") + # Refresh + group.getinfo(true) + assert_equal(newgid, group.gid, "Object got wrong gid") + + assert_nothing_raised("Failed to change group id") { + group.gid = old + } + end + + # Iterate over each of our groups and try to grab the gid. + def test_ownprovidergroups + groupnames().each { |group| + gobj = nil + comp = nil + fakemodel = fakemodel(:group, group) + assert_nothing_raised { + gobj = @provider.new(fakemodel) + } + + assert(gobj.gid, "Failed to retrieve gid") + } + end + + if Process.uid == 0 + def test_mkgroup + gobj = nil + comp = nil + name = "pptestgr" + assert(missing?(name), "Group %s is still present" % name) + group = mkgroup(name) + + @@tmpgroups << name + + assert(group.respond_to?(:addcmd), "no respondo?") + assert_nothing_raised { + group.create + } + assert(!missing?(name), "Group %s is missing" % name) + + tests = Puppet.type(:group).validstates + + tests.each { |test| + if self.respond_to?("attrtest_%s" % test) + self.send("attrtest_%s" % test, group) + else + $stderr.puts "Not testing attr %s of group" % test + end + } + + assert_nothing_raised { + group.delete + } + end + + # groupadd -o is broken in FreeBSD. + unless Facter["operatingsystem"].value == "FreeBSD" + def test_duplicateIDs + group1 = mkgroup("group1", :gid => 125) + group2 = mkgroup("group2", :gid => 125) + + @@tmpgroups << "group1" + @@tmpgroups << "group2" + # Create the first group + assert_nothing_raised { + group1.create + } + + # Not all OSes fail here, so we can't test that it doesn't work with + # it off, only that it does work with it on. + assert_nothing_raised { + group2.model[:allowdupe] = :true + } + + # Now create the second group + assert_nothing_raised { + group2.create + } + assert_equal(:present, group2.ensure, + "Group did not get created") + end + end + else + $stderr.puts "Not running as root; skipping group creation tests." + end +end + +# $Id$ diff --git a/test/providers/nameservice.rb b/test/providers/nameservice.rb new file mode 100644 index 000000000..0e0f8050f --- /dev/null +++ b/test/providers/nameservice.rb @@ -0,0 +1,36 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../.." +end + +require 'puppettest' +require 'puppet' +require 'test/unit' +require 'facter' + +class TestNameServiceProvider < Test::Unit::TestCase + include FileTesting + + def test_option + klass = Class.new(Puppet::Type::Provider::NameService) + klass.model = Puppet::Type.type(:user) + + val = nil + assert_nothing_raised { + val = klass.option(:home, :flag) + } + + assert_nil(val, "Got an option") + + assert_nothing_raised { + klass.options :home, :flag => "-d" + } + assert_nothing_raised { + val = klass.option(:home, :flag) + } + assert_equal("-d", val, "Got incorrect option") + end +end + +# $Id$ diff --git a/test/providers/package.rb b/test/providers/package.rb new file mode 100644 index 000000000..210fae5c1 --- /dev/null +++ b/test/providers/package.rb @@ -0,0 +1,86 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../.." +end + +require 'etc' +require 'puppet/type' +require 'puppettest' +require 'test/unit' + +class TestPackageProvider < Test::Unit::TestCase + include TestPuppet + def setup + super + @provider = nil + assert_nothing_raised { + @provider = Puppet::Type.type(:package).defaultprovider + } + + assert(@provider, "Could not find default package provider") + assert(@provider.name != :fake, "Got a fake provider") + end + + def test_nothing + end + + if Facter["operatingsystem"].value == "Solaris" and Process.uid == 0 + if Puppet.type(:package).provider(:blastwave).suitable? + # FIXME The packaging crap needs to be rewritten to support testing + # multiple package types on the same platform. + def test_list_blastwave + pkgs = nil + assert_nothing_raised { + pkgs = Puppet::Type.type(:package).provider(:blastwave).list + } + + pkgs.each do |pkg| + if pkg[:name] =~ /^CSW/ + assert_equal(:blastwave, pkg[:provider], + "Type was not set correctly") + end + end + end + + def test_install_blastwave + pkg = nil + name = "cabextract" + model = fakemodel(:package, name) + assert_nothing_raised { + pkg = Puppet::Type.type(:package).provider(:blastwave).new(model) + } + + if hash = pkg.query and hash[:ensure] != :absent + p hash + $stderr.puts "Cannot test pkg installation; %s is already installed" % + name + return + end + + assert_nothing_raised { + pkg.install + } + + hash = nil + assert(hash = pkg.query, + "package did not install") + assert(hash[:ensure] != :absent, + "package did not install") + + latest = nil + assert_nothing_raised { + latest = pkg.latest + } + assert(latest, "Could not find latest package version") + assert_nothing_raised { + pkg.uninstall + } + end + else + $stderr.puts "No pkg-get scripting; skipping blastwave tests" + end + end +end + +# $Id$ diff --git a/test/providers/user.rb b/test/providers/user.rb new file mode 100644 index 000000000..15b453780 --- /dev/null +++ b/test/providers/user.rb @@ -0,0 +1,528 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../.." +end + +require 'puppettest' +require 'puppet' +require 'test/unit' +require 'facter' + +class TestUserProvider < Test::Unit::TestCase + include FileTesting + + def setup + super + setme() + @@tmpusers = [] + @provider = nil + assert_nothing_raised { + @provider = Puppet::Type.type(:user).defaultprovider + } + + assert(@provider, "Could not find default user provider") + + end + + def teardown + @@tmpusers.each { |user| + unless missing?(user) + remove(user) + end + } + super + #Puppet.type(:user).clear + end + + case Facter["operatingsystem"].value + when "Darwin": + def missing?(user) + output = %x{nidump -r /users/#{user} / 2>/dev/null}.chomp + + if output == "" + return true + else + return false + end + + assert_equal("", output, "User %s is present:\n%s" % [user, output]) + end + + def current?(param, user) + state = Puppet.type(:user).states.find { |st| + st.name == param + } + + output = %x{nireport / /users name #{state.netinfokey}} + output.split("\n").each { |line| + if line =~ /^(\w+)\s+(.+)$/ + username = $1 + id = $2.sub(/\s+$/, '') + if username == user.name + if id =~ /^[-0-9]+$/ + return Integer(id) + else + return id + end + end + else + raise "Could not match %s" % line + end + } + + return nil + end + + def remove(user) + system("niutil -destroy / /users/%s" % user) + end + else + def missing?(user) + begin + obj = Etc.getpwnam(user) + return false + rescue ArgumentError + return true + end + end + + def current?(param, user) + state = Puppet.type(:user).states.find { |st| + st.name == param + } + + assert_nothing_raised { + obj = Etc.getpwnam(user.name) + return obj.send(user.posixmethod(param)) + } + + return nil + end + + def remove(user) + system("userdel %s" % user) + end + end + + + def eachstate + Puppet::Type.type(:user).validstates.each do |state| + next if state == :ensure + yield state + end + end + + def findshell(old = nil) + %w{/bin/sh /bin/bash /sbin/sh /bin/ksh /bin/zsh /bin/csh /bin/tcsh + /usr/bin/sh /usr/bin/bash /usr/bin/ksh /usr/bin/zsh /usr/bin/csh + /usr/bin/tcsh}.find { |shell| + if old + FileTest.exists?(shell) and shell != old + else + FileTest.exists?(shell) + end + } + end + + def fakedata(name, param) + case param + when :name: name + when :comment: "Puppet Testing User %s" % name + when :gid: Process.gid() + when :shell: findshell() + when :home: "/home/%s" % name + else + return nil + end + end + + def mkuser(name) + fakemodel = fakemodel(:user, name) + user = nil + assert_nothing_raised { + user = @provider.new(fakemodel) + } + assert(user, "Could not create provider user") + + return user + end + + def test_list + names = nil + assert_nothing_raised { + names = @provider.listbyname + } + + assert(names.length > 0, "Listed no users") + + # Now try it by object + assert_nothing_raised { + names = @provider.list + } + assert(names.length > 0, "Listed no users as objects") + + names.each do |obj| + assert_instance_of(Puppet::Type.type(:user), obj) + assert(obj[:provider], "Provider was not set") + end + end + + def test_infocollection + fakemodel = fakemodel(:user, @me) + + user = nil + assert_nothing_raised { + user = @provider.new(fakemodel) + } + assert(user, "Could not create user provider") + + Puppet::Type.type(:user).validstates.each do |state| + next if state == :ensure + val = nil + assert_nothing_raised { + val = user.send(state) + } + + assert(val != :absent, + "State %s is missing" % state) + + assert(val, "Did not get value for %s" % state) + end + end + + def test_exists + user = mkuser("nosuchuserok") + + assert(! user.exists?, + "Fake user exists?") + + user = mkuser(@me) + assert(user.exists?, + "I don't exist?") + end + + def attrtest_ensure(user) + old = user.ensure + assert_nothing_raised { + user.ensure = :absent + } + + assert(missing?(user.name), "User is still present") + assert_nothing_raised { + user.ensure = :present + } + assert(!missing?(user.name), "User is absent") + assert_nothing_raised { + user.ensure = :absent + } + + unless old == :absent + user.ensure = old + end + end + + def attrtest_comment(user) + old = user.comment + + assert_nothing_raised { + user.comment = "A different comment" + } + + assert_equal("A different comment", current?(:comment, user), + "Comment was not changed") + + assert_nothing_raised { + user.comment = old + } + + assert_equal(old, current?(:comment, user), + "Comment was not reverted") + end + + def attrtest_home(user) + old = current?(:home, user) + + assert_nothing_raised { + user.home = "/tmp" + } + + assert_equal("/tmp", current?(:home, user), "Home was not changed") + assert_nothing_raised { + user.home = old + } + + assert_equal(old, current?(:home, user), "Home was not reverted") + end + + def attrtest_shell(user) + old = current?(:shell, user) + + newshell = findshell(old) + + unless newshell + $stderr.puts "Cannot find alternate shell; skipping shell test" + return + end + + assert_nothing_raised { + user.shell = newshell + } + + assert_equal(newshell, current?(:shell, user), + "Shell was not changed") + + assert_nothing_raised { + user.shell = old + } + + assert_equal(old, current?(:shell, user), "Shell was not reverted") + end + + def attrtest_gid(user) + old = current?(:gid, user) + + newgroup = %w{nogroup nobody staff users daemon}.find { |gid| + begin + group = Etc.getgrnam(gid) + rescue ArgumentError => detail + next + end + old != group.gid + } + group = Etc.getgrnam(newgroup) + + unless newgroup + $stderr.puts "Cannot find alternate group; skipping gid test" + return + end + + assert_raise(ArgumentError, "gid allowed a non-integer value") do + user.gid = group.name + end + + assert_nothing_raised("Failed to specify group by id") { + user.gid = group.gid + } + + assert_equal(group.gid, current?(:gid,user), "GID was not changed") + + assert_nothing_raised("Failed to change back to old gid") { + user.gid = old + } + end + + def attrtest_uid(user) + old = current?(:uid, user) + + newuid = old + while true + newuid += 1 + + if newuid - old > 1000 + $stderr.puts "Could not find extra test UID" + return + end + begin + newuser = Etc.getpwuid(newuid) + rescue ArgumentError => detail + break + end + end + + assert_nothing_raised("Failed to change user id") { + user.uid = newuid + } + + assert_equal(newuid, current?(:uid, user), "UID was not changed") + + assert_nothing_raised("Failed to change user id") { + user.uid = old + } + assert_equal(old, current?(:uid, user), "UID was not changed back") + end + + def attrtest_groups(user) + Etc.setgrent + max = 0 + while group = Etc.getgrent + if group.gid > max and group.gid < 5000 + max = group.gid + end + end + + groups = [] + main = [] + extra = [] + 5.times do |i| + i += 1 + name = "pptstgr%s" % i + tmpgroup = Puppet.type(:group).create( + :name => name, + :gid => max + i + ) + + groups << tmpgroup + + cleanup do + tmpgroup.provider.delete if tmpgroup.provider.exists? + end + + if i < 3 + main << name + else + extra << name + end + end + + # Create our test groups + assert_apply(*groups) + + # Now add some of them to our user + assert_nothing_raised { + user.model[:groups] = extra.join(",") + } + + # Some tests to verify that groups work correctly startig from nothing + # Remove our user + user.ensure = :absent + + # And add it again + user.ensure = :present + + # Make sure that the group list is added at creation time. + # This is necessary because we don't have default fakedata for groups. + assert(user.groups, "Did not retrieve group list") + + list = user.groups.split(",") + assert_equal(extra.sort, list.sort, "Group list was not set at creation time") + + # Now set to our main list of groups + assert_nothing_raised { + user.groups = main.join(",") + } + + list = user.groups.split(",") + assert_equal(main.sort, list.sort, "Group list is not equal") + end + + if Process.uid == 0 + def test_simpleuser + name = "pptest" + + assert(missing?(name), "User %s is present" % name) + + user = mkuser(name) + + eachstate do |state| + if val = fakedata(user.name, state) + user.model[state] = val + end + end + + @@tmpusers << name + + assert_nothing_raised { + user.create + } + + assert_equal("Puppet Testing User pptest", + user.comment, + "Comment was not set") + + assert_nothing_raised { + user.delete + } + + assert(missing?(user.name), "User was not deleted") + end + + def test_alluserstates + user = nil + name = "pptest" + + assert(missing?(name), "User %s is present" % name) + + user = mkuser(name) + + eachstate do |state| + if val = fakedata(user.name, state) + user.model[state] = val + end + end + + @@tmpusers << name + + assert_nothing_raised { + user.create + } + assert_equal("Puppet Testing User pptest", user.comment, + "Comment was not set") + + tests = Puppet::Type.type(:user).validstates + + just = nil + tests.each { |test| + next unless test == :groups + if self.respond_to?("attrtest_%s" % test) + self.send("attrtest_%s" % test, user) + else + Puppet.err "Not testing attr %s of user" % test + end + } + + assert_nothing_raised { + user.delete + } + end + + # This is a weird method that shows how annoying the interface between + # types and providers is. Grr. + def test_duplicateIDs + user1 = mkuser("user1") + user1.create + user1.uid = 125 + user2 = mkuser("user2") + user2.model[:uid] = 125 + + cleanup do + user1.ensure = :absent + user2.ensure = :absent + end + + # Not all OSes fail here, so we can't test that it doesn't work with + # it off, only that it does work with it on. + assert_nothing_raised { + user2.model[:allowdupe] = :true + } + assert_nothing_raised { user2.create } + assert_equal(:present, user2.ensure, + "User did not get created") + end + else + $stderr.puts "Not root; skipping user creation/modification tests" + end + + # Here is where we test individual providers + def test_useradd_flags + useradd = nil + assert_nothing_raised { + useradd = Puppet::Type.type(:user).provider(:useradd) + } + assert(useradd, "Did not retrieve useradd provider") + + user = nil + assert_nothing_raised { + fakemodel = fakemodel(:user, @me) + user = useradd.new(fakemodel) + } + + assert_equal("-d", user.send(:flag, :home), + "Incorrect home flag") + + assert_equal("-s", user.send(:flag, :shell), + "Incorrect shell flag") + end +end + +# $Id$ diff --git a/test/puppet/utiltest.rb b/test/puppet/utiltest.rb index ebf61e0f0..c2dcdada5 100755 --- a/test/puppet/utiltest.rb +++ b/test/puppet/utiltest.rb @@ -294,6 +294,48 @@ class TestPuppetUtil < Test::Unit::TestCase assert(Process.euid == 0, "UID did not get reset") end end + + def test_proxy + klass = Class.new do + attr_accessor :hash + class << self + attr_accessor :ohash + end + end + klass.send(:include, Puppet::Util) + + klass.ohash = {} + + inst = klass.new + inst.hash = {} + assert_nothing_raised do + Puppet::Util.proxy klass, :hash, "[]", "[]=", :clear, :delete + end + + assert_nothing_raised do + Puppet::Util.classproxy klass, :ohash, "[]", "[]=", :clear, :delete + end + + assert_nothing_raised do + inst[:yay] = "boo" + inst["cool"] = :yayness + end + + [:yay, "cool"].each do |var| + assert_equal(inst.hash[var], inst[var], + "Var %s did not take" % var) + end + + assert_nothing_raised do + klass[:Yay] = "boo" + klass["Cool"] = :yayness + end + + [:Yay, "Cool"].each do |var| + assert_equal(inst.hash[var], inst[var], + "Var %s did not take" % var) + end + end end # $Id$ diff --git a/test/puppettest.rb b/test/puppettest.rb index cc7ff2ccc..a49ceb175 100644 --- a/test/puppettest.rb +++ b/test/puppettest.rb @@ -9,6 +9,100 @@ require 'test/unit' module TestPuppet include ObjectSpace + # A baseclass for the faketypes. + class FakeModel < Hash + class << self + attr_accessor :name + @name = :fakemodel + end + + def self.validstates + Puppet::Type.type(@name).validstates + end + + def self.validstate?(name) + Puppet::Type.type(@name).validstate?(name) + end + + def initialize(name) + self[:name] = name + end + + def inspect + "%s(%s)" % [self.class.to_s.sub(/.+::/, ''), super()] + end + + def name + self[:name] + end + end + + class FakeProvider + attr_accessor :model + class << self + attr_accessor :name, :model, :methods + end + + # A very low number, so these never show up as defaults via the standard + # algorithms. + def self.defaultnum + -50 + end + + # Set up methods to fake things + def self.apimethods(*ary) + @model.validstates.each do |state| + ary << state unless ary.include? state + end + attr_accessor *ary + + @methods = ary + end + + def self.initvars + @calls = Hash.new do |hash, key| + hash[key] = 0 + end + end + + def self.suitable? + true + end + + def initialize(model) + @model = model + end + end + + @@fakemodels = {} + @@fakeproviders = {} + + def fakemodel(type, name, options = {}) + type = type.intern if type.is_a? String + unless @@fakemodels.include? type + @@fakemodels[type] = Class.new(FakeModel) + @@fakemodels[type].name = type + end + + obj = @@fakemodels[type].new(name) + obj[:name] = name + options.each do |name, val| + obj[name] = val + end + obj + end + + def fakeprovider(type, model) + type = type.intern if type.is_a? String + unless @@fakeproviders.include? type + @@fakeproviders[type] = Class.new(FakeModel) do + @name = type + end + end + + @@fakeproviders[type].new(model) + end + def gcdebug(type) Puppet.warning "%s: %s" % [type, ObjectSpace.each_object(type) { |o| }] end @@ -119,6 +213,19 @@ module TestPuppet @@cleaners << block end + def setme + # retrieve the user name + id = %x{id}.chomp + if id =~ /uid=\d+\(([^\)]+)\)/ + @me = $1 + else + puts id + end + unless defined? @me + raise "Could not retrieve user name; 'id' did not work" + end + end + def teardown stopservices diff --git a/test/server/pelement.rb b/test/server/pelement.rb index 7bff1cb26..5d5b5ceb4 100644 --- a/test/server/pelement.rb +++ b/test/server/pelement.rb @@ -27,11 +27,8 @@ class TestPElementServer < Test::Unit::TestCase obj.retrieve end - assert(obj.insync?, "Described %s[%s] is not in sync" % - [trans.type, name]) - if trans.type == :package - assert_equal(Puppet::Type.type(:package).default, obj[:type]) + assert_equal(Puppet::Type.type(:package).defaultprovider.name, obj[:provider]) end end type.clear diff --git a/test/types/basic.rb b/test/types/basic.rb index 1c41d37bc..f734cb5a0 100644 --- a/test/types/basic.rb +++ b/test/types/basic.rb @@ -38,7 +38,7 @@ class TestBasic < Test::Unit::TestCase assert_nothing_raised() { @sleeper = Puppet.type(:service).create( :name => "sleeper", - :type => "init", + :provider => "init", :path => File.join($puppetbase,"examples/root/etc/init.d"), :hasstatus => true, :ensure => :running diff --git a/test/types/cron.rb b/test/types/cron.rb index fee09b360..caada9f91 100755 --- a/test/types/cron.rb +++ b/test/types/cron.rb @@ -15,16 +15,8 @@ class TestCron < Test::Unit::TestCase include TestPuppet def setup super - # retrieve the user name - id = %x{id}.chomp - if id =~ /uid=\d+\(([^\)]+)\)/ - @me = $1 - else - puts id - end - unless defined? @me - raise "Could not retrieve user name; 'id' did not work" - end + + setme() # god i'm lazy @crontype = Puppet.type(:cron) diff --git a/test/types/group.rb b/test/types/group.rb index cb9b988ed..508b8436c 100755 --- a/test/types/group.rb +++ b/test/types/group.rb @@ -1,11 +1,9 @@ if __FILE__ == $0 $:.unshift '..' $:.unshift '../../lib' - $puppetbase = "../../../../language/trunk" + $puppetbase = "../.." end -# $Id$ - require 'etc' require 'puppet/type' require 'puppettest' @@ -13,18 +11,40 @@ require 'test/unit' class TestGroup < Test::Unit::TestCase include TestPuppet + + p = Puppet::Type.type(:group).provide :fake, :parent => TestPuppet::FakeProvider do + @name = :fake + apimethods :ensure, :gid + + def create + @ensure = :present + end + + def delete + @ensure = :absent + end + + def exists? + if defined? @ensure and @ensure == :present + true + else + false + end + end + end + + FakeGroupProvider = p + + @@fakeproviders[:group] = p + def setup super - @@tmpgroups = [] + Puppet::Type.type(:group).defaultprovider = FakeGroupProvider end def teardown Puppet.type(:group).clear - @@tmpgroups.each { |group| - unless missing?(group) - remove(group) - end - } + Puppet::Type.type(:group).defaultprovider = nil super end @@ -38,60 +58,6 @@ class TestGroup < Test::Unit::TestCase return group end - case Facter["operatingsystem"].value - when "Darwin": - def missing?(group) - output = %x{nidump -r /groups/#{group} / 2>/dev/null}.chomp - - if output == "" - return true - else - return false - end - - assert_equal("", output, "Group %s is present:\n%s" % [group, output]) - end - - def gid(name) - %x{nireport / /groups name gid}.split("\n").each { |line| - group, id = line.chomp.split(/\s+/) - assert(id =~ /^-?\d+$/, "Group id %s for %s is not a number" % - [id, group]) - if group == name - return Integer(id) - end - } - - return nil - end - - def remove(group) - system("niutil -destroy / /groups/%s" % group) - end - else - def missing?(group) - begin - obj = Etc.getgrnam(group) - return false - rescue ArgumentError - return true - end - end - - def gid(name) - assert_nothing_raised { - obj = Etc.getgrnam(name) - return obj.gid - } - - return nil - end - - def remove(group) - system("groupdel %s" % group) - end - end - def groupnames %x{groups}.chomp.split(/ /) end @@ -101,99 +67,52 @@ class TestGroup < Test::Unit::TestCase end def attrtest_ensure(group) - old = group.is(:ensure) group[:ensure] = :absent comp = newcomp("ensuretest", group) - assert_apply(group) - assert(missing?(group.name), "User is still present") + assert_apply(comp) + assert_equal(:absent, group.provider.ensure, "Group is still present") group[:ensure] = :present assert_events([:group_created], comp) - assert(!missing?(group.name), "User is absent") + assert_equal(:present, group.provider.ensure, "Group is absent") group[:ensure] = :absent trans = assert_events([:group_removed], comp) + assert_equal(:absent, group.provider.ensure, "Group is present") assert_rollback_events(trans, [:group_created], "group") - - group[:ensure] = old - assert_apply(group) + assert_equal(:present, group.provider.ensure, "Group is absent") end + # This is a bit odd, since we're not actually doing anything on the machine. + # Just make sure we can set the gid and that it will work correctly. def attrtest_gid(group) - obj = nil - #assert_nothing_raised { - # obj = Etc.getgrnam(group[:name]) - #} - group.retrieve - old = gid(group[:name]) - comp = newcomp("gidtest", group) - - group[:gid] = old - - trans = assert_events([], comp, "group") - - newgid = old - while true - newgid += 1 - - if newgid - old > 1000 - $stderr.puts "Could not find extra test UID" - return - end - begin - Etc.getgrgid(newgid) - rescue ArgumentError => detail - break - end - end - - assert_nothing_raised("Failed to change group id") { - group[:gid] = newgid - } - - trans = assert_events([:group_modified], comp, "group") - curgid = nil + # Check the validation. assert_nothing_raised { - curgid = gid(group[:name]) + group[:gid] = "15" } - assert_equal(newgid, curgid, "GID was not changed") + assert_equal(15, group.should(:gid), + "Did not convert gid to number") - assert_rollback_events(trans, [:group_modified], "group") + comp = newcomp(group) + trans = assert_events([:group_modified], comp, "group") + assert_equal(15, group.provider.gid, "GID was not changed") assert_nothing_raised { - curgid = gid(group[:name]) + group[:gid] = 16 } - assert_equal(old, curgid, "UID was not reverted") - end - - # Disabled, because it was testing implementation, not function - def disabled_test_eachmethod - obj = Etc.getgrnam(groupnames()[0]) - - assert(obj, "Could not retrieve test group object") + assert_equal(16, group.should(:gid), + "Did not keep gid as number") - Puppet.type(:group).validstates.each { |name, state| - assert_nothing_raised { - method = state.infomethod - assert(method, "State %s has no infomethod" % name) - assert(obj.respond_to?(method), - "State %s has an invalid method %s" % - [name, method] - ) - } + # Now switch to 16 + trans = assert_events([:group_modified], comp, "group") + assert_equal(16, group.provider.gid, "GID was not changed") - assert_nothing_raised { - method = state.infomethod - assert(method, "State %s has no infomethod" % name) - assert(obj.respond_to?(method), - "State %s has an invalid method %s" % - [name, method] - ) - } - } + # And then rollback + assert_rollback_events(trans, [:group_modified], "group") + assert_equal(15, group.provider.gid, "GID was not changed") end def test_owngroups @@ -205,119 +124,52 @@ class TestGroup < Test::Unit::TestCase :name => group, :check => [:gid] ) - - comp = newcomp("grouptest %s" % group, gobj) } - #trans = nil + # Set a fake gid + gobj.provider.gid = rand(100) + assert_nothing_raised { gobj.retrieve - #trans = comp.evaluate } assert(gobj.is(:gid), "Failed to retrieve gid") } end - # Test that we can query things - # It'd be nice if we could automate this... - def test_checking - require 'etc' + def test_mkgroup + gobj = nil + comp = nil + name = "pptestgr" - name = nil - assert_nothing_raised { - name = Etc.getgrgid(Process.gid).name - } - user = nil assert_nothing_raised { - checks = Puppet.type(:group).validstates - user = Puppet.type(:group).create( + gobj = Puppet.type(:group).create( :name => name, - :check => checks + :ensure => :present ) - } - assert_nothing_raised { - user.retrieve + comp = newcomp("groupmaker %s" % name, gobj) } - assert_equal(Process.gid, user.is(:gid), "Retrieved UID does not match") - end + trans = assert_events([:group_created], comp, "group") - if Process.uid == 0 - def test_mkgroup - gobj = nil - comp = nil - name = "pptestgr" - - #os = Facter["operatingsystem"].value - - #if os == "Darwin" - # obj = nil - # assert_nothing_raised { - # obj = Etc.getgrnam(name) - # } - # assert_equal(-2, obj.gid, "Darwin GID is not -2") - #else - #assert_raise(ArgumentError) { - # obj = Etc.getgrnam(name) - #} - #end - assert(missing?(name), "Group %s is still present" % name) - assert_nothing_raised { - gobj = Puppet.type(:group).create( - :name => name, - :ensure => :present - ) - - comp = newcomp("groupmaker %s" % name, gobj) - } - - @@tmpgroups << name - trans = assert_events([:group_created], comp, "group") - - obj = nil - assert_nothing_raised { - obj = Etc.getgrnam(name) - } - assert(!missing?(name), "Group %s is missing" % name) + assert(gobj.provider.exists?, + "Did not create group") - tests = Puppet.type(:group).validstates + tests = Puppet.type(:group).validstates - gobj.retrieve - tests.each { |test| - if self.respond_to?("attrtest_%s" % test) - self.send("attrtest_%s" % test, gobj) - else - #$stderr.puts "Not testing attr %s of group" % test - end - } - - assert_rollback_events(trans, [:group_removed], "group") - - assert(missing?(name), "Group %s is still present" % name) - end - - # groupadd -o is broken in FreeBSD. - unless Facter["operatingsystem"].value == "FreeBSD" - def test_duplicateIDs - group1 = mkgroup("group1", :gid => 125) - group2 = mkgroup("group2", :gid => 125) + gobj.retrieve + tests.each { |test| + if self.respond_to?("attrtest_%s" % test) + self.send("attrtest_%s" % test, gobj) + else + #$stderr.puts "Not testing attr %s of group" % test + end + } - assert_apply(group1) + assert_rollback_events(trans, [:group_removed], "group") - # Not all OSes fail here, so we can't test that it doesn't work with - # it off, only that it does work with it on. - assert_nothing_raised { - group2[:allowdupe] = true - } - assert_apply(group2) - group2.retrieve - assert_equal(:present, group2.state(:ensure).is, - "Group did not get created") - end - end - else - $stderr.puts "Not running as root; skipping group creation tests." + assert(! gobj.provider.exists?, + "Did not delete group") end end diff --git a/test/types/package.rb b/test/types/package.rb index d4ddc14c9..779c693fe 100644 --- a/test/types/package.rb +++ b/test/types/package.rb @@ -11,7 +11,7 @@ require 'facter' $platform = Facter["operatingsystem"].value -unless Puppet.type(:package).default +unless Puppet.type(:package).defaultprovider puts "No default package type for %s; skipping package tests" % $platform else @@ -54,14 +54,15 @@ class TestPackages < Test::Unit::TestCase list.each { |pkg, source| hash = {:name => pkg} if useensure - hash[:ensure] = "latest" + hash[:ensure] = "installed" end if source source = source[0] if source.is_a? Array hash[:source] = source end + # Override the default package type for our test packages. if Facter["operatingsystem"].value == "Darwin" - hash[:type] = "darwinport" + hash[:provider] = "darwinport" end obj = Puppet.type(:package).create(hash) assert(pkg, "Could not create package") @@ -103,6 +104,8 @@ class TestPackages < Test::Unit::TestCase retval = {"aop" => nil} when "FreeBSD": retval = {"yahtzee" => nil} + when "RedHat": + retval = {"puppet" => "/home/luke/rpm/RPMS/i386/puppet-0.16.1-1.i386.rpm"} else Puppet.notice "No test packages for %s" % $platform end @@ -168,18 +171,6 @@ class TestPackages < Test::Unit::TestCase } end - def test_specifypkgtype - pkg = nil - assert_nothing_raised { - pkg = Puppet.type(:package).create( - :name => "mypkg", - :type => "yum" - ) - } - assert(pkg, "Did not create package") - assert_equal(:yum, pkg[:type]) - end - def test_latestpkg mkpkgs { |pkg| next unless pkg.respond_to? :latest @@ -194,11 +185,8 @@ class TestPackages < Test::Unit::TestCase def test_listing pkgtype = Puppet::Type.type(:package) - # Heh - defaulttype = pkgtype.pkgtype(pkgtype.default) - assert_nothing_raised("Could not list packages") do - defaulttype.list + pkgtype.list end end @@ -213,14 +201,14 @@ class TestPackages < Test::Unit::TestCase pkg.retrieve } - if pkg.insync? or pkg.is(:ensure) != :absent + if pkg.provider.query Puppet.notice "Test package %s is already installed; please choose a different package for testing" % pkg next end comp = newcomp("package", pkg) - assert_events([:package_created], comp, "package") + assert_events([:package_installed], comp, "package") pkg.retrieve @@ -243,7 +231,7 @@ class TestPackages < Test::Unit::TestCase pkg[:ensure] = "latest" } - assert_events([:package_created], comp, "package") + assert_events([:package_installed], comp, "package") pkg.retrieve assert(pkg.insync?, "After install, package is not insync") @@ -306,9 +294,9 @@ class TestPackages < Test::Unit::TestCase modpkg(pkg) - assert(pkg.latest, "Could not retrieve latest value") + assert(pkg.provider.latest, "Could not retrieve latest value") - assert_events([:package_created], pkg) + assert_events([:package_installed], pkg) assert_nothing_raised { pkg.retrieve @@ -345,7 +333,8 @@ class TestPackages < Test::Unit::TestCase pkg = Puppet::Type.type(:package).create( :name => "pkgtesting", :source => "/Users/luke/Documents/Puppet/pkgtesting.pkg", - :ensure => :present + :ensure => :present, + :provider => :apple ) } @@ -362,7 +351,7 @@ class TestPackages < Test::Unit::TestCase @@tmpfiles << "/tmp/file" @@tmpfiles << "/Library/Receipts/pkgtesting.pkg" - assert_events([:package_created], pkg, "package") + assert_events([:package_installed], pkg, "package") assert_nothing_raised { pkg.retrieve @@ -375,15 +364,15 @@ class TestPackages < Test::Unit::TestCase end # Yay, gems. They're special because any OS can test them. - if %x{which gem 2>/dev/null}.chomp != "" + if Puppet::Type.type(:package).provider(:gem).suitable? def test_list_gems gems = nil assert_nothing_raised { - gems = Puppet::Type.type(:package).pkgtype(:gem).list + gems = Puppet::Type.type(:package).provider(:gem).list } gems.each do |gem| - assert_equal(:gem, gem[:type], + assert_equal(:gem, gem[:provider], "Type was not set correctly") end end @@ -396,7 +385,7 @@ class TestPackages < Test::Unit::TestCase :name => name, :version => "0.0.2", :ensure => "installed", - :type => :gem + :provider => :gem ) } @@ -410,7 +399,7 @@ class TestPackages < Test::Unit::TestCase return end - assert_events([:package_created], gem) + assert_events([:package_installed], gem) assert_nothing_raised { gem.retrieve @@ -421,7 +410,7 @@ class TestPackages < Test::Unit::TestCase latest = nil assert_nothing_raised { - latest = gem.latest + latest = gem.provider.latest } assert(latest != gem[:version], "Did not correctly find latest value") @@ -440,12 +429,13 @@ class TestPackages < Test::Unit::TestCase end else + $stderr.puts "Install gems for gem tests" def test_nogems_nofailures obj = nil assert_nothing_raised do Puppet::Type.newpackage( :name => "yayness", - :type => "gem", + :provider => "gem", :ensure => "installed" ) end @@ -455,7 +445,7 @@ class TestPackages < Test::Unit::TestCase end end end - if ["Fedora", "RedHat", "CentOS"].include?(Facter["operatingsystem"].value) and + if Puppet.type(:package).provider(:rpm).suitable? and FileTest.exists?("/home/luke/rpm/RPMS/i386/puppet-server-0.16.1-1.i386.rpm") # We have a special test here, because we don't actually want to install the @@ -464,73 +454,36 @@ class TestPackages < Test::Unit::TestCase pkg = nil assert_nothing_raised { pkg = Puppet::Type.type(:package).create( - :type => :rpm, + :provider => :rpm, :name => "puppet-server", :source => "/home/luke/rpm/RPMS/i386/puppet-server-0.16.1-1.i386.rpm" ) } - assert_equal("0.16.1-1", pkg.latest, "RPM did not provide correct value for latest") + assert_equal("0.16.1-1", pkg.provider.latest, "RPM did not provide correct value for latest") end end - if Facter["operatingsystem"].value == "Solaris" - if pkgget = %x{which pkg-get 2>/dev/null}.chomp and pkgget != "" - # FIXME The packaging crap needs to be rewritten to support testing - # multiple package types on the same platform. - def test_list_blastwave - pkgs = nil - assert_nothing_raised { - pkgs = Puppet::Type.type(:package).pkgtype(:blastwave).list - } - - pkgs.each do |pkg| - if pkg[:name] =~ /^CSW/ - assert_equal(:blastwave, pkg[:type], - "Type was not set correctly") - end + def test_packagedefaults + should = case Facter["operatingsystem"].value + when "Debian": :apt + when "Darwin": :apple + when "RedHat": :rpm + when "Fedora": :yum + when "FreeBSD": :ports + when "OpenBSD": :openbsd + when "Solaris": :sun end - end - def test_install_blastwave - pkg = nil - name = "cabextract" - assert_nothing_raised { - pkg = Puppet::Type.newpackage( - :name => name, - :ensure => "installed", - :type => :blastwave - ) - } + default = Puppet.type(:package).defaultprovider + assert(default, "No default package provider for %s" % + Facter["operatingsystem"].value) - assert_nothing_raised { - pkg.retrieve - } - if pkg.is(:ensure) != :absent - p pkg.is(:ensure) - $stderr.puts "Cannot test pkg installation; %s is already installed" % - name - return + if should + assert_equal(should, default.name, + "Incorrect default package format") end - - assert_events([:package_created], pkg) - - assert_nothing_raised { - pkg.retrieve - } - - latest = nil - assert_nothing_raised { - latest = pkg.latest - } - pkg[:ensure] = :absent - - assert_events([:package_removed], pkg) - end - else - $stderr.puts "No pkg-get scripting; skipping blastwave tests" - end end end end diff --git a/test/types/query.rb b/test/types/query.rb index 451291cac..121670012 100644 --- a/test/types/query.rb +++ b/test/types/query.rb @@ -34,7 +34,7 @@ class TestQuery < Test::Unit::TestCase unless Puppet.type(:service).has_key?("sleeper") Puppet.type(:service).create( :name => "sleeper", - :type => "init", + :provider => "init", :path => File.join($puppetbase,"examples/root/etc/init.d"), :hasstatus => true, :check => [:ensure] @@ -82,6 +82,7 @@ class TestQuery < Test::Unit::TestCase def test_service service = service() + assert(service, "Did not get service object") service.eachstate { |state| assert_nil(state.is) } diff --git a/test/types/service.rb b/test/types/service.rb index 3a8ba089a..176f84818 100644 --- a/test/types/service.rb +++ b/test/types/service.rb @@ -118,7 +118,7 @@ class TestLocalService < Test::Unit::TestCase end def mktestsvcs - tstsvcs.collect { |svc,svcargs| + list = tstsvcs.collect { |svc,svcargs| args = svcargs.dup args[:name] = svc Puppet.type(:service).create(args) @@ -167,11 +167,9 @@ class TestLocalService < Test::Unit::TestCase # test refreshing it assert_nothing_raised() { - service.refresh + service.provider.refresh } - assert(service.respond_to?(:refresh)) - # now stop it assert_nothing_raised() { service[:ensure] = :stopped @@ -229,7 +227,7 @@ class TestLocalService < Test::Unit::TestCase mktestsvcs.each { |svc| val = nil assert_nothing_raised("Could not get status") { - val = svc.status + val = svc.provider.status } assert_instance_of(Symbol, val) } @@ -242,29 +240,28 @@ class TestLocalService < Test::Unit::TestCase mktestsvcs.each { |svc| startstate = nil assert_nothing_raised("Could not get status") { - startstate = svc.status + startstate = svc.provider.status } cycleservice(svc) svc[:ensure] = startstate assert_apply(svc) - Puppet.type(:service).clear Puppet.type(:component).clear } end def test_serviceenabledisable mktestsvcs.each { |svc| + assert(svc[:name], "Service has no name") startstate = nil svc[:check] = :enable assert_nothing_raised("Could not get status") { - startstate = svc.enabled? + startstate = svc.provider.enabled? } cycleenable(svc) svc[:enable] = startstate assert_apply(svc) - Puppet.type(:service).clear Puppet.type(:component).clear } end @@ -299,7 +296,6 @@ class TestLocalService < Test::Unit::TestCase svc[:enable] = startenable svc[:ensure] = startensure assert_apply(svc) - Puppet.type(:service).clear Puppet.type(:component).clear end end diff --git a/test/types/state.rb b/test/types/state.rb index 406b9af6b..638372afe 100644 --- a/test/types/state.rb +++ b/test/types/state.rb @@ -32,6 +32,7 @@ class TestState < Test::Unit::TestCase def test_newvalue state = newstate() + # These are bogus because they don't define events. :/ assert_nothing_raised { state.newvalue(:one) do @is = 1 @@ -51,6 +52,7 @@ class TestState < Test::Unit::TestCase } assert_equal(:one, inst.should) + ret = nil assert_nothing_raised { inst.set_one } assert_equal(1, inst.is) @@ -87,6 +89,43 @@ class TestState < Test::Unit::TestCase assert_equal("yayness".upcase, inst.is) end + + def test_newvalue_event_option + state = newstate() + + assert_nothing_raised do + state.newvalue(:myvalue, :event => :fake_valued) do + @is = :valued + end + state.newvalue(:other, :event => "fake_other") do + @is = :valued + end + end + inst = newinst(state) + + assert_nothing_raised { + inst.should = :myvalue + } + + ret = nil + assert_nothing_raised { + ret = inst.sync + } + + assert_equal(:fake_valued, ret, + "Event did not get returned correctly") + + assert_nothing_raised { + inst.should = :other + } + + assert_nothing_raised { + ret = inst.sync + } + + assert_equal(:fake_other, ret, + "Event did not get returned correctly") + end end # $Id$ diff --git a/test/types/type.rb b/test/types/type.rb index 333c5683f..48e0b4323 100644 --- a/test/types/type.rb +++ b/test/types/type.rb @@ -35,20 +35,22 @@ class TestType < Test::Unit::TestCase # next #end - assert( - type.namevar, - "Failed to retrieve namevar for %s" % name - ) + assert_nothing_raised { + assert( + type.namevar, + "Failed to retrieve namevar for %s" % name + ) - assert_not_nil( - type.states, - "States for %s are nil" % name - ) + assert_not_nil( + type.states, + "States for %s are nil" % name + ) - assert_not_nil( - type.validstates, - "Valid states for %s are nil" % name - ) + assert_not_nil( + type.validstates, + "Valid states for %s are nil" % name + ) + } } end @@ -464,6 +466,63 @@ end assert_equal("yaytest", File.read(path), "Exec did not correctly copy file.") end + + def test_newstate_options + # Create a type with a fake provider + providerclass = Class.new do + def method_missing(method, *args) + return method + end + end + self.class.const_set("ProviderClass", providerclass) + + type = Puppet::Type.newtype(:mytype) do + newparam(:name) do + isnamevar + end + def provider + @provider ||= ProviderClass.new + + @provider + end + end + + # Now make a state with no options. + state = nil + assert_nothing_raised do + state = type.newstate(:noopts) do + end + end + + # Now create an instance + obj = type.create(:name => :myobj) + + inst = state.new(:parent => obj) + + # And make sure it's correctly setting @is + ret = nil + assert_nothing_raised { + ret = inst.retrieve + } + + assert_equal(:noopts, inst.is) + + # Now create a state with a different way of doing it + state = nil + assert_nothing_raised do + state = type.newstate(:setretrieve, :retrieve => :yayness) + end + + inst = state.new(:parent => obj) + + # And make sure it's correctly setting @is + ret = nil + assert_nothing_raised { + ret = inst.retrieve + } + + assert_equal(:yayness, inst.is) + end end # $Id$ diff --git a/test/types/user.rb b/test/types/user.rb index 87e90ff22..008f39272 100755 --- a/test/types/user.rb +++ b/test/types/user.rb @@ -1,11 +1,9 @@ if __FILE__ == $0 $:.unshift '..' $:.unshift '../../lib' - $puppetbase = "../../../../language/trunk" + $puppetbase = "../.." end -# $Id$ - require 'etc' require 'puppet/type' require 'puppettest' @@ -13,90 +11,37 @@ require 'test/unit' class TestUser < Test::Unit::TestCase include TestPuppet - def setup - super - @@tmpusers = [] - end - def teardown - @@tmpusers.each { |user| - unless missing?(user) - remove(user) + p = Puppet::Type.type(:user).provide :fake, :parent => TestPuppet::FakeProvider do + @name = :fake + apimethods + def create + @ensure = :present + @model.eachstate do |state| + next if state.name == :ensure + state.sync end - } - super - #Puppet.type(:user).clear - end - - case Facter["operatingsystem"].value - when "Darwin": - def missing?(user) - output = %x{nidump -r /users/#{user} / 2>/dev/null}.chomp - - if output == "" - return true - else - return false - end - - assert_equal("", output, "User %s is present:\n%s" % [user, output]) end - def current?(param, name) - state = Puppet.type(:user).states.find { |st| - st.name == param - } - - output = %x{nireport / /users name #{state.netinfokey}} - output.split("\n").each { |line| - if line =~ /^(\w+)\s+(.+)$/ - user = $1 - id = $2.sub(/\s+$/, '') - if user == name - if id =~ /^[-0-9]+$/ - return Integer(id) - else - return id - end - end - else - raise "Could not match %s" % line - end - } - - return nil + def delete + @ensure = :absent + @model.eachstate do |state| + send(state.name.to_s + "=", :absent) + end end - def remove(user) - system("niutil -destroy / /users/%s" % user) - end - else - def missing?(user) - begin - obj = Etc.getpwnam(user) - return false - rescue ArgumentError - return true + def exists? + if defined? @ensure and @ensure == :present + true + else + false end end + end - def current?(param, name) - state = Puppet.type(:user).states.find { |st| - st.name == param - } - - assert_nothing_raised { - obj = Etc.getpwnam(name) - return obj.send(state.posixmethod) - } - - return nil - end + FakeUserProvider = p - def remove(user) - system("userdel %s" % user) - end - end + @@fakeproviders[:group] = p def findshell(old = nil) %w{/bin/sh /bin/bash /sbin/sh /bin/ksh /bin/zsh /bin/csh /bin/tcsh @@ -110,6 +55,16 @@ class TestUser < Test::Unit::TestCase } end + def setup + super + Puppet::Type.type(:user).defaultprovider = FakeUserProvider + end + + def teardown + Puppet::Type.type(:user).defaultprovider = nil + super + end + def mkuser(name) user = nil assert_nothing_raised { @@ -122,19 +77,21 @@ class TestUser < Test::Unit::TestCase ) } + assert(user, "Did not create user") + return user end def attrtest_ensure(user) - old = user.is(:ensure) + old = user.provider.ensure user[:ensure] = :absent comp = newcomp("ensuretest", user) assert_apply(user) - assert(missing?(user.name), "User is still present") + assert(!user.provider.exists?, "User is still present") user[:ensure] = :present assert_events([:user_created], comp) - assert(!missing?(user.name), "User is absent") + assert(user.provider.exists?, "User is absent") user[:ensure] = :absent trans = assert_events([:user_removed], comp) @@ -146,19 +103,19 @@ class TestUser < Test::Unit::TestCase def attrtest_comment(user) user.retrieve - old = user.is(:comment) + old = user.provider.comment user[:comment] = "A different comment" comp = newcomp("commenttest", user) - trans = assert_events([:user_modified], comp, "user") + trans = assert_events([:user_changed], comp, "user") - assert_equal("A different comment", current?(:comment, user[:name]), + assert_equal("A different comment", user.provider.comment, "Comment was not changed") - assert_rollback_events(trans, [:user_modified], "user") + assert_rollback_events(trans, [:user_changed], "user") - assert_equal(old, current?(:comment, user[:name]), + assert_equal(old, user.provider.comment, "Comment was not reverted") end @@ -166,24 +123,24 @@ class TestUser < Test::Unit::TestCase obj = nil comp = newcomp("hometest", user) - old = current?(:home, user[:name]) + old = user.provider.home user[:home] = old trans = assert_events([], comp, "user") user[:home] = "/tmp" - trans = assert_events([:user_modified], comp, "user") + trans = assert_events([:user_changed], comp, "user") - assert_equal("/tmp", current?(:home, user[:name]), "Home was not changed") + assert_equal("/tmp", user.provider.home, "Home was not changed") - assert_rollback_events(trans, [:user_modified], "user") + assert_rollback_events(trans, [:user_changed], "user") - assert_equal(old, current?(:home, user[:name]), "Home was not reverted") + assert_equal(old, user.provider.home, "Home was not reverted") end def attrtest_shell(user) - old = current?(:shell, user[:name]) + old = user.provider.shell comp = newcomp("shelltest", user) user[:shell] = old @@ -199,19 +156,21 @@ class TestUser < Test::Unit::TestCase user[:shell] = newshell - trans = assert_events([:user_modified], comp, "user") + trans = assert_events([:user_changed], comp, "user") - assert_equal(newshell, current?(:shell, user[:name]), + user.retrieve + assert_equal(newshell, user.provider.shell, "Shell was not changed") - assert_rollback_events(trans, [:user_modified], "user") + assert_rollback_events(trans, [:user_changed], "user") + user.retrieve - assert_equal(old, current?(:shell, user[:name]), "Shell was not reverted") + assert_equal(old, user.provider.shell, "Shell was not reverted") end def attrtest_gid(user) obj = nil - old = current?(:gid,user.name) + old = user.provider.gid comp = newcomp("gidtest", user) user.retrieve @@ -239,7 +198,7 @@ class TestUser < Test::Unit::TestCase user[:gid] = newgid } - trans = assert_events([:user_modified], comp, "user") + trans = assert_events([:user_changed], comp, "user") # then by id newgid = Etc.getgrnam(newgid).gid @@ -252,23 +211,21 @@ class TestUser < Test::Unit::TestCase assert_events([], comp, "user") - assert_equal(newgid, current?(:gid,user[:name]), "GID was not changed") + assert_equal(newgid, user.provider.gid, "GID was not changed") - assert_rollback_events(trans, [:user_modified], "user") + assert_rollback_events(trans, [:user_changed], "user") - assert_equal(old, current?(:gid,user[:name]), "GID was not reverted") + assert_equal(old, user.provider.gid, "GID was not reverted") end def attrtest_uid(user) obj = nil comp = newcomp("uidtest", user) - old = current?(:uid, user[:name]) - user[:uid] = old + user.provider.uid = 1 - trans = assert_events([], comp, "user") - - newuid = old + old = 1 + newuid = 1 while true newuid += 1 @@ -287,13 +244,13 @@ class TestUser < Test::Unit::TestCase user[:uid] = newuid } - trans = assert_events([:user_modified], comp, "user") + trans = assert_events([:user_changed], comp, "user") - assert_equal(newuid, current?(:uid, user[:name]), "UID was not changed") + assert_equal(newuid, user.provider.uid, "UID was not changed") - assert_rollback_events(trans, [:user_modified], "user") + assert_rollback_events(trans, [:user_changed], "user") - assert_equal(old, current?(:uid, user[:name]), "UID was not reverted") + assert_equal(old, user.provider.uid, "UID was not reverted") end def attrtest_groups(user) @@ -311,11 +268,7 @@ class TestUser < Test::Unit::TestCase 5.times do |i| i += 1 name = "pptstgr%s" % i - groups << Puppet.type(:group).create( - :name => name, - :gid => max + i - ) - + groups << name if i < 3 main << name else @@ -323,9 +276,6 @@ class TestUser < Test::Unit::TestCase end end - # Create our test groups - assert_apply(*groups) - assert(user[:membership] == :minimum, "Membership did not default correctly") assert_nothing_raised { @@ -353,6 +303,10 @@ class TestUser < Test::Unit::TestCase user[:ensure] = :present assert_apply(user) + # Make sure that the groups are a string, not an array + assert(user.provider.groups.is_a?(String), + "Incorrectly passed an array to groups") + user.retrieve assert(user.state(:groups).is, "Did not retrieve group list") @@ -365,7 +319,7 @@ class TestUser < Test::Unit::TestCase user[:groups] = main } - assert_equal((main + extra).sort.join(","), user.state(:groups).should) + assert_equal((main + extra).sort, user.state(:groups).should.sort) assert_nothing_raised { user.retrieve @@ -393,7 +347,7 @@ class TestUser < Test::Unit::TestCase assert(!user.insync?, "User is incorrectly in sync") - assert_events([:user_modified], user) + assert_events([:user_changed], user) assert_nothing_raised { user.retrieve } @@ -401,75 +355,35 @@ class TestUser < Test::Unit::TestCase list = user.state(:groups).is assert_equal(main.sort, list.sort, "Group list is not equal") - # Now delete our groups - groups.each do |group| - group[:ensure] = :absent - end - user.delete(:groups) - - assert_apply(*groups) - end - - # Disabled, because this is testing too much internal implementation - def disabled_test_eachmethod - obj = Etc.getpwuid(Process.uid) - - assert(obj, "Could not retrieve test group object") - - Puppet.type(:user).validstates.each { |name| - assert_nothing_raised { - method = state.posixmethod - assert(method, "State %s has no infomethod" % name) - assert(obj.respond_to?(method), - "State %s has an invalid method %s" % - [name, method]) - } - } end - def test_checking - require 'etc' - - name = nil - assert_nothing_raised { - name = Etc.getpwuid(Process.uid).name - } + def test_autorequire + file = tempfile() + comp = nil user = nil + group =nil + home = nil + ogroup = nil assert_nothing_raised { - checks = Puppet.type(:user).validstates user = Puppet.type(:user).create( - :name => name, - :check => checks + :name => "pptestu", + :home => file, + :gid => "pptestg", + :groups => "yayness" ) + home = Puppet.type(:file).create( + :path => file, + :ensure => "directory" + ) + group = Puppet.type(:group).create( + :name => "pptestg" + ) + ogroup = Puppet.type(:group).create( + :name => "yayness" + ) + comp = newcomp(user, group, home, ogroup) } - - assert_nothing_raised { - user.retrieve - } - - assert_equal(Process.uid, user.is(:uid), "Retrieved UID does not match") - end - - def test_autorequire - file = tempfile() - user = Puppet.type(:user).create( - :name => "pptestu", - :home => file, - :gid => "pptestg", - :groups => "yayness" - ) - home = Puppet.type(:file).create( - :path => file, - :ensure => "directory" - ) - group = Puppet.type(:group).create( - :name => "pptestg" - ) - ogroup = Puppet.type(:group).create( - :name => "yayness" - ) - comp = newcomp(user, group, home, ogroup) comp.finalize comp.retrieve @@ -478,86 +392,52 @@ class TestUser < Test::Unit::TestCase assert(user.requires?(ogroup), "User did not require other groups") end - if Process.uid == 0 - def test_simpleuser - name = "pptest" - - assert(missing?(name), "User %s is present" % name) - - user = mkuser(name) - - @@tmpusers << name - - comp = newcomp("usercomp", user) + def test_simpleuser + name = "pptest" - trans = assert_events([:user_created], comp, "user") + user = mkuser(name) - assert_equal("Puppet Testing User", current?(:comment, user[:name]), - "Comment was not set") + comp = newcomp("usercomp", user) - assert_rollback_events(trans, [:user_removed], "user") + trans = assert_events([:user_created], comp, "user") - assert(missing?(user[:name])) - end - - def test_allstates - user = nil - name = "pptest" + assert_equal(user.should(:comment), user.provider.comment, + "Comment was not set correctly") - assert(missing?(name), "User %s is present" % name) + assert_rollback_events(trans, [:user_removed], "user") - user = mkuser(name) + assert(! user.provider.exists?, "User did not get deleted") + end - @@tmpusers << name + def test_allusermodelstates + user = nil + name = "pptest" - comp = newcomp("usercomp", user) + user = mkuser(name) - trans = assert_events([:user_created], comp, "user") + assert(! user.provider.exists?, "User %s is present" % name) - user.retrieve - assert_equal("Puppet Testing User", current?(:comment, user[:name]), - "Comment was not set") + comp = newcomp("usercomp", user) - tests = Puppet.type(:user).validstates + trans = assert_events([:user_created], comp, "user") - tests.each { |test| - next unless test.to_s =~ /groups/ - if self.respond_to?("attrtest_%s" % test) - self.send("attrtest_%s" % test, user) - else - Puppet.err "Not testing attr %s of user" % test - end - } - - user[:ensure] = :absent - assert_apply(user) - end + user.retrieve + assert_equal("Puppet Testing User", user.provider.comment, + "Comment was not set") - def test_duplicateIDs - user1 = mkuser("user1") - user1[:uid] = 125 - user2 = mkuser("user2") - user2[:uid] = 125 + tests = Puppet.type(:user).validstates - cleanup do - user1[:ensure] = :absent - user2[:ensure] = :absent - assert_apply(user1, user2) + tests.each { |test| + if self.respond_to?("attrtest_%s" % test) + self.send("attrtest_%s" % test, user) + else + Puppet.err "Not testing attr %s of user" % test end + } - assert_apply(user1) - - # Not all OSes fail here, so we can't test that it doesn't work with - # it off, only that it does work with it on. - assert_nothing_raised { - user2[:allowdupe] = true - } - assert_apply(user2) - user2.retrieve - assert_equal(:present, user2.state(:ensure).is, - "User did not get created") - end - else - $stderr.puts "Not root; skipping user creation/modification tests" + user[:ensure] = :absent + assert_apply(user) end end + +# $Id$ |