Creating “Custom” Facts in Puppet

The servers that I’m managing can be divided into two groups, colo and local servers. While most of the configuration has been similar between these two, there have been a growing number of things which I’ve had to configure differently through Puppet.

The latest example of this is with the time daemon. Local servers needed to have ntp installed, and colo servers needed to have ptp installed.

When I tried to implement these different installations, a small error in the Puppet code caused a subset of colo servers to be seen as local servers, so they ended up having ntp installed and configured instead of ptp.

Part of the reason this error occurred is that the logic for determining if a server is local or not is in my Puppet manifest code, which has become large and unruly. This would have been easily avoidable if the logic didn’t have to be programmed directly in my Puppet manifest file. This led me to think, what if I could make a custom fact that would simply say whether or not a server was a colo server of a local server? This would greatly simplify the code in my manifests.

Turns out that this customization is possible and such a thing is called an external fact and not a custom fact, which is slightly different (This is why “custom” is in quotes in the title. Puppet has custom and external facts, and while the two are similar they are not exactly the same thing.)

External facts are just scripts which return text in the form fact=value. The first part was creating the script, the second part was testing that the script correctly returned the fact, the third part was getting it installed on all servers, and the final part was using the fact in a Puppet manifest.

 

1. Creating the script

This is the bash script I created to return a new fact that I named “location.” It makes use of another fact available out-of-the-box called “ipaddress.”

#!/bin/bash
IP=`facter ipaddress`
if [[ $IP == “10.123”* ]]; then
location=local
elif [[ $IP == “10.157”* ]]; then
location=local
elif [[ $IP == “192.168.120”* ]]; then
location=local
elif [[ $IP == “192.168.150”* ]]; then
location=colo
elif [[ $IP == “192.168.190”* ]]; then
location=colo
elif [[ $IP == “10.147.150”* ]]; then
location=colo
else
location=unknown
fi
echo location=$location

When this script is run, it will print one of the following:

location=local
location=colo
location=unknown

 

2. Testing the external fact

I copied my script to hthe /var/lib/puppet facts.d directory.

If this were an out-of-the-box fact, the following two commands could be used to test that it’s working correctly:

facter location
facter | grep location

but neither one of those came up with anything.

That didn’t mean that Puppet wouldn’t recognize the fact. The syntax just had to be changed a little for facter to recognize it.

The key is to tell facter where to find this external fact by using the –external-dir option. The following two commands both confirmed that the fact was available:

facter –external-dir=/var/lib/puppet/facts.d location
facter –external-dir=/var/lib/puppet/facts.d | grep location

Furthermore, there is a third test which can be run to know if Puppet itself recognizes the fact:

puppet apply -e “notice($::location)”

Running that command confirmed that Puppet would be able to recognize my fact.

After those tests succeeded, I copied them to several different servers and confirmed that they returned the expected results.

 

3.Distributing the fact

In order to use this fact for all servers, it’s not enough to just copy it to the Puppet master server, all the client servers have to have it. In order for Puppet to automatically distribute the fact to all clients, it will have to be installed in the directory <Puppet Module Directory>/<Module>/facts.d on the Puppet master server, and when the puppet clients pull from the server, pluginsync works in the background to transfer the script to the correct directory on the client.

So I created the directory /etc/puppet/modules/facts/facts.d and copied my script there.

I ran “puppet agent -t” on a client server, pluginsync found my file on the puppet master server, and copied it to the directory /var/lib/puppet/facts.d on the client machine.

I then ran the aforementioned test commands and all three tests passed.

 

4. Using the fact

I could now simplify the code in Puppet since I no longer had to program in the logic of determining whether or not a server was at a colo or not, all I had to do was reference the fact.

Basically my puppet code would simplify to:

if $location == ‘colo’ {
            # run these commands
} elsif $location == ‘local’ {
            # run these other commands
}

Simple Share Buttons