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