My first puppet facts

An apparently simple problem: have puppet manage an host's own entries: the localhost one, and the hostname's one. Well, we have plenty of facts to help us with this: we have ipaddress, and hostname, and fqdn. It should be simple, right?

But think. What happens if the host is multihomed? What if one of the interfaces is a bond interface? Does facter choose a value for ipaddress in a smart way?

Unfortunately, it doesn't. And to make it smart, you have to feed it the right thing. And to feed it, you need to write code in Ruby. Oh oh…

I decided to give it a try, and to pull in some shell script to actually do the job (bad thing, I know, but I really don't have time to learn ruby; and when I'll use some time to learn another programming language, I'd go through python and perl 6 first). …The first thing to do when you are trying something new is to find some authoritative tutorials. There is one in three parts in the puppet community blogs: Facter 101, Testing and Deployment, and a pretty advanced one called Caching and TTL.

Using the first two I managed to write two facts called ifdefault and ipdefault. The two facts actually leveraged two shell script: the first one parses the output of the netstat command to find the interface that has the default route on itself. The second script parsed the output of ifconfig to pull the address of that interface.

That worked, but then it came to my mind that the IP address for the interface, say, eth0, is returned by the fact ipaddress_eth0, so while I still needed my shell script to find the right interface, I could leverage facter's own knowledge to get the other information.

That seemed to be easy, too, but something didn't quite work like it should: when I ran facter without arguments, my facts were correctly returned. But when I ran facter ipdefault it complained and returned nothing. Maybe I hit a bug?

I started google-ing, and it turned out it was actually a feature: there was yet another document I needed to read: Adding Custom Facts to Facter from the Puppetlabs wiki. There I found an important information that the other tutorials were missing: you need to explicitly load into your code all the facts you plan to use.

That was all, and now facter ifdefault tells me which interface is the "default" one, and facter ipdefault returns the IP address of that interface.

And here's the code.

The shell script

#!/bin/bash

export LANG=C
export PATH="/puppet/common/fact-helpers:$PATH"

OS=$( uname -s )

if [ "$OS" == "Linux" ]
then
  DEFAULT_IF=$( netstat -rn | awk '$1=="0.0.0.0" { print $8 }' )
elif [ "$OS" == "SunOS" ]
then
  DEFAULT_IF=$( netstat -f inet -rn | awk '$1=="default" { print $6 }' )
else
  echo "Unsupported OS"
  exit 255
fi

echo $DEFAULT_IF

exit 0

The ifdefault fact

require 'facter'
Facter.add(:ifdefault) do
  setcode do
    Facter::Util::Resolution::exec("/puppet/common/fact-helpers/ifdefault.sh")
  end
end

The ipdefault fact

require 'facter'
Facter.add(:ipdefault) do
  setcode do
    # Load all default facts
    Facter.loadfacts()

    # Retrieve our custom fact, ifdefault
    ifdefault=Facter.value(:ifdefault)

    # Now compose the name of the fact containing the ip address of the
    # interface returned by ifdefault, and return it
    Facter["ipaddress_#{ifdefault}"].value()
  end
end
Advertisements

2 thoughts on “My first puppet facts

  1. I've just re-read it, and I see that you had the same problem as me. Definitely, that loadfacts stuff needs to be added to the existing tutorials. I can't understand why everybody forgets about that "small detail"…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s