diff --git a/lib/puppet/provider/package/pacman.rb b/lib/puppet/provider/package/pacman.rb new file mode 100644 index 000000000..6eb7dbe3d --- /dev/null +++ b/lib/puppet/provider/package/pacman.rb @@ -0,0 +1,94 @@ +require 'puppet/provider/package' + +Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Package do + desc "Support for the Package Manager Utility (pacman) used in Archlinux." + + commands :pacman => "/usr/bin/pacman" + defaultfor :operatingsystem => :archlinux + confine :operatingsystem => :archlinux + has_feature :upgradeable + + # Install a package using 'pacman'. + # Installs quietly, without confirmation or progressbar, updates package + # list from servers defined in pacman.conf. + def install + pacman "--noconfirm", "--noprogressbar", "-Sy", @resource[:name] + + unless self.query + raise Puppet::ExecutionFailure.new("Could not find package %s" % self.name) + end + end + + def self.listcmd + [command(:pacman), " -Q"] + end + + # Fetch the list of packages currently installed on the system. + def self.instances + packages = [] + begin + execpipe(listcmd()) do |process| + # pacman -Q output is 'packagename version-rel' + regex = %r{^(\S+)\s(\S+)} + fields = [:name, :ensure] + hash = {} + + process.each { |line| + if match = regex.match(line) + fields.zip(match.captures) { |field,value| + hash[field] = value + } + + name = hash[:name] + hash[:provider] = self.name + + packages << new(hash) + hash = {} + else + warning("Failed to match line %s" % line) + end + } + end + rescue Puppet::ExecutionFailure + return nil + end + packages + end + + # Because Archlinux is a rolling release based distro, installing a package + # should always result in the newest release. + def update + # Install in pacman can be used for update, too + self.install + end + + def latest + pacman "-Sy" + output = pacman "-Sp", "--print-format", "%v", @resource[:name] + output.chomp + end + + # Querys the pacman master list for information about the package. + def query + begin + output = pacman("-Qi", @resource[:name]) + + if output =~ /Version.*:\s(.+)/ + return { :ensure => $1 } + end + rescue Puppet::ExecutionFailure + return { + :ensure => :purged, + :status => 'missing', + :name => @resource[:name], + :error => 'ok', + } + end + nil + end + + # Removes a package from the system. + def uninstall + pacman "--noconfirm", "--noprogressbar", "-R", @resource[:name] + end +end diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb new file mode 100644 index 000000000..fbe4c446e --- /dev/null +++ b/spec/unit/provider/package/pacman_spec.rb @@ -0,0 +1,238 @@ +#!/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(:pacman) + +describe provider do + before do + provider.stubs(:command).with(:pacman).returns('/usr/bin/pacman') + @resource = stub 'resource' + @resource.stubs(:[]).returns("package") + @resource.stubs(:name).returns("name") + @provider = provider.new(@resource) + end + + describe "when installing" do + before do + @provider.stubs(:query).returns({ + :ensure => '1.0' + }) + end + + it "should call pacman" do + provider. + expects(:execute). + at_least_once. + with { |args| + args[0] == "/usr/bin/pacman" + }. + returns "" + + @provider.install + end + + it "should be quiet" do + provider. + expects(:execute). + with { |args| + args[1,2] == ["--noconfirm", "--noprogressbar"] + }. + returns("") + + @provider.install + end + + it "should install the right package" do + provider. + expects(:execute). + with { |args| + args[3,4] == ["-Sy", @resource[0]] + }. + returns("") + + @provider.install + end + + it "should raise an ExecutionFailure if the installation failed" do + provider.stubs(:execute).returns("") + @provider.expects(:query).returns(nil) + + lambda { @provider.install }.should raise_exception(Puppet::ExecutionFailure) + end + end + + describe "when updating" do + it "should call install" do + @provider.expects(:install).returns("install return value") + @provider.update.should == "install return value" + end + end + + describe "when uninstalling" do + it "should call pacman" do + provider. + expects(:execute). + with { |args| + args[0] == "/usr/bin/pacman" + }. + returns "" + + @provider.uninstall + end + + it "should be quiet" do + provider. + expects(:execute). + with { |args| + args[1,2] == ["--noconfirm", "--noprogressbar"] + }. + returns("") + + @provider.uninstall + end + + it "should remove the right package" do + provider. + expects(:execute). + with { |args| + args[3,4] == ["-R", @resource[0]] + }. + returns("") + + @provider.uninstall + end + end + + describe "when querying" do + it "should query pacman" do + provider. + expects(:execute). + with(["/usr/bin/pacman", "-Qi", @resource[0]]) + @provider.query + end + + it "should return the version" do + query_output = <=2.7.1 libfetch>=2.25 pacman-mirrorlist +Optional Deps : fakeroot: for makepkg usage as normal user + curl: for rankmirrors usage +Required By : None +Conflicts With : None +Replaces : None +Installed Size : 2352.00 K +Packager : Dan McGee +Architecture : i686 +Build Date : Sat 22 Jan 2011 03:56:41 PM EST +Install Date : Thu 27 Jan 2011 06:45:49 AM EST +Install Reason : Explicitly installed +Install Script : Yes +Description : A library-based package manager with dependency support +EOF + + provider.expects(:execute).returns(query_output) + @provider.query.should == {:ensure => "1.01.3-2"} + end + + it "should return a nil if the package isn't found" do + provider.expects(:execute).returns("") + @provider.query.should be_nil + end + + it "should return a hash indicating that the package is missing on error" do + provider.expects(:execute).raises(Puppet::ExecutionFailure.new("ERROR!")) + @provider.query.should == { + :ensure => :purged, + :status => 'missing', + :name => @resource[0], + :error => 'ok', + } + end + end + + describe "when fetching a package list" do + it "should query pacman" do + provider.expects(:execpipe).with(["/usr/bin/pacman", ' -Q']) + provider.instances + end + + it "should return installed packages with their versions" do + provider.expects(:execpipe).yields("package1 1.23-4\npackage2 2.00\n") + packages = provider.instances + + packages.length.should == 2 + + packages[0].properties.should == { + :provider => :pacman, + :ensure => '1.23-4', + :name => 'package1' + } + + packages[1].properties.should == { + :provider => :pacman, + :ensure => '2.00', + :name => 'package2' + } + end + + it "should return nil on error" do + provider.expects(:execpipe).raises(Puppet::ExecutionFailure.new("ERROR!")) + provider.instances.should be_nil + end + + it "should warn on invalid input" do + provider.expects(:execpipe).yields("blah") + provider.expects(:warning).with("Failed to match line blah") + provider.instances.should == [] + end + end + + describe "when determining the latest version" do + it "should refresh package list" do + refreshed = states('refreshed').starts_as('unrefreshed') + provider. + expects(:execute). + when(refreshed.is('unrefreshed')). + with(['/usr/bin/pacman', '-Sy']). + then(refreshed.is('refreshed')) + + provider. + stubs(:execute). + when(refreshed.is('refreshed')). + returns("") + + @provider.latest + end + + it "should get query pacman for the latest version" do + refreshed = states('refreshed').starts_as('unrefreshed') + provider. + stubs(:execute). + when(refreshed.is('unrefreshed')). + then(refreshed.is('refreshed')) + + provider. + expects(:execute). + when(refreshed.is('refreshed')). + with(['/usr/bin/pacman', '-Sp', '--print-format', '%v', @resource[0]]). + returns("") + + @provider.latest + end + + it "should return the version number from pacman" do + provider. + expects(:execute). + at_least_once(). + returns("1.00.2-3\n") + + @provider.latest.should == "1.00.2-3" + end + end +end