Backstory: There are 2 package providers supplied for freebsd - one
for freebsd packages (:freebsd), and one for ports (:ports).
Freebsd's ports is the most commonly used system for freebsd as it
contains instructions and patches for compiling software from source.

The current package :ports utilizes a common 3rd party software
(portupgrade) system to manage (install/version/etc) the ports system
which remains unchanged.  This ticket/code is to replace that
provider.

":ports" provider contains a few issues that are addressed in this
replacement
* ensure=>latest is broken
* unable to deal with multi-versions of a software port (eg: mysql50,
mysql51, etc)
* uninstall can fail

Incompatible change with this new package:
* package name needs to be the full port origin (eg: databases/mysql51-
client)

Configuration example:

package { "shells/bash":
        provider => portupgrade,
        alias   => "bash",
        ensure  => latest,
}

Signed-off-by: Ross West <[email protected]>
---

diff --git a/lib/puppet/provider/package/portupgrade.rb b/lib/puppet/
provider/package/portupgrade.rb
new file mode 100644
index 0000000..531cab2
--- /dev/null
+++ b/lib/puppet/provider/package/portupgrade.rb
@@ -0,0 +1,251 @@
+
+# Whole new package, so include pack stuff
+require 'puppet/provider/package'
+
+Puppet::Type.type(:package).provide :portupgrade, :parent =>
Puppet::Provider::Package do
+       include Puppet::Util::Execution
+
+       desc "Support for FreeBSD's ports using the portupgrade ports
management software.
+             Use the port's full origin as the resource name. eg
(ports-mgmt/portupgrade)
+             for the portupgrade port."
+
+       ## has_features is usually autodetected based on defs below.
+       # has_features :installable, :uninstallable, :upgradeable
+
+       commands :portupgrade   => "/usr/local/sbin/portupgrade",
+                :portinstall   => "/usr/local/sbin/portinstall",
+                :portversion   => "/usr/local/sbin/portversion",
+                :portuninstall => "/usr/local/sbin/pkg_deinstall",
+                :portinfo      => "/usr/sbin/pkg_info"
+
+       ## Activate this only once approved by someone important.
+       # defaultfor :operatingsystem => :freebsd
+
+       # Remove unwanted environment variables.
+       %w{INTERACTIVE UNAME}.each do |var|
+               if ENV.include?(var)
+                       ENV.delete(var)
+               end
+       end
+
+       ######## instances sub command (builds the installed packages
list)
+
+       def self.instances
+               Puppet.debug "portupgrade.rb Building packages list
from installed ports"
+
+               # regex to match output from pkg_info
+               regex = %r{^(\S+)-([^-\s]+):(\S+)$}
+               # Corresponding field names
+               fields = [:portname, :ensure, :portorigin]
+               # define Temporary hash used, packages array of hashes
+               hash = Hash.new
+               packages = []
+
+               # exec command
+               cmdline = ["-aoQ"]
+               begin
+                       output = portinfo(*cmdline)
+               rescue Puppet::ExecutionFailure
+                       raise Puppet::Error.new(output)
+                       return nil
+               end
+
+               # split output and match it and populate temp hash
+               output.split("\n").each { |data|
+                       # reset hash to nil for each line
+                       hash.clear
+                       if match = regex.match(data)
+                               # Output matched regex
+                               fields.zip(match.captures) { |field,
value|
+                                       hash[field] = value
+                               }
+
+                               # populate the actual :name field from
the :portorigin
+                               # Set :provider to this object name
+                               hash[:name] = hash[:portorigin]
+                               hash[:provider] = self.name
+
+                               # Add to the full packages listing
+                               packages << new(hash)
+
+                       else
+                               # unrecognised output from pkg_info
+                               Puppet.debug "portupgrade.Instances()
- unable to match output: %s" % data
+                       end
+               }
+
+               # return the packages array of hashes
+               return packages
+
+       end
+
+       ######## Installation sub command
+
+       def install
+               Puppet.debug "portupgrade.install() - Installation
call on %s" % @resource[:name]
+               # -M: yes, we're a batch, so don't ask any questions
+               cmdline = ["-M BATCH=yes", @resource[:name]]
+
+               # FIXME: it's possible that portinstall prompts for
data so locks up.
+               begin
+                       output = portinstall(*cmdline)
+               rescue Puppet::ExecutionFailure
+                       raise Puppet::Error.new(output)
+               end
+
+               if output =~ /\*\* No such /
+                       raise Puppet::ExecutionFailure, "Could not
find package %s" % @resource[:name]
+               end
+
+               # No return code required, so do nil to be clean
+               return nil
+       end
+
+       ######## Latest subcommand (returns the latest version
available, or current version if installed is latest)
+
+       def latest
+               Puppet.debug "portupgrade.latest() - Latest check
called on %s" % @resource[:name]
+               # search for latest version available, or return
current version.
+               # cmdline = "portversion -v <portorigin>", returns
"<portname> <code> <stuff>"
+               # or "** No matching package found: <portname>"
+               cmdline = ["-v", @resource[:name]]
+
+               begin
+                       output = portversion(*cmdline)
+               rescue Puppet::ExecutionFailure
+                       raise Puppet::Error.new(output)
+               end
+
+               # Check: output format.
+               if output =~ /^\S+-([^-\s]+)\s+(\S)\s+(.*)/
+                       # $1 = installed version, $2 = comparison, $3
other data
+                       # latest installed
+                       installedversion = $1
+                       comparison = $2
+                       otherdata = $3
+
+                       # Only return a new version number when it's
clear that there is a new version
+                       # all others return the current version so no
unexpected 'upgrades' occur.
+                       case comparison
+                       when "=", ">"
+                               Puppet.debug "portupgrade.latest() -
Installed package is latest (%s)" % installedversion
+                               return installedversion
+                       when "<"
+                               # "portpkg-1.7_5  <  needs updating
(port has 1.14)"
+                               # "portpkg-1.7_5  <  needs updating
(port has 1.14) (=> 'newport/pkg')
+                               if otherdata =~ /\(port has (\S+)\)/
+                                       newversion = $1
+                                       Puppet.debug
"portupgrade.latest() - Installed version needs updating to (%s)" %
newversion
+                                       return newversion
+                               else
+                                       Puppet.debug
"portupgrade.latest() - Unable to determine new version from (%s)" %
otherdata
+                                       return installedversion
+                               end
+                       when "?", "!", "#"
+                               Puppet.debug "portupgrade.latest() -
Comparison Error reported from portversion (%s)" % output
+                               return installedversion
+                       else
+                               Puppet.debug "portupgrade.latest() -
Unknown code from portversion output (%s)" % output
+                               return installedversion
+                       end
+
+               else
+                       # error: output not parsed correctly, error
out with nil.
+                       # Seriously - this section should never be
called in a perfect world.
+                       # as verification that the port is installed
has already happened in query.
+                       if output =~ /^\*\* No matching package /
+                               raise Puppet::ExecutionFailure, "Could
not find package %s" % @resource[:name]
+                       else
+                               # Any other error (dump output to log)
+                               raise Puppet::ExecutionFailure,
"Unexpected output from portversion: %s" % output
+                       end
+
+                       # Just in case we still are running, return
nil
+                       return nil
+               end
+
+               # At this point normal operation has finished and we
shouldn't have been called.
+               # Error out and let the admin deal with it.
+               raise Puppet::Error, "portversion.latest() - fatal
error with portversion: %s" % output
+               return nil
+
+       end
+
+       ###### Query subcommand - return a hash of details if exists,
or nil if it doesn't.
+       # Used to make sure the package is installed
+
+       def query
+               Puppet.debug "portupgrade.query() - Called on %s" %
@resource[:name]
+
+               cmdline = ["-qO", @resource[:name]]
+               begin
+                       output = portinfo(*cmdline)
+               rescue Puppet::ExecutionFailure
+                       raise Puppet::Error.new(output)
+               end
+
+               # Check: if output isn't in the right format, return
nil
+               if output =~ /^(\S+)-([^-\s]+)/
+                       # Fill in the details
+                       hash = Hash.new
+                       hash[:portorigin]       = self.name
+                       hash[:portname]         = $1
+                       hash[:ensure]           = $2
+
+                       # If more details are required, then we can do
another pkg_info query here
+                       # and parse out that output and add to the
hash
+
+                       # return the hash to the caller
+                       return hash
+               else
+                       Puppet.debug "portupgrade.query() - package
(%s) not installed" % @resource[:name]
+                       return nil
+               end
+
+       end # def query
+
+       ####### Uninstall command
+
+       def uninstall
+               Puppet.debug "portupgrade.uninstall() - called on %s"
% @resource[:name]
+               # Get full package name from port origin to uninstall
with
+               cmdline = ["-qO", @resource[:name]]
+               begin
+                       output = portinfo(*cmdline)
+               rescue Puppet::ExecutionFailure
+                       raise Puppet::Error.new(output)
+               end
+
+               if output =~ /^(\S+)/
+                       # output matches, so uninstall it
+                       portuninstall $1
+               end
+
+       end
+
+       ######## Update/upgrade command
+
+       def update
+               Puppet.debug "portupgrade.update() - called on (%s)" %
@resource[:name]
+
+               cmdline = ["-qO", @resource[:name]]
+               begin
+                       output = portinfo(*cmdline)
+               rescue Puppet::ExecutionFailure
+                       raise Puppet::Error.new(output)
+               end
+
+               if output =~ /^(\S+)/
+                       # output matches, so upgrade the software
+                       cmdline = ["-M BATCH=yes", $1]
+                       begin
+                               output = portupgrade(*cmdline)
+                       rescue Puppet::ExecutionFailure
+                               raise Puppet::Error.new(output)
+                       end
+               end
+       end
+
+## EOF
+end
+

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to