Solaris 11 – IPS Packages and Puppet

By | March 18, 2013

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;-)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.