External Facts

I have an application that I have running on 30 servers. It requires a serial number to be used. The serial is stored in a conf file. Every time the application updates, you need a new serial number.

The vendor provides me with a .txt file with three columns. MAC address, serial number, and version. Every time a new version comes out, they automatically send me an updated file with all the serial numbers for all of the systems and versions.

I decided to use an External Fact, but this has presented a few problems.

  1. I put the External Fact in my module in the module/facts.d directory. It is an .sh file and it runs on all machines, not just the machines that use that module.

  2. Because this External Fact needs to get data out of the .txt file, the txt file is not on all of the nodes and gets an error. I distribute the .txt file in the module (possibly a mistake) that installs and configures this program. This wouldn’t be an issue if I could solve problem 1.

  3. Because the .txt file has to make it on the machine first, the first time the Fact runs, it doesn’t have the .txt file and it can’t generate the Fact. If I upgraded the .txt file, then the node (after two runs) would have the updated serial as a Fact, and then the current program wouldn’t be licensed. I need Facter to be able to update the Fact before the Puppet run to prevent a version mismatch.

  4. All of this aside, (I’m just testing the Fact for now) the Foreman GUI shows the same data for my Fact on every node. They all show the first serial in the txt file. If on the node I type facter -p fact_name, then I get the right data. If I run puppet agent –test, the Foreman GUI shows the right dat, but then 30 minutes later (next Puppet run), it reverts back to the same information as all of the other nodes.

I updated Foreman from 1.12.1 to 1.16.0 today (another post said many fixes were done to Facts in the display) to try to solve the GUI issue, but I am still having the issue in this version.

I have two Custom Facts that are located in module_name/lib/facter/*.rb. They also run on every machine and not just the machines assigned to the module. However in this instance, I want them all to get this Fact. These Facts in the GUI match the output of facter -p fact_name.

Here is my External Fact code. I’d like to know if there is a better way or if the code is causing any of the issues above.

#! /bin/bash

mac=$(facter macaddress --no-external-facts)
program_serial=$(/bin/cat /root/program_serial.txt | grep --ignore-case "$mac" | grep 1.0.5 | awk '{print $2}')
echo "program_serial=$program_serial"

AArb000 - two suggestions for you - instead of distributing a text file to the servers make the fact source a public web directory and look for the text file -

Instead of this

program_serial=$(/bin/cat /root/program_serial.txt | grep --ignore-case “$mac” | grep 1.0.5 | awk ‘{print $2}’)
echo "program_serial=$program_serial

Try this

program_serial=$(curl -ks https://$(web_host_url)/pub/program_serial.txt | grep --ignore-case “$mac” | grep 1.0.5 | awk ‘{print $2}’)
echo "program_serial=$program_serial

The other thing you could do is use expose class parameters to foreman and use the foreman api to find the machine id by mac and update the serial number directly on the host class parameter.

Facts in puppets will always run - even if you noop or do a dry run. There are ways to have them not run but the typical methodology is that facts generate even during test runs.

Thanks for the suggestion on the web. That would prevent errors on the nodes not in the module.

I would have all of the remaining issues so let me ask this.

Is there a way to just make this part of the module itself and not rely on a Fact? I only went with a fact to put the data in the template. Is there a way to build a module to get data from that .txt file and insert it into a .conf file?

I think that may be where you are going with the ‘expose class parameters’, but I don’t really know what that means. I know you can ‘override’ parameters in Foreman, but all I have done with that is manually entering data into the GUI or module based larger groups like timezones. I don’t know of a way to read data from a file and parse it out like I was trying to do with the Fact.

Any ideas on how I can achieve this in a module would be appreciated. That would avoid every other issue in my list.

Had an idea to use a template to create a shell script which would put the macaddress into the script. Then use Puppet to run an exec which would run the script and sed the license key into the conf file.

This works and I like that it will only run on the machines with the application. I do have one issue.

Puppet issues a conf file without a serial number. Puppet inserts serial number. Puppet delivers new conf file without a serial number. Puppet inserts serial number.

I know I can prevent the conf file from being overwritten, but if I need to change other parts of the conf file I won’t be able to.

Is there a way for Puppet to not overwrite the file if the Exec does it, but overwrite it if the template does?

I switched to a custom fact. This code works and displays correctly in the Foreman GUI. It also only runs on machines I want it to. It solves all of the problems outlined above.

Facter.add(:serials) do
  confine :server_role => 'serial'
  setcode do
    Facter::Core::Execution.execute("/usr/bin/curl -ks http://10.41.27.139/serials.txt | grep 1.0.15 | grep " + Facter.value(:macaddress) + " | awk '{print $2}'")
  end
end
2 Likes