summaryrefslogtreecommitdiffstats
path: root/lib/puppet/provider
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-08-14 06:21:03 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-08-14 06:21:03 +0000
commit12452ee9ca294563f2e2724ff36f179004f9846f (patch)
treebee6053e8164f4a8dbf214f1898fafecb1d61f2f /lib/puppet/provider
parent4d6120a1f77cfe76fafbe32caa5d853449562376 (diff)
downloadpuppet-12452ee9ca294563f2e2724ff36f179004f9846f.tar.gz
puppet-12452ee9ca294563f2e2724ff36f179004f9846f.tar.xz
puppet-12452ee9ca294563f2e2724ff36f179004f9846f.zip
Merging r1468 from the implementations branch with r1438 from when the branch was first created.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1469 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet/provider')
-rw-r--r--lib/puppet/provider/group/groupadd.rb29
-rw-r--r--lib/puppet/provider/group/netinfo.rb15
-rw-r--r--lib/puppet/provider/group/pw.rb31
-rw-r--r--lib/puppet/provider/nameservice.rb319
-rw-r--r--lib/puppet/provider/nameservice/netinfo.rb211
-rw-r--r--lib/puppet/provider/nameservice/objectadd.rb45
-rw-r--r--lib/puppet/provider/nameservice/pw.rb22
-rwxr-xr-xlib/puppet/provider/package/apple.rb55
-rwxr-xr-xlib/puppet/provider/package/apt.rb112
-rwxr-xr-xlib/puppet/provider/package/blastwave.rb138
-rwxr-xr-xlib/puppet/provider/package/darwinport.rb95
-rwxr-xr-xlib/puppet/provider/package/dpkg.rb115
-rwxr-xr-xlib/puppet/provider/package/freebsd.rb52
-rwxr-xr-xlib/puppet/provider/package/gem.rb108
-rwxr-xr-xlib/puppet/provider/package/openbsd.rb110
-rwxr-xr-xlib/puppet/provider/package/ports.rb104
-rwxr-xr-xlib/puppet/provider/package/rpm.rb120
-rwxr-xr-xlib/puppet/provider/package/sun.rb179
-rwxr-xr-xlib/puppet/provider/package/sunfreeware.rb15
-rwxr-xr-xlib/puppet/provider/package/yum.rb53
-rwxr-xr-xlib/puppet/provider/service/base.rb135
-rwxr-xr-xlib/puppet/provider/service/debian.rb52
-rwxr-xr-xlib/puppet/provider/service/init.rb151
-rwxr-xr-xlib/puppet/provider/service/redhat.rb45
-rwxr-xr-xlib/puppet/provider/service/smf.rb84
-rw-r--r--lib/puppet/provider/user/netinfo.rb91
-rw-r--r--lib/puppet/provider/user/pw.rb41
-rw-r--r--lib/puppet/provider/user/useradd.rb46
28 files changed, 2573 insertions, 0 deletions
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/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb
new file mode 100755
index 000000000..b8acbf326
--- /dev/null
+++ b/lib/puppet/provider/service/debian.rb
@@ -0,0 +1,52 @@
+# Manage debian services. Start/stop is the same as InitSvc, but enable/disable
+# is special.
+Puppet::Type.type(:service).provide :debian, :parent => :init do
+ desc "Debian's form of ``init``-style management. The only difference
+ is that this supports service enabling and disabling via ``update-rc.d``."
+
+ commands :update => "/usr/sbin/update-rc.d"
+ defaultfor :operatingsystem => :debian
+
+ # Remove the symlinks
+ def disable
+ cmd = %{#{command(:update)} -f #{@model[:name]} remove 2>&1}
+ self.debug "Executing '%s'" % cmd
+ output = %x{#{cmd}}
+
+ unless $? == 0
+ raise Puppet::Error, "Could not disable %s: %s" %
+ [self.name, output]
+ end
+ end
+
+ def enabled?
+ cmd = %{#{command(:update)} -n -f #{@model[:name]} remove 2>&1}
+ self.debug "Executing 'enabled' test: '%s'" % cmd
+ output = %x{#{cmd}}
+ unless $? == 0
+ raise Puppet::Error, "Could not check %s: %s" %
+ [self.name, output]
+ end
+
+ # If it's enabled, then it will print output showing removal of
+ # links.
+ if output =~ /etc\/rc[\dS].d|Nothing to do\./
+ return :true
+ else
+ return :false
+ end
+ end
+
+ def enable
+ cmd = %{#{command(:update)} #{@model[:name]} defaults 2>&1}
+ self.debug "Executing '%s'" % cmd
+ output = %x{#{cmd}}
+
+ unless $? == 0
+ raise Puppet::Error, "Could not enable %s: %s" %
+ [self.name, output]
+ end
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb
new file mode 100755
index 000000000..76cac2763
--- /dev/null
+++ b/lib/puppet/provider/service/init.rb
@@ -0,0 +1,151 @@
+# The standard init-based service type. Many other service types are
+# customizations of this module.
+Puppet::Type.type(:service).provide :init, :parent => :base do
+ desc "Standard init service management. This provider assumes that the
+ init script has 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
+
+ #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()
+ end
+
+ # 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)
+
+ # Now see if there are any included modules
+ included_modules.each do |mod|
+ next unless mod.respond_to? :name
+
+ mname = mod.name
+
+ if mpaths = Puppet.type(:service).paths(mname) and ! mpaths.empty?
+ paths += mpaths
+ end
+ end
+
+ paths.each do |path|
+ unless FileTest.directory?(path)
+ Puppet.notice "Service path %s does not exist" % path
+ next
+ end
+
+ check = [:ensure]
+
+ if public_method_defined? :enabled?
+ check << :enable
+ end
+
+ Dir.entries(path).reject { |e|
+ fullpath = File.join(path, e)
+ e =~ /^\./ or ! FileTest.executable?(fullpath)
+ }.each do |name|
+ if obj = Puppet::Type.type(:service)[name]
+ obj[:check] = check
+ else
+ Puppet::Type.type(:service).create(
+ :name => name, :check => check, :path => path
+ )
+ end
+ end
+ end
+ end
+
+ # Mark that our init script supports 'status' commands.
+ def hasstatus=(value)
+ case value
+ when true, "true": @parameters[:hasstatus] = true
+ when false, "false": @parameters[:hasstatus] = false
+ else
+ raise Puppet::Error, "Invalid 'hasstatus' value %s" %
+ value.inspect
+ end
+ end
+
+ # it'd be nice if i didn't throw the output away...
+ # this command returns true if the exit code is 0, and returns
+ # false otherwise
+ def initcmd(cmd)
+ script = self.initscript
+
+ self.debug "Executing '%s %s' as initcmd for '%s'" %
+ [script,cmd,self]
+
+ rvalue = Kernel.system("%s %s" %
+ [script,cmd])
+
+ self.debug "'%s' ran with exit status '%s'" %
+ [cmd,rvalue]
+
+
+ rvalue
+ end
+
+ # Where is our init script?
+ def initscript
+ if defined? @initscript
+ return @initscript
+ else
+ @initscript = self.search(@model[:name])
+ end
+ end
+
+ def search(name)
+ @model[:path].each { |path|
+ fqname = File.join(path,name)
+ begin
+ stat = File.stat(fqname)
+ rescue
+ # should probably rescue specific errors...
+ self.debug("Could not find %s in %s" % [name,path])
+ next
+ end
+
+ # if we've gotten this far, we found a valid script
+ return fqname
+ }
+ raise Puppet::Error, "Could not find init script for '%s'" % name
+ end
+
+ # The start command is just the init scriptwith 'start'.
+ def startcmd
+ self.initscript + " start"
+ end
+
+ # If it was specified that the init script has a 'status' command, then
+ # we just return that; otherwise, we return false, which causes it to
+ # fallback to other mechanisms.
+ def statuscmd
+ if @model[:hasstatus]
+ return self.initscript + " status"
+ else
+ return false
+ end
+ end
+
+ # The stop command is just the init script with 'stop'.
+ def stopcmd
+ self.initscript + " stop"
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/provider/service/redhat.rb b/lib/puppet/provider/service/redhat.rb
new file mode 100755
index 000000000..e599e63f1
--- /dev/null
+++ b/lib/puppet/provider/service/redhat.rb
@@ -0,0 +1,45 @@
+# Manage debian services. Start/stop is the same as InitSvc, but enable/disable
+# is special.
+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 #{@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]
+ end
+ end
+
+ def enabled?
+ begin
+ output = util_execute("/sbin/chkconfig #{@model[:name]} 2>&1").chomp
+ rescue Puppet::ExecutionFailure
+ return :false
+ end
+
+ return :true
+ end
+
+ # Don't support them specifying runlevels; always use the runlevels
+ # in the init scripts.
+ def enable
+ begin
+ 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/provider/service/smf.rb b/lib/puppet/provider/service/smf.rb
new file mode 100755
index 000000000..becd6fa3f
--- /dev/null
+++ b/lib/puppet/provider/service/smf.rb
@@ -0,0 +1,84 @@
+# Solaris 10 SMF-style services.
+Puppet::Type.type(:service).provide :smf, :parent => :base do
+ desc "Support for Sun's new Service Management Framework. Starting a service
+ is effectively equivalent to enabling it, so there is only support
+ for starting and stopping services, which also enables and disables them,
+ respectively."
+
+ defaultfor :operatingsystem => :solaris
+
+ commands :adm => "/usr/sbin/svcadm", :svcs => "/usr/bin/svcs"
+
+ def enable
+ self.start
+ end
+
+ def enabled?
+ case self.status
+ when :running:
+ return :true
+ else
+ return :false
+ end
+ end
+
+ def disable
+ self.stop
+ end
+
+ def restartcmd
+ "#{command(:adm)} restart %s" % @model[:name]
+ end
+
+ def startcmd
+ "#{command(:adm)} enable %s" % @model[:name]
+ end
+
+ def status
+ if @model[:status]
+ super
+ return
+ end
+ 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+(.+)/
+ var = $1
+ value = $2
+ else
+ Puppet.err "Could not match %s" % line.inspect
+ end
+ case var
+ when "state":
+ case value
+ when "online":
+ #self.warning "matched running %s" % line.inspect
+ return :running
+ when "offline", "disabled", "uninitialized"
+ #self.warning "matched stopped %s" % line.inspect
+ return :stopped
+ when "legacy_run":
+ raise Puppet::Error,
+ "Cannot manage legacy services through SMF"
+ else
+ raise Puppet::Error,
+ "Unmanageable state '%s' on service %s" %
+ [value, self.name]
+ end
+ end
+ }
+ end
+
+ def stopcmd
+ "#{command(:adm)} disable %s" % @model[:name]
+ end
+end
+
+# $Id$
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$