* Employs port -q installed, much faster than port list installed
  * Handles upgrades correctly now
  * Makes use of internal port revision for ensure => latest upgrades
  * Versionable, now works with ensure => specified_version
  * Does not handle port variants at all yet.
  * Does not allow manual specification of revision, only version
  * Test coverage expanded

Signed-off-by: Nigel Kersten <ni...@puppetlabs.com>
---
 lib/puppet/provider/package/darwinport.rb   |   86 -------------------
 lib/puppet/provider/package/macports.rb     |  103 +++++++++++++++++++++++
 spec/unit/provider/package/macports_spec.rb |  121 +++++++++++++++++++++++++++
 3 files changed, 224 insertions(+), 86 deletions(-)
 delete mode 100755 lib/puppet/provider/package/darwinport.rb
 create mode 100755 lib/puppet/provider/package/macports.rb
 create mode 100755 spec/unit/provider/package/macports_spec.rb

diff --git a/lib/puppet/provider/package/darwinport.rb 
b/lib/puppet/provider/package/darwinport.rb
deleted file mode 100755
index c5f9ba2..0000000
--- a/lib/puppet/provider/package/darwinport.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'puppet/provider/package'
-
-Puppet::Type.type(:package).provide :darwinport, :parent => 
Puppet::Provider::Package do
-  desc "Package management using DarwinPorts on OS X."
-
-  confine :operatingsystem => :darwin
-  commands :port => "/opt/local/bin/port"
-
-  def self.eachpkgashash
-    # list out all of the packages
-    open("| #{command(:port)} list installed") { |process|
-      regex = %r{(\S+)\s+@(\S+)\s+(\S+)}
-      fields = [:name, :ensure, :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[:provider] = self.name
-          yield hash.dup
-        else
-          raise Puppet::DevError,
-            "Failed to match dpkg line #{line}"
-        end
-      }
-    }
-  end
-
-  def self.instances
-    packages = []
-
-    eachpkgashash do |hash|
-      packages << new(hash)
-    end
-
-    packages
-  end
-
-  def install
-    should = @resource.should(:ensure)
-
-    # Seems like you can always say 'upgrade'
-    output = port "upgrade", @resource[:name]
-    if output =~ /^Error: No port/
-      raise Puppet::ExecutionFailure, "Could not find package 
#{@resource[:name]}"
-    end
-  end
-
-  def query
-    version = nil
-    self.class.eachpkgashash do |hash|
-      return hash if hash[:name] == @resource[:name]
-    end
-
-    nil
-  end
-
-  def latest
-    info = port :search, "^#{@resource[:name]}$"
-
-    if $CHILD_STATUS != 0 or info =~ /^Error/
-      return nil
-    end
-
-    ary = info.split(/\s+/)
-    version = ary[2].sub(/^@/, '')
-
-    version
-  end
-
-  def uninstall
-    port :uninstall, @resource[:name]
-  end
-
-  def update
-    install
-  end
-end
-
diff --git a/lib/puppet/provider/package/macports.rb 
b/lib/puppet/provider/package/macports.rb
new file mode 100755
index 0000000..c6d3f8b
--- /dev/null
+++ b/lib/puppet/provider/package/macports.rb
@@ -0,0 +1,103 @@
+require 'puppet/provider/package'
+
+Puppet::Type.type(:package).provide :macports, :parent => 
Puppet::Provider::Package do
+  desc "Package management using MacPorts on OS X.
+
+    Supports MacPorts versions and revisions, but not variants.
+    When specifying a version in the Puppet DSL, do not include the revision.
+    Revisions are only used internally for ensuring the latest 
version/revision of a port.
+  "
+
+  confine :operatingsystem => :darwin
+  commands :port => "/opt/local/bin/port"
+
+  has_feature :installable
+  has_feature :uninstallable
+  has_feature :upgradeable
+  has_feature :versionable
+
+
+  def self.parse_installed_query_line(line)
+    regex = /(\S+)\s+@(\S+)_(\S+)\s+\(active\)/
+    fields = [:name, :ensure, :revision]
+    hash_from_line(line, regex, fields)
+  end
+
+  def self.parse_info_query_line(line)
+    regex = /(\S+)\s+(\S+)/
+    fields = [:version, :revision]
+    hash_from_line(line, regex, fields)
+  end
+
+  def self.hash_from_line(line, regex, fields)
+    hash = {}
+    if match = regex.match(line)
+      fields.zip(match.captures) { |field, value|
+        hash[field] = value
+      }
+      hash[:provider] = self.name
+      return hash
+    end
+    nil
+  end
+
+  def self.instances
+    packages = []
+    port("-q", :installed).each do |line|
+      if hash = parse_installed_query_line(line)
+        packages << new(hash)
+      end
+    end
+    packages
+  end
+
+  def install
+    should = @resource.should(:ensure)
+    if [:latest, :installed, :present].include?(should)
+      output = port("-q", :install, @resource[:name])
+    else
+      output = port("-q", :install, @resource[:name], "@#{should}")
+    end
+    # MacPorts now correctly exits non-zero with appropriate errors in
+    # situations where a port cannot be found or installed.
+  end
+
+  def query
+    return self.class.parse_installed_query_line(port("-q", :installed, 
@resource[:name]))
+  end
+
+  def latest
+    # We need both the version and the revision to be confident
+    # we've got the latest revision of a specific version
+    # Note we're still not doing anything with variants here.
+    info_line = port("-q", :info, "--line", "--version", "--revision", 
@resource[:name])
+    return nil if info_line == ""
+
+    if newest = self.class.parse_info_query_line(info_line)
+      current = query
+      # We're doing some fiddling behind the scenes here to cope with updated 
revisions.
+      # If we're already at the latest version/revision, then just return the 
version
+      # so the current and desired values match. Otherwise return version and 
revision
+      # to trigger an upgrade to the latest revision.
+      if newest[:version] == current[:ensure] and newest[:revision] == 
current[:revision]
+        return current[:ensure]
+      else
+        return "#{newest[:version]}_#{newest[:revision]}"
+      end
+    end
+    nil
+  end
+
+  def uninstall
+    port("-q", :uninstall, @resource[:name])
+  end
+
+  def update
+    if query[:name] == @resource[:name]  # 'port upgrade' cannot install new 
ports
+      port("-q", :upgrade, @resource[:name])
+    else
+      install
+    end
+  end
+end
+
diff --git a/spec/unit/provider/package/macports_spec.rb 
b/spec/unit/provider/package/macports_spec.rb
new file mode 100755
index 0000000..958f105
--- /dev/null
+++ b/spec/unit/provider/package/macports_spec.rb
@@ -0,0 +1,121 @@
+#!/usr/bin/env ruby
+
+Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? 
require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
+
+provider = Puppet::Type.type(:package).provider(:macports)
+
+describe provider do
+  before do
+    @resource_name = "foo"
+    @resource = Puppet::Type.type(:package).new(:name => @resource_name,
+                                                :provider => :macports)
+    @provider = @resource.provider
+    @provider.expects(:execute).never # forbid "manual" executions
+    @current_hash = {:name => @resource_name,
+                     :ensure => "1.2.3",
+                     :revision => "1",
+                     :provider => :macports}
+  end
+
+  it "should be installable" do
+    provider.should be_installable
+  end
+
+  it "should be uninstallable" do
+    provider.should be_uninstallable
+  end
+  it "should be upgradeable" do
+    provider.should be_upgradeable
+  end
+
+  it "should be versionable" do
+    provider.should be_versionable
+  end
+
+  describe "when listing all instances" do
+    it "should call port -q installed" do
+      provider.expects(:port).with("-q", :installed).returns("")
+      provider.instances
+    end
+
+    it "should create instances from active ports" do
+      provider.expects(:port).returns("foo @1.234.5_2 (active)")
+      provider.instances.size.should == 1
+    end
+
+    it "should ignore ports that aren't activated" do
+      provider.expects(:port).returns("foo @1.234.5_2")
+      provider.instances.size.should == 0
+    end
+  end
+
+  describe "when installing" do
+   it "should not specify a version when ensure is set to latest" do
+     @resource[:ensure] = :latest
+     @provider.expects(:port).with { |flag, method, name, version|
+       version.should be_nil
+     }
+     @provider.install
+   end
+
+   it "should not specify a version when ensure is set to present" do
+     @resource[:ensure] = :present
+     @provider.expects(:port).with { |flag, method, name, version|
+       version.should be_nil
+     }
+     @provider.install
+   end
+
+   it "should specify a version when ensure is set to a version" do
+     @resource[:ensure] = "1.2.3"
+     @provider.expects(:port).with { |flag, method, name, version|
+       version.should be
+     }
+     @provider.install
+   end
+  end
+
+  describe "when querying for the latest version" do
+    before do
+      @new_info_line = "1.2.3 2"
+      @infoargs = ["-q", :info, "--line", "--version", "--revision", 
@resource_name]
+    end
+    it "should return nil when the package cannot be found" do
+      @resource[:name] = @resource_name
+      @provider.expects(:port).returns("")
+      @provider.latest.should == nil
+    end
+
+    it "should return the current version if the installed port has the same 
revision" do
+      @current_hash[:revision] = "2"
+      @provider.expects(:port).with(*@infoargs).returns(@new_info_line)
+      @provider.expects(:query).returns(@current_hash)
+      @provider.latest.should == @current_hash[:ensure]
+    end
+
+    it "should return the new version_revision if the installed port has a 
lower revision" do
+      @current_hash[:revision] = "1"
+      @provider.expects(:port).with(*@infoargs).returns(@new_info_line)
+      @provider.expects(:query).returns(@current_hash)
+      @provider.latest.should == "1.2.3_2"
+    end
+  end
+
+  describe "when updating a port" do
+    it "should execute port upgrade if the port is installed" do
+      @resource[:name] = @resource_name
+      @resource[:ensure] = :present
+      @provider.expects(:query).returns(@current_hash)
+      @provider.expects(:port).with("-q", :upgrade, @resource_name)
+      @provider.update
+    end
+
+    it "should execute port install if the port is not installed" do
+      @resource[:name] = @resource_name
+      @resource[:ensure] = :present
+      @provider.expects(:query).returns("")
+      @provider.expects(:port).with("-q", :install, @resource_name)
+      @provider.update
+    end
+  end
+end
-- 
1.7.4.1

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

Reply via email to