Facter with a custom paintjob
A little while back I was setting up Cloudwatch Logs for some containers, and did this via the use of CloudFormation templates, Puppet and Facter.
Fortunately I was able to do all this with refs inside the CloudFormation template and echo it into a bootstrap.txt
file through the EC2 instance’s User Data.
The next step was to then store the CloudWatch Logs group as a custom fact to be picked up by Puppet.
Puppet can then configure the Amazon CloudWatch Logs logging driver option for the service that runs the container.
Custom Facts#
What is a fact?
A fact is a key/value data pair that represents some aspect of node state, such as its IP address, uptime, operating system, or whether it’s a virtual machine.
So what’s a Custom Fact?
Sometimes you need to be able to write conditional expressions based on site-specific data that just isn’t available via Facter, or perhaps you’d like to include it in a template.
Since you can’t include arbitrary Ruby code in your manifests, the best solution is to add a new fact to Facter. These additional facts can then be distributed to Puppet clients and are available for use in manifests and templates, just like any other fact would be.
Example#
For this situation I wanted to create a fact named cloudwatch_logs_group
that contains the name of the logs groups and is stored within a bootstrap.txt
that was created by the instance’s User Data
. Here’s an example of the bootstrap.txt:
cloudwatch_logs_group=logs_group_name
Now to create the fact…
Facter searches all directories in the Ruby $LOAD_PATH variable for subdirectories named ‘facter’, and loads all Ruby files in those directories.
The first step is to create a subdir in your Puppet module called facter
for the fact to be evaluated during the Puppet run.
Next we create the fact inside the facter
directory, for example cloudwatch_logs_group.rb
:
Facter.add('cloudwatch_logs_group') do
bootstrap_file = "/path/to/bootstrap.txt"
confine :some_other_fact => 'true'
setcode do
if bootstrap_file.exists?
Facter::Core::Execution.execute("grep 'cloudwatch_logs_group' #{bootstrap_file} | awk -F '=' '{print $2}'")
end
end
end
This will successfully create the fact we’re looking for however, what does it all mean?
The custom fact is created with the name cloudwatch_logs_group
and then initiates the block to iterate through.
Facter.add('cloudwatch_logs_group') do
Here we’re setting the variable bootstrap_file
to the location of the bootstrap.txt
on the host.
bootstrap_file = "/path/to/bootstrap.txt"
Confine restricts the fact to only run on systems that matches another given fact. This then means I can make sure the fact only runs on the machines I want it to run on.
confine :some_other_fact => 'true'
This is the actual meat of the code where it checks to see if the bootstrap.txt
file exists. If true, it greps cloudwatch_logs_group
and returns the field after the =
sign. Whatever’s returned by the Facter::Core::Execution.execute
method is then stored as the fact’s value.
In this case the value will be the CloudWatch Logs Group name.
setcode do
if bootstrap_file.exists?
Facter::Core::Execution.execute("grep 'cloudwatch_logs_group' #{bootstrap_file} | awk -F '=' '{print $2}'")
end
end
Provided all goes well, you should now have a fully working Custom Fact!
Conclusion#
If you’re a Puppet user, I can guarantee you’ll be using Facter in some way or another. Having the ability to specify a custom fact has proven (from my own personal experience) to be a powerful tool.
Custom Facts help you work around those sometimes pesky environment variables, and allows you to solve a potentially complex problem with some simple logic.