diff --git a/lib/puppet/parser/functions/regsubst.rb b/lib/puppet/parser/functions/regsubst.rb index b6bb5afcf..397d2b2ee 100644 --- a/lib/puppet/parser/functions/regsubst.rb +++ b/lib/puppet/parser/functions/regsubst.rb @@ -1,93 +1,119 @@ +# Copyright (C) 2009 Thomas Bellman +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THOMAS BELLMAN BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of Thomas Bellman shall +# not be used in advertising or otherwise to promote the sale, use or +# other dealings in this Software without prior written authorization +# from Thomas Bellman. + module Puppet::Parser::Functions newfunction( :regsubst, :type => :rvalue, :doc => " Perform regexp replacement on a string or array of strings. * *Parameters* (in order): * _target_ The string or array of strings to operate on. If an array, the replacement will be performed on each of the elements in the array, and the return value will be an array. * _regexp_ The regular expression matching the target string. If you want it anchored at the start and or end of the string, you must do that with ^ and $ yourself. * _replacement_ Replacement string. Can contain backreferences to what was matched using \\0 (whole match), \\1 (first set of parentheses), and so on. * _flags_ Optional. String of single letter flags for how the regexp is interpreted: - *E* Extended regexps - *I* Ignore case in regexps - *M* Multiline regexps - *G* Global replacement; all occurrences of the regexp in each target string will be replaced. Without this, only the first occurrence will be replaced. * _encoding_ Optional. How to handle multibyte characters. A single-character string with the following values: - *N* None - *E* EUC - *S* SJIS - *U* UTF-8 * *Examples* Get the third octet from the node's IP address: $i3 = regsubst($ipaddress,'^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$','\\3') Put angle brackets around each octet in the node's IP address: $x = regsubst($ipaddress, '([0-9]+)', '<\\1>', 'G') ") \ do |args| unless args.length.between?(3, 5) raise( Puppet::ParseError, "regsubst(): got #{args.length} arguments, expected 3 to 5") end target, regexp, replacement, flags, lang = args reflags = 0 operation = :sub if flags == nil flags = [] elsif flags.respond_to?(:split) flags = flags.split('') else raise( Puppet::ParseError, "regsubst(): bad flags parameter #{flags.class}:`#{flags}'") end flags.each do |f| case f when 'G' then operation = :gsub when 'E' then reflags |= Regexp::EXTENDED when 'I' then reflags |= Regexp::IGNORECASE when 'M' then reflags |= Regexp::MULTILINE else raise(Puppet::ParseError, "regsubst(): bad flag `#{f}'") end end begin re = Regexp.compile(regexp, reflags, lang) rescue RegexpError, TypeError raise( Puppet::ParseError, "regsubst(): Bad regular expression `#{regexp}'") end if target.respond_to?(operation) # String parameter -> string result result = target.send(operation, re, replacement) elsif target.respond_to?(:collect) and target.respond_to?(:all?) and target.all? { |e| e.respond_to?(operation) } # Array parameter -> array result result = target.collect { |e| e.send(operation, re, replacement) } else raise( Puppet::ParseError, "regsubst(): bad target #{target.class}:`#{target}'") end return result end end diff --git a/lib/puppet/parser/functions/shellquote.rb b/lib/puppet/parser/functions/shellquote.rb index 3ddb988f2..ee070c740 100644 --- a/lib/puppet/parser/functions/shellquote.rb +++ b/lib/puppet/parser/functions/shellquote.rb @@ -1,39 +1,65 @@ +# Copyright (C) 2009 Thomas Bellman +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THOMAS BELLMAN BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of Thomas Bellman shall +# not be used in advertising or otherwise to promote the sale, use or +# other dealings in this Software without prior written authorization +# from Thomas Bellman. + module Puppet::Parser::Functions Safe = 'a-zA-Z0-9@%_+=:,./-' # Safe unquoted Dangerous = '!"`$\\' # Unsafe inside double quotes newfunction(:shellquote, :type => :rvalue, :doc => "\ Quote and concatenate arguments for use in Bourne shell. Each argument is quoted separately, and then all are concatenated with spaces. If an argument is an array, the elements of that array is interpolated within the rest of the arguments; this makes it possible to have an array of arguments and pass that array to shellquote instead of having to specify each argument individually in the call. ") \ do |args| result = [] args.flatten.each do |word| if word.length != 0 and word.count(Safe) == word.length result << word elsif word.count(Dangerous) == 0 result << ('"' + word + '"') elsif word.count("'") == 0 result << ("'" + word + "'") else r = '"' word.each_byte do |c| r += "\\" if Dangerous.include?(c) r += c.chr end r += '"' result << r end end return result.join(" ") end end diff --git a/lib/puppet/parser/functions/sprintf.rb b/lib/puppet/parser/functions/sprintf.rb index 5eb4a4f9d..118020412 100644 --- a/lib/puppet/parser/functions/sprintf.rb +++ b/lib/puppet/parser/functions/sprintf.rb @@ -1,13 +1,39 @@ +# Copyright (C) 2009 Thomas Bellman +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THOMAS BELLMAN BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of Thomas Bellman shall +# not be used in advertising or otherwise to promote the sale, use or +# other dealings in this Software without prior written authorization +# from Thomas Bellman. + module Puppet::Parser::Functions newfunction( :sprintf, :type => :rvalue, :doc => "Perform printf-style formatting of text. The first parameter is format string describing how the rest of the parameters should be formatted. See the documentation for the `Kernel::sprintf` function in Ruby for all the details.") do |args| raise Puppet::ParseError, 'sprintf() needs at least one argument' if args.length < 1 fmt = args.shift return sprintf(fmt, *args) end end diff --git a/lib/puppet/provider/package/pacman.rb b/lib/puppet/provider/package/pacman.rb new file mode 100644 index 000000000..5e05d147e --- /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 { |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..679172621 --- /dev/null +++ b/spec/unit/provider/package/pacman_spec.rb @@ -0,0 +1,237 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +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