Facter Part 1: Facter 101
With the release of facter 1.5.8, it is a great opportunity to revisit this utility and review how it works in conjunction with Puppet. This is a multi-part blog post that will progressively dive deeper into facter and writing custom facts for puppet. Facter is a standalone tool based on Ruby that provides system information in “key => value” pairs:
# facter architecture => i386 ... ipaddress => 172.16.182.129 is_virtual => true kernel => Linux kernelmajversion => 2.6 ... operatingsystem => CentOS operatingsystemrelease => 5.5 physicalprocessorcount => 0 processor0 => Intel(R) Core(TM)2 Duo CPU P8800 @ 2.66GHz processorcount => 1 productname => VMware Virtual Platform ... |
Facter can be used independently from Puppet to gather information about a system. Whether it’s parsing the /proc/xen directory on Linux or running prtdiag command on Solaris, the tool does a great job abstracting the specific operating system commands used to determine the collection of facts. When used in conjunction with Puppet, facts gather through the system allows the puppet master to make intelligent decisions during manifest compilation. Within your puppet manifest, you can reference any key value pairs provided by facter by prefixing the hash key with “$”, since facter variables are set at top scope I prefer “$::” to make it explicit:
case $::operatingsystem { 'CentOS': { include centos } 'MacOS': { include mac } } |
If the default set of facts are not sufficient, there are two ways to extend Facter to provide additional fact. First we can create custom facts using ruby. This sounds far more intimidating, but as you can see in the example below it’s actually rather straightforward.
require 'facter' Facter.add(:system_role) do setcode "cat /etc/system_role" end |
Let’s break down the example line by line, require ‘facter’ loads the facter library, we add a new fact called system_role via Facter.add(:system_role). For users new to ruby, :system_role is a symbol for “system_role”, do/end indicate the beginning and end of a code block ({} also works). Last, setcode method executes the string as an OS command to obtain the value of system_role fact. Obviously not all facts is are a one line command, in that case, provide a do/end block with setcode method and the last line will be the return value for the custom fact. After a quick rewrite the previous example is now:
require 'facter' Facter.add(:system_role) do setcode do Facter::Util::Resolution.exec("cat /etc/system_role") end end |
If you read an earlier version of this blog post, originally OS commands was executed via %x{}, and that has been replaced by Facter::Util::Resolution.exec, because it provides several benefits such as searching the command $PATH, chomp the output string to remove newline, and handle the difference between Windows and Unix.
Another way to extend facter is by using environment variables prefixed with FACTER_. So matching the ruby example above would simply be:
export FACTER_system_role=`cat /etc/system_role`; facter |
One thing to be aware is environment variables will override custom ruby facts, but not facts generated by facter. FACTER_operatingsystem will not override the value CentOS in the example above, but FACTER_system_role will take precedence over the custom fact written in system_role.rb. It makes sense to name custom facts either with a sensible prefix, which avoids this problem as well as makes finding custom facts easier (there’s also suggestions to use qualified names along the line of com.puppetlabs.system_role). Though you could write a wrapper to update facter environment variables on each puppet execution, it make more sense to use them when the facts are static, and write ruby custom facts if the value is dynamic.
To end this post, a quick summary regarding facter:
- When using facts in puppet manifest, preface with $:: to explicitly specify top scope.
- Facter can be extended using ruby custom facts, or environment variables starting with FACTER_.
- Facts provided through FACTER_ environment variables have precedence over ruby custom facts.
- Custom facts should be named appropriately to avoid potential collisions.
We will dive into further details on how to test/distribute custom facts in part 2.
17 Comments
So custom facts are supposed to go in puppet ‘vardir’?
“Ruby facts should be used for dynamic values, environment variables should be used for static values (*).”
I don’t think this is the right general rule. If you have a static value fact that you wish to distribute to all your clients, there’s nothing wrong with having a “real” fact that supplies the static value, rather than working out some way of pushing environment variables to them.
Custom facts should be placed under lib/facter sub directory inside modules to take advantage of the autoloader (0.25.x and higher):
[MODULE_PATH]/[module_name]/lib/facterIn puppet 0.24.x custom ruby facts should go into plugins/facter sub directory.
[MODULE_PATH]/[module_name]/plugins/facterYeah, I originally intended to elaborate more, hence the (*). Ruby custom facts is definitely suitable for distribution of static values, and I’ve also seen wrapper scripts around puppet that source environment variables so I’ve revised that line in the post.
Facter Part 2: Testing and Deployment | Puppet Labs
[...] facter part 1 we showed how to use facter, as well as how to create custom facts using ruby and environment [...]
Hi,
I just tried the facter gem. Seems really great but unfortunately there are a lot of warnings when running with ruby -w, which is what we run with all the time so we cannot use it.
Just letting you know about these warnings :)
Cheers,
James
James, sorry not sure what to suggest, since I’m not sure about the facter, ruby (1.9.x most likely was give warnings), and gems version. If you have any specific issues, feel free to provide more details as a bug report to projects.puppetlabs.com.
Hi
Is there a way to load a factor fact into an array rather than a string? I think there are a lot of situations where facts should be an array or a hash even. Take the ec2_userdata fact, by default this returns a string with all the spaces removed rather than an array: -
ec2_userdata => access_key=XXXsecret_key=XXXName=puppetmplatform=testbootbucket=pclouds-live-bootbucketprofile=puppetmaster
However, if you run it in Yaml or JSON modes it seems to understand that the fact is actually an array of things: -
ec2_userdata:
– access_key=XXX
– secret_key=XXX
– Name=puppetm
– platform=test
– bootbucket=pclouds-live-bootbucket
– profile=puppet
– master
or
“ec2_userdata”:["access_key=XXX","secret_key=XXX","Name=puppetm","platform=test","bootbucket=pclouds-live-bootbucket","profile=puppet","master"]
Is there a way to use these in puppet? My simple test showed me that puppet was using the single string fact.
regards
Dave
Dave, at the moment facter is still just string value. I think we can double check if you run:
notice (inline_template(“< %= ec2_userdata.inspect %>“))
Support for hash/array is on the feature roadmap, You can to use to_yaml, and yaml_load to overcome this limitation (see stdlib library).
Thanks,
Nan
Hi folks,
is there any way to sent parameters to the facter? The reason why I need this:
I should check for the existence of a particular folder on the target machine. I wrote a fact for that (see below), but would be great to set the lookup path dynamically in Puppet.. Is there a nice way how to do this in Puppet?
Of course, facts should be facts and the value they check should basically not be set dynamically. But would be cool to have such a functionality available.
Facter.add(“cache_folder_created”) do
setcode do
File.exists?(‘/data/cache’)
end
end
If you mean set a variable in the puppet manifests, then no. Because at compile time, the master needs all available fact, it can’t compile a dynamic fact prior to requesting for facts.
Client pluginsync -> Client discover system Facts -> Master compilation -> Client apply catalog -> Client report.
During pluginsync, the client needs the facts and I don’t see a way to dynamically pass different values. If you run puppet apply, because the compilation occur on the same system, you can work around this limitation, but not during agent/master mode.
would it be possible to set the variable in the manifest then force a fact sync before compilation??
Custom facts can’t use variables in the puppet manifests to do something dynamic. You can use facts to set variables in the manifests behave, but not the the other way around.
Let userdata tell Puppet how to configure your cloud VMs
[...] To start learning more about Facter and how to use it see: Facter 101. [...]
Simple facts with Puppet « soimasysadmin
[...] useful link for facter incase you feel like reading more Share this:MoreLike this:LikeBe the first to like [...]
I think this has some flaws to it. I think it should be possible to request a facter fact from the manifests with maybe some sort of dynamic/lambda ruby function.
Here is a use case:
Why create facter facts for each individual object/class learned via WMI on Windows systems?
$::wmi_classname_object would be extremely powerful…
How to Install Puppet Open Source on CentOS 6.3 - 6tech.org
[...] Facter [...]