Table of contents
1.
Introduction
2.
Custom facts in Puppet
3.
Adding custom facts to Facter
4.
Loading custom facts
4.1.
Using the Ruby load path
4.2.
Using the --custom-dir command line option
4.3.
Using the FACTERLIB environment variable
5.
Two parts of every fact
6.
Using other facts
7.
Configuring facts
7.1.
Confining facts
7.2.
Fact precedence
7.3.
Execution timeouts
8.
Aggregate resolutions
9.
Environment facts 
10.
Writing custom facts in Puppet
10.1.
Writing custom facts in puppet with simple resolutions
10.2.
Main components of simple resolutions
10.3.
How to format facts
11.
Writing structured facts
12.
Writing custom facts in puppet with aggregate resolutions
12.1.
Main components of aggregate resolutions
13.
External facts in Puppet
14.
Executable facts on Unix 
15.
Executable facts on Windows 
16.
Executable fact locations
17.
Troubleshooting
18.
Drawbacks
19.
Frequently Asked Questions
19.1.
What exactly is a Puppet fact?
19.2.
Where can you find Puppet facts?
19.3.
In Puppet, how are variables like $Operatingsystem set?
19.4.
What exactly is the Facter command?
19.5.
In Puppet, how do you make a custom fact?
20.
Conclusion
Last Updated: Mar 27, 2024
Easy

Overview of Custom Facts in Puppet

Author Rashi
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Puppet Enterprise enables you to comprehend your infrastructure's configuration, including all physical components of your data center, virtualized and cloud infrastructure, and everything running in containers. Puppet maintains the appropriate state automatically while giving you complete authority to make adjustments as your company's needs change, ensuring consistency and keeping you in compliance.

Custom Facts in Puppet

The key-value pair is referred to as "fact" in Puppet. Each resource contains its own set of facts, and users can create their custom facts in puppet. The Facter command can be used to display a list of all the environment variables and their values.

Custom facts in Puppet

Custom facts can be added to the main Puppet server by writing snippets of Ruby code. Puppet then distributes the facts to the client via plug-ins in modules.

Adding custom facts to Facter

You may need to write conditional expressions based on site-specific data that Facter does not provide, or you may want to include it in a template.

Because arbitrary Ruby code cannot be included in manifests, the best solution is to add a new fact to Facter. These extra facts can then be distributed to Puppet clients and used in manifests and templates just like any other fact.

Loading custom facts

Facter offers multiple methods of loading custom facts in puppet.

These include:
 

  • $LOAD\_PATH, or the Ruby library load path.
     
  • The --custom-dir command line option.
     
  • The environment variable FACTERLIB.
     

You can use these methods to test files locally before distributing them or to make a specific set of facts available on specific machines.

Using the Ruby load path

Facter looks for subdirectories named Facter in the Ruby $LOAD PATH variable and loads all Ruby files in those directories. Facter loads facter/users.rb, facter/rackspace.rb, and facter/system load.rb.

Using the --custom-dir command line option

Facter accepts multiple –custom-dir command-line options that specify a single directory to search for custom facts. Facter attempts to load all Ruby files in the directories specified.

$ ls my_facts
system_load.rb
$ ls my_other_facts
users.rb
$ facter --custom-dir=./my_facts --custom-dir=./my_other_facts system_load users
system_load => 0.25
users => thomas,pat

Using the FACTERLIB environment variable

Facter also examines the environment variable FACTERLIB for a delimited (semicolon for Windows, colon for all other platforms) set of directories and attempts to load all Ruby files in those directories.

$ ls my_facts
system_load.rb
$ ls my_other_facts
users.rb
$ export FACTERLIB="./my_facts:./my_other_facts"
$ facter system_load users
system_load => 0.25
users => thomas,pat

Two parts of every fact

Most facts contain at least two components.

Parts of every facts

 

  • Facter.add('fact name') is used to determine the name of the fact.
     
  • For simple resolutions, a setcode statement that is evaluated to determine the fact's value.

Facts can be much more complicated than that, but those two are the most common ways to implement a custom fact.

Using other facts

