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