summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xlib/puppet/provider/package/pkgutil.rb175
-rwxr-xr-xspec/unit/provider/package/pkgutil_spec.rb182
2 files changed, 357 insertions, 0 deletions
diff --git a/lib/puppet/provider/package/pkgutil.rb b/lib/puppet/provider/package/pkgutil.rb
new file mode 100755
index 000000000..a1d844f73
--- /dev/null
+++ b/lib/puppet/provider/package/pkgutil.rb
@@ -0,0 +1,175 @@
+# Packaging using Peter Bonivart's pkgutil program.
+Puppet::Type.type(:package).provide :pkgutil, :parent => :sun, :source => :sun do
+ desc "Package management using Peter Bonivart's ``pkgutil`` command on Solaris."
+
+ pkgutil_bin = "pkgutil"
+ if FileTest.executable?("/opt/csw/bin/pkgutil")
+ pkgutil_bin = "/opt/csw/bin/pkgutil"
+ end
+
+ confine :operatingsystem => :solaris
+
+ commands :pkguti => pkgutil_bin
+
+ def self.healthcheck()
+ unless FileTest.exists?("/var/opt/csw/pkgutil/admin")
+ Puppet.notice "It is highly recommended you create '/var/opt/csw/pkgutil/admin'."
+ Puppet.notice "See /var/opt/csw/pkgutil"
+ end
+
+ correct_wgetopts = false
+ [ "/opt/csw/etc/pkgutil.conf", "/etc/opt/csw/pkgutil.conf" ].each do |confpath|
+ File.open(confpath) do |conf|
+ conf.each {|line| correct_wgetopts = true if line =~ /^\s*wgetopts\s*=.*(-nv|-q|--no-verbose|--quiet)/ }
+ end
+ end
+ if ! correct_wgetopts
+ Puppet.notice "It is highly recommended that you set 'wgetopts=-nv' in your pkgutil.conf."
+ end
+ end
+
+ def self.instances(hash = {})
+ healthcheck
+
+ # Use the available pkg list (-a) to work out aliases
+ aliases = {}
+ availlist.each do |pkg|
+ aliases[pkg[:name]] = pkg[:alias]
+ end
+
+ # The -c pkglist lists installed packages
+ pkginsts = []
+ pkglist(hash).each do |pkg|
+ pkg.delete(:avail)
+ pkginsts << new(pkg)
+
+ # Create a second instance with the alias if it's different
+ pkgalias = aliases[pkg[:name]]
+ if pkgalias and pkg[:name] != pkgalias
+ apkg = pkg.dup
+ apkg[:name] = pkgalias
+ pkginsts << new(apkg)
+ end
+ end
+
+ pkginsts
+ end
+
+ # Turns a pkgutil -a listing into hashes with the common alias, full
+ # package name and available version
+ def self.availlist
+ output = pkguti ["-a"]
+
+ list = output.split("\n").collect do |line|
+ next if line =~ /^common\s+package/ # header of package list
+ next if noise?(line)
+
+ if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/
+ { :alias => $1, :name => $2, :avail => $3 }
+ else
+ Puppet.warning "Cannot match %s" % line
+ end
+ end.reject { |h| h.nil? }
+ end
+
+ # Turn our pkgutil -c listing into a bunch of hashes.
+ # Supports :justme => packagename, which uses the optimised --single arg
+ def self.pkglist(hash)
+ command = ["-c"]
+
+ if hash[:justme]
+ # The --single option speeds up the execution, because it queries
+ # the package managament system for one package only.
+ command << "--single"
+ command << hash[:justme]
+ end
+
+ output = pkguti(command).split("\n")
+
+ if output[-1] == "Not in catalog"
+ Puppet.warning "Package not in pkgutil catalog: %s" % hash[:justme]
+ return nil
+ end
+
+ list = output.collect do |line|
+ next if line =~ /installed\s+catalog/ # header of package list
+ next if noise?(line)
+
+ pkgsplit(line)
+ end.reject { |h| h.nil? }
+
+ if hash[:justme]
+ # Single queries may have been for an alias so return the name requested
+ if list.any?
+ list[-1][:name] = hash[:justme]
+ return list[-1]
+ end
+ else
+ list.reject! { |h| h[:ensure] == :absent }
+ return list
+ end
+ end
+
+ # Identify common types of pkgutil noise as it downloads catalogs etc
+ def self.noise?(line)
+ true if line =~ /^#/
+ true if line =~ /^Checking integrity / # use_gpg
+ true if line =~ /^gpg: / # gpg verification
+ true if line =~ /^=+> / # catalog fetch
+ true if line =~ /\d+:\d+:\d+ URL:/ # wget without -q
+ false
+ end
+
+ # Split the different lines into hashes.
+ def self.pkgsplit(line)
+ if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/
+ hash = {}
+ hash[:name] = $1
+ hash[:ensure] = if $2 == "notinst"
+ :absent
+ else
+ $2
+ end
+ hash[:avail] = $3
+
+ if hash[:avail] =~ /^SAME\s*$/
+ hash[:avail] = hash[:ensure]
+ end
+
+ # Use the name method, so it works with subclasses.
+ hash[:provider] = self.name
+
+ return hash
+ else
+ Puppet.warning "Cannot match %s" % line
+ return nil
+ end
+ end
+
+ def install
+ pkguti "-y", "-i", @resource[:name]
+ end
+
+ # Retrieve the version from the current package file.
+ def latest
+ hash = self.class.pkglist(:justme => @resource[:name])
+ hash[:avail] if hash
+ end
+
+ def query
+ if hash = self.class.pkglist(:justme => @resource[:name])
+ hash
+ else
+ {:ensure => :absent}
+ end
+ end
+
+ def update
+ pkguti "-y", "-u", @resource[:name]
+ end
+
+ def uninstall
+ pkguti "-y", "-r", @resource[:name]
+ end
+end
+
diff --git a/spec/unit/provider/package/pkgutil_spec.rb b/spec/unit/provider/package/pkgutil_spec.rb
new file mode 100755
index 000000000..5549b3f6d
--- /dev/null
+++ b/spec/unit/provider/package/pkgutil_spec.rb
@@ -0,0 +1,182 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+provider = Puppet::Type.type(:package).provider(:pkgutil)
+
+describe provider do
+ before(:each) do
+ @resource = Puppet::Type.type(:package).new(
+ :name => "TESTpkg",
+ :ensure => :present,
+ :provider => :pkgutil
+ )
+ @provider = provider.new(@resource)
+
+ # Stub all file and config tests
+ provider.stubs(:healthcheck)
+ end
+
+ it "should have an install method" do
+ @provider.should respond_to(:install)
+ end
+
+ it "should have a latest method" do
+ @provider.should respond_to(:uninstall)
+ end
+
+ it "should have an update method" do
+ @provider.should respond_to(:update)
+ end
+
+ it "should have a latest method" do
+ @provider.should respond_to(:latest)
+ end
+
+ describe "when installing" do
+ it "should use a command without versioned package" do
+ @resource[:ensure] = :latest
+ @provider.expects(:pkguti).with('-y', '-i', 'TESTpkg')
+ @provider.install
+ end
+ end
+
+ describe "when updating" do
+ it "should use a command without versioned package" do
+ @provider.expects(:pkguti).with('-y', '-u', 'TESTpkg')
+ @provider.update
+ end
+ end
+
+ describe "when uninstalling" do
+ it "should call the remove operation" do
+ @provider.expects(:pkguti).with('-y', '-r', 'TESTpkg')
+ @provider.uninstall
+ end
+ end
+
+ describe "when getting latest version" do
+ it "should return TESTpkg's version string" do
+ fake_data = "
+noisy output here
+TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == "1.4.5,REV=2007.11.20"
+ end
+
+ it "should handle TESTpkg's 'SAME' version string" do
+ fake_data = "
+noisy output here
+TESTpkg 1.4.5,REV=2007.11.18 SAME"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == "1.4.5,REV=2007.11.18"
+ end
+
+ it "should handle a non-existent package" do
+ fake_data = "noisy output here
+Not in catalog"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == nil
+ end
+
+ it "should warn on unknown pkgutil noise" do
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns("testingnoise")
+ @provider.latest.should == nil
+ end
+
+ it "should ignore pkgutil noise/headers to find TESTpkg" do
+ fake_data = "# stuff
+=> Fetching new catalog and descriptions (http://mirror.opencsw.org/opencsw/unstable/i386/5.11) if available ...
+2011-02-19 23:05:46 URL:http://mirror.opencsw.org/opencsw/unstable/i386/5.11/catalog [534635/534635] -> \"/var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11.tmp\" [1]
+Checking integrity of /var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11 with gpg.
+gpg: Signature made February 17, 2011 05:27:53 PM GMT using DSA key ID E12E9D2F
+gpg: Good signature from \"Distribution Manager <dm@blastwave.org>\"
+==> 2770 packages loaded from /var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11
+package installed catalog
+TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.latest.should == "1.4.5,REV=2007.11.20"
+ end
+
+ it "should find REALpkg via an alias (TESTpkg)" do
+ fake_data = "
+noisy output here
+REALpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:name].should == "TESTpkg"
+ end
+ end
+
+ describe "when querying current version" do
+ it "should return TESTpkg's version string" do
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:ensure].should == "1.4.5,REV=2007.11.18"
+ end
+
+ it "should handle a package that isn't installed" do
+ fake_data = "TESTpkg notinst 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:ensure].should == :absent
+ end
+
+ it "should handle a non-existent package" do
+ fake_data = "noisy output here
+Not in catalog"
+ provider.expects(:pkguti).with(['-c', '--single', 'TESTpkg']).returns fake_data
+ @provider.query[:ensure].should == :absent
+ end
+ end
+
+ describe "when querying current instances" do
+ it "should warn on unknown pkgutil noise" do
+ provider.expects(:pkguti).with(['-a']).returns("testingnoise")
+ provider.expects(:pkguti).with(['-c']).returns("testingnoise")
+ Puppet.expects(:warning).times(2)
+ provider.expects(:new).never
+ provider.instances.should == []
+ end
+
+ it "should return TESTpkg's version string" do
+ fake_data = "TESTpkg TESTpkg 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-a']).returns fake_data
+
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c']).returns fake_data
+
+ testpkg = mock 'pkg1'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg
+ provider.instances.should == [testpkg]
+ end
+
+ it "should also return both TESTpkg and mypkg alias instances" do
+ fake_data = "mypkg TESTpkg 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-a']).returns fake_data
+
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c']).returns fake_data
+
+ testpkg = mock 'pkg1'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg
+
+ aliaspkg = mock 'pkg2'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "mypkg", :provider => :pkgutil).returns aliaspkg
+
+ provider.instances.should == [testpkg,aliaspkg]
+ end
+
+ it "shouldn't mind noise in the -a output" do
+ fake_data = "noisy output here"
+ provider.expects(:pkguti).with(['-a']).returns fake_data
+
+ fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20"
+ provider.expects(:pkguti).with(['-c']).returns fake_data
+
+ testpkg = mock 'pkg1'
+ provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg
+
+ provider.instances.should == [testpkg]
+ end
+ end
+
+end