Facter.value('somefact') allows you to create a fact that uses other facts. Facter returns nil if the fact cannot be resolved or is not present.

Facter.add('osfamily') do
  setcode do
    distid = Facter.value('lsbdistid')
    case distid
    when /RedHatEnterprise|CentOS|Fedora/
      'redhat'
    when 'ubuntu'
      'debian'
    else
      distid
    end
  end
end

Configuring facts

You can use the properties of facts to customize how they are evaluated.

Confining facts

The confine statement restricts the fact to only running on systems that match another given fact, which is one of the more commonly used properties.

Fact precedence

A single fact can have multiple resolutions, each of which determines the fact's value differently. It's common, for example, to have different resolutions for different operating systems. You add the fact again with a different setcode statement to add a new resolution to it.

When there are multiple resolutions for a fact, the first resolution that returns a value other than nil determines the fact's value. The weight property describes how Facter decides on the issue of resolution precedence. After Facter eliminates any resolutions ruled out due to confining statements, the resolution with the highest weight is evaluated first. If that resolution returns nil, Facter moves on to the next resolution (in descending order) until it returns nil.

Execution timeouts

Resolution timeouts are supported by Factor 4. Facter prints an error message if the timeout is exceeded. Facter.warn ensures that the message is printed to STDERR when run as a standalone application. Facter.warn prints the message to Puppet's log when called as part of a catalog application. When an exception is not caught, Facter logs it as an error.

Aggregate resolutions

Use aggregate resolutions if your fact combines the output of multiple commands. Each chunk of an aggregate resolution is responsible for resolving one piece of the fact. After each chunk has been resolved independently, it is combined into a single flat or structured fact and returned.

Aggregate resolutions differ from simple resolutions in several ways, beginning with the fact declaration. To introduce an aggregate resolution, add the :type => :aggregate parameter:

Facter.add('fact_name', :type => :aggregate) do
    #chunks go here
    #aggregate block goes here
end


Each step in the resolution then gets its own named chunk statement: 

chunk('one') do
    'Chunk one returns this. '
end
chunk('two') do
    'Chunk two returns this.'
end


A setcode statement is never used in aggregate resolutions. They have an optional aggregate block that combines the chunks instead. The fact's value is whatever value the aggregate block returns. Here's an example that simply combines the strings from the previous two chunks:

aggregate do |chunks|
  result = ' '
  chunks.each_value do |str|
    result += str
  end
  # Result: "Chunk one returns this. Chunk two returns this."
  result
end


You can skip the aggregate block if all chunk blocks return arrays or hashes. If you do, Facter will combine all of your data into a single array or hash and use that as the value of the fact.

Environment facts 

You can use the CLI or the Ruby API to access a fact set with environment variables (Facter.value or Facter.fact methods).

It's worth noting that environment facts are lowercase before being added to the fact collection; for example, FACTER EXAMPLE and FACTER example resolve to a single fact called example.

Writing custom facts in Puppet

In Facter, a typical fact is a collection of several elements that can be written as a simple value like"flat" fact or as structured data ("structured" fact). This page explains how to write and format facts correctly.

Writing Custom Facts

Writing custom facts in puppet with simple resolutions

Most facts are resolved all at once, eliminating the need to combine data from multiple sources. In that case, the solution is straightforward. Simple resolutions can be found for both flat and structured facts.

Main components of simple resolutions

Simple facts are typically composed of the following components:

1. A Facter.add(:fact name) call:

  • This adds a new fact or a new resolution to an existing fact of the same name.
     
  • A symbol or a string can be used as the name.
     
  • The remainder of the information is contained within the do... end block of the add call.

2. Zero or more confine statements:

  • Determine whether the resolution is appropriate (and therefore is evaluated).
     
  • Can either match another fact's value or evaluate a Ruby block.
     
  • A block is required when given a symbol or string representing a fact name, and the fact's value is passed as an argument to the block.
     
  • The keys of a hash are expected to be fact names. The hash values are either the expected fact values or an array of values to compare.
     
  • If a block is provided, the confine is appropriate only if the block returns any value other than false or nil.
     

3. Optional has_weight statement:

  • Multiple resolutions are when available for a fact, and resolutions are ranked from highest to lowest weight value.
     
  • It must be a positive integer, i.e., greater than 0.
     
  • Number of confined statements for the resolution is the default.
     

4. Setcode statement that determines the value of the fact

  • It can accept a string or a block.
     
  • Facter executes a string as a shell command when given one. If the command is successful, the command's output is the fact's value. If the command fails, the next appropriate resolution is considered.
     
  • If a block is provided, the fact's value is returned unless the block returns nil. If nil is returned, the next appropriate resolution is considered.
     
  • The Facter::Core::Execution.exec function can be used to execute shell commands within a setcode block.
     
  • Only the last setcode block is used if multiple setcode statements are evaluated for a single resolution.

How to format facts

Good

Bad

Facter.add('phi') do
  confine owner: "BTO"
  confine :kernel do |value|
    value == "Linux"
  end
  setcode do
    bar=Facter.value('theta')
    bar + 1
  end
end
Facter.add('phi') do
  confine owner: "BTO"
  confine :kernel do |value|
    value == "Linux"
  end
  bar = Facter.value('theta')
  setcode do
    bar + 1
  end
end

When outside the guarded setcode block and in the unguarded part of the Facter.add block, the Facter.value('theta') call is made. This means that the statement is always executed on every system, regardless of confine, weight, or phi resolution. Any code with potential side effects, or code relating to determining the value of a fact, must be contained within the setcode block. Outside of setcode, the only code left is code that helps Facter decide which resolution of a fact to use.

Writing structured facts

Structured facts can be represented as hashes or arrays. You don't need to do anysuch specific thing to mark the fact as structured; if your fact returns a hash or an array, Facter recognizes it as such. Structured facts can be resolved in simple or aggregate ways.

Writing custom facts in puppet with aggregate resolutions

Aggregate resolutions enable you to divide a fact's resolution into separate chunks. Facter merges hashes or arrays with hashes by default, resulting in a structured fact. Still, you can aggregate the chunks into a flat fact using concatenation, addition, or any other Ruby function.

Main components of aggregate resolutions

Aggregate resolutions differ from simple resolutions: the presence of chunk statements and the absence of a setcode statement. Facter merges hashes or arrays with hashes or arrays if the aggregate block is not present.

1. CallingFacter.add(:fact_name, :type => :aggregate):

  • It introduces a new fact or a new resolution for an already existing fact of the same name.
     
  • A symbol or a string can be used as the name.
     
  • For aggregate resolutions, the:type =>:aggregate parameter is required.
     
  • The remainder of the information is contained within the do... end block of the add call.
     

2. Zero or more confine statements:

  • Determine whether the solution is appropriate and (therefore, is evaluated).
     
  • They can either compare the value of one fact to another or evaluate a Ruby block.
     
  • A block is required when given a symbol or string representing a fact name, and the fact's value is passed as an argument to the block.
     
  • The keys of a hash are expected to be fact names. The hash values are either the expected fact values or an array of values to compare.
     
  • If a block is provided, the confine is appropriate if the block returns a value other than nil or false.
     

3. An optional has_weight statement:

  • This function evaluates multiple resolutions for a fact from the highest weight value to the lowest.
     
  • It must be a positive integer, i.e., greater than zero.
     
  • The number of confined statements for the resolution is the default.
     

4. One or more than one calls to chunk, each containing:

  • Name (as the argument to chunk).
     
  • A piece of code is in charge of converting the chunk to a value. The block returns the chunk's value; it can be of any type but is typically a hash or array.
     

5. An optional aggregate block:

  • Facter automatically merges hashes and arrays if they are not present.
     
  • To merge the chunks in any other specific way, you must use aggregate, which requires a code block.
     
  • The block is given a single argument (in this case, chunks), a hash of chunk name to chunk value for all chunks in the resolution.
     

External facts in Puppet

External facts enable the use of arbitrary executables or scripts as facts and the setting of facts statically with structured data. You can use this information to create a custom fact in Puppet, Perl, C, or a one-line text file.

Executable facts on Unix 

