Software inventory with facter

Getting hash of installed software with facter:

# File: /your/puppetdir/modules/yourmodule/lib/facter/sw_inventory.rb
# Description: Report versions of installed software

require 'facter'

Facter.add(:sw_inv) do
    setcode do
        # OSX
        if Facter.value(:osfamily) == 'Darwin'
            require 'facter/util/macosx'
            sw_inv = {}
            # Lists all installed software
            output = Facter::Util::Macosx.intern_xml(Facter::Util::Macosx.profiler_xml('SPApplicationsDataType'))
            output[0]["_items"].each do |sw|
                name = sw["_name"]
                if sw["version"] != nil
                    version = sw["version"]
                    version = "N/A"
                sw_inv[name] = version
        # Debian, Ubuntu
        if Facter.value(:osfamily) == 'Debian'
            # Just parse dpkg output...
            output = Facter::Util::Resolution.exec("dpkg -l | grep ^ii | awk '{print $2, $3}'")
            # Output format is:
            # software version
            # software2 version
            if output != nil
                # Splat might blow up stack
                sw_inv = Hash[output.gsub(/\/n/,' ').split(" ").each_slice(2).to_a]
        # Redhat, Centos, Fedora
        if Facter.value(:osfamily) == 'RedHat'
            # rpm output to hash
            # output format is software version software2 version...
            output = Facter::Util::Resolution.exec("rpm -qa --qf \"%{NAME} %{VERSION} \"")
            if output != nil
                sw_inv = Hash[output.split(" ").each_slice(2).to_a]

Note that all sw does not install itself using standard packaging tools e.g OSX java plugin needs:

version = Facter::Util::Resolution.exec('/usr/bin/defaults read /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin/Contents/Info CFBundleVersion 2> /dev/null')
if version.to_s == ''
    version = "N/A"
sw_inv["Java plugin"] = version

Fact is in format:

sw_inv => {"Preview"=>"8.0", "Draw"=>"42.0.2311.90", "iOS Simulator"=>"8.3",..., "Dashboard"=>"1.8"}

Local testing can be done with FACTERLIB=`pwd` facter

You should see fact sw_inv in output, if operating system is not matched, you won’t see it

You can read data from puppetdb using for example python:

# Query puppet DB and print installed sw  

import sys
from pypuppetdb import connect
from ast import literal_eval

    puppetdb = connect(host=u'', port=8081, ssl_key='mykey.pem', ssl_cert=mycert.pem')
except Exception, e:
    sys.exit("Problem connecting to DB: %s" % e)

facts = puppetdb.facts('sw_inv')
for fact in facts:
    # certname = nodename in puppet 
    certname = str(fact).replace('sw_inv/',"") 
    # Skip stringified facts, node's puppet config is not correct
    if '=>' not in fact.value:
        print("stringified facts, doh....")
    fact_val = literal_eval(str(fact.value.encode('ascii', 'ignore')).replace('=>',':'))
 for sw, ver in fact_val.items():
     print("sw: %s ver: %s" % (sw, ver))

Beware that you’ll get lot’s of data…

Resolving fact takes 91.80ms in Ubuntu 14.10 with 3779 installed packages (processor0 => Intel(R) Core(TM) i7-2640M CPU @ 2.80GHz, processorcount => 4)

2 comments for “Software inventory with facter

  1. Brandon Martin
    09.03.2017 at 18:51

    I’m using this fact successfully on redhat/debian, it’s working great!

    I’m trying to get a few osx servers in my puppet environment, and would really like to use this fact. I see the note ‘that all sw does not install itself using standard packaging tools’, could this be related to the error I’m getting?

    Facter cannot find the file ‘lib/util/macosx’, I have found the file on github, but am not sure if something has changed with Facter on mac (10.12), I’m seeing other Facter errors as well for network_ipaddress6

    • ETP
      10.03.2017 at 16:11

      Note is a reminder that system can have stuff installed in unknown locations, just in case someone is using fact as a security measure or something 😉

      Looks like something has changed in facter, unfortunately I do not have environment to test this anymore…

Leave a Reply

Your email address will not be published. Required fields are marked *

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