Puppet Blog

Back to Index

Facter Part 1: Facter 101

Posted on
By
Nan Liu
in
Blog, How to
Responses
17 Comments »

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

Nigel Kersten

“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.

nan

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/facter

In puppet 0.24.x custom ruby facts should go into plugins/facter sub directory.

[MODULE_PATH]/[module_name]/plugins/facter

nan

Yeah, 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.

James French

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

Nan Liu

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.

Dave McCormick

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

Nan Liu

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

Tobi

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

Nan Liu

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.

Mike Jeski

would it be possible to set the variable in the manifest then force a fact sync before compilation??

Nan Liu

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.

rismoney

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…

Leave a Response