On Unix, executable facts are activated by placing an executable file in the standard external fact path. On Unix, executable facts always require a shebang (#!) The execution of the fact fails if the shebang is missing.

An example external fact written in Python is given below: 

#!/usr/bin/env python
data = {"key1" : "value1", "key2" : "value2" }
for k in data:
    print "%s=%s" % (k,data[k])


Ensure that the script has set it's execute bit:

chmod +x /etc/facter/facts.d/my_fact_script.py


The script should always return key-value pairs, JSON, or YAML for Facter to parse.

Facter parses data returned by custom executable external facts in YAML or JSON format. Facter returns to parsing the returned value as a key-value pair if it is not YAML.

Executable facts on Windows 

On Windows, executable facts are activated by placing an executable file in the external fact path. According to the external facts interface, Windows scripts should end with a known extension. Lines can end with either LF or CRLF. The extensions listed below are supported:
 

  • .exe and .com: binary executables
     
  • .cmd and .bat: batch scripts, and
     
  • .ps1: PowerShell scripts
     

The script should always return key-value pairs, JSON, or YAML. Facter parses data returned by custom executable external facts in YAML or JSON format. Facter falls back to parsing the returned value as a key-value pair if it is not YAML.

Executable fact locations

Using pluginsync, you can distribute external executable facts. Place external executable facts in MODULEPATH>/MODULE>/facts.d/ to add them to your Puppet modules.

External facts must be placed in a standard directory if you are not using pluginsync. This directory's location varies depending on your operating system, whether you're using Puppet Enterprise or open source releases, or running as root or Administrator. With the —external-dir option, you can specify the external facts directory when calling Facter from the command line.

In a module(preferred): 

<MODULEPATH>/<MODULE>/facts.d/


On Linux, Unix, or Mac OS X, there are three directories mentioned: 

/opt/puppetlabs/facter/facts.d/
/etc/puppetlabs/facter/facts.d/
/etc/facter/facts.d/


Windows: 

C:\ProgramData\PuppetLabs\facter\facts.d\


When running as a non-Administrator or non-root user: 

<HOME DIRECTORY>/.facter/facts.d/

Troubleshooting

If your external fact does not appear in Facter's output, running Facter in debug mode can help you figure out why and which file is causing the issue:

# puppet facts --debug


If an external fact doesn't match what you have configured in your facts.d directory, check to see if you have defined the same fact using the stdlib module's external facts capabilities.

Drawbacks

External facts, while providing a mostly-equal way to create variables for Puppet, have a few drawbacks:
 

  • An external fact cannot be used to refer to another fact. Due to parsing order, you can reference an external fact from a Ruby fact.
     
  • External executable facts are forked rather than executed concurrently.

Frequently Asked Questions

What exactly is a Puppet fact?

The key-value pair is referred to as "fact" in Puppet. Each resource has its own set of facts, and the users can create their custom facts in puppet The Facter command can be used to list all of the environment variables and their values.

Where can you find Puppet facts?

Run facter -p on the command line to see a node's fact values or browse facts on node detail pages in the Puppet Enterprise console.

In Puppet, how are variables like $Operatingsystem set?

Facter controls all of the variables. Running a facter in a shell will give you a complete list of the available variables and their values.

What exactly is the Facter command?

Facter is a command-line tool that collects basic information about nodes (systems), such as hardware specifications, network settings, operating system type and version, and more.

In Puppet, how do you make a custom fact?

Custom facts in Puppet can be added to the main server by writing snippets of Ruby code. Puppet then distributes the facts to the client via plug-ins in modules. See Module plug-in types for information on how to add custom facts to modules.

Conclusion

We have now learned what custom facts are in puppets and how to add and write them on windows and Unix operating systems. We also went through some external facts on windows and Unix and learned about their location and how to troubleshoot them.
Check out this problem - First Missing Positive 

You can refer to our guided paths on Coding Ninjas Studio to learn more about DSA, Competitive Programming, JavaScript, System Design, etc. Enroll in our courses and refer to the mock test and problems available. Take a look at the interview experiences and interview bundle for placement preparations.

Live masterclass