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.”
if [[ $IP == “10.123”* ]]; then
elif [[ $IP == “10.157”* ]]; then
elif [[ $IP == “192.168.120”* ]]; then
elif [[ $IP == “192.168.150”* ]]; then
elif [[ $IP == “192.168.190”* ]]; then
elif [[ $IP == “10.147.150”* ]]; then
When this script is run, it will print one of the following:
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 | 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: