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
Good stuff!
This was my first fact, still in production and going strong: datacenter.rb 🙂
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"…