Our installation runs on Solaris 11 zones with many custom built IPS packages. That all works pretty well but sometimes config files need to change depending on which version of a package is installed or even if a particular package is installed at all on that zone. To make that information available to puppet, I decided to create a custom fact-set and the corresponding puppet extensions to compare versions.

The facter extension is pretty small and creates one fact for every installed package we are interested in. It looks like:

.../puppet/modules/pppt/lib/facter# cat foo_packages.rb
if File.executable?("/usr/bin/pkg")
  output = %x{/usr/bin/pkg list -Hv --no-refresh 'foo/*'}
  output.split(/$\n/).each do |str|
    if str =~ /^pkg:\/\/foo(\/foo\/[^@]+)@([^,]+)/
      p=$1
      v=$2
      Facter.add(("pkg" + p.gsub("/", "_")).to_sym) do
        setcode do
          v.to_s
        end
      end
    end
  end
end

The puppet parser function looks like:

.../puppet/modules/pppt/lib/puppet/parser/functions# cat pkg_installed.rb 
#
# A function to check if a IPS package is installed or not with relation
# to a given version.
# args[0] - a string of the package name, something like "foo/source/bugzilla"
# args[1] - a relation to the version, something like
#   "not" - not installed (third arg is not used)
#   "eq", "ge", "gt", "le", "lt", "ne" - relation to the version number in the third
#           argunment
# args[2] - a string specifying the version with the same restriktions as IPS 
#
module Puppet::Parser::Functions
  newfunction(:pkg_installed, :type => :rvalue) do |args|
    name = args[0]
    rel = args[1]
    vers = args[2]
    ret = nil
    done = nil

    # warning "mw pkg_installed(#{name}, #{rel}, #{vers})"
    if name && rel
      fct_name = "pkg_" + name.gsub("/", "_")
      fct_vers = lookupvar(fct_name)
      # warning "mw t1 name= #{fct_name} => #{fct_vers}"

      if rel == "not"
        ret = true if fct_vers == :undefined
        done = true
      else
        if (fct_vers == :undefined) || vers.nil?
          done = true
        end
      end
    end

    unless done
      # warning "mw t2 tests done"

      a_version = fct_vers.split('.')
      b_version = vers.split('.')

      l = a_version.length
      l = b_version.length if b_version.length > a_version.length

      0.upto(l - 1) do |i|
        # warning "mw t3 [#{i}] a_version= #{a_version[i]} <==> b_version= #{b_version[i]}"

        if a_version[i] > b_version[i]
          if [ "ge", "gt", "ne" ].include?(rel)
            ret = true
            done = true
          elsif [ "eq", "le", "lt" ].include?(rel)
            done = true
          end
          break
        elsif a_version[i] < b_version[i]
          if [ "le", "lt", "ne" ].include?(rel)
            ret = true
            done = true
          elsif [ "eq", "ge", "gt" ].include?(rel)
            done = true
          end
          break
        end
      end
    end

    unless done
      ret = true if [ "eq", "ge", "le" ].include?(rel)
    end

    ret
  end
end

That makes it pretty easy to create conditional steps either in the manifests or even in templates. For manifests, it would look like:

if pkg_installed("foo/tomcat6/password", "ge", "0.1.201301032306") {
  ...
}

In templates, the similar construct would look like:

<% if scope.function_pkg_installed([ "foo/tomcat6/password", "ge", "0.1.201301240309" ]) -%>
  ...
<% end -%>

I hope this helps somebody;-)