Custom facts

Facts are the most comfortable kind of variables to work with:

  • They are at top scope, therefore easily accessible in every part of our code
  • They provide direct and trusted information, being executed on the client
  • They are computed: their values are set automatically and we don't have to manage them manually.

Out-of-the-box, depending on Facter version and the underlying OS, they give us:

  • Hardware information (architecture bios_* board* memory* processor virtual)
  • Operating system details (kernel* osfamily operatingsystem* lsb* sp_* selinux ssh* timezone uptime*)
  • Network configuration (hostname domain fqdn interfaces ipaddress_* macaddress_* network*)
  • Disks and filesystems (blockdevice_* filesystems swap*)
  • Puppet related software versions (facterversion puppetversion ruby*)

We already use some of these facts in modules to manage the right resources to apply for our operating systems, and we already classify nodes according to their hostname.

There's a lot more that we can do with them, we can create custom facts useful to:

  • Classify our nodes according to their functions and locations (using custom facts that might be named (dc env region role node_number and so on)
  • Determine the version and status of a program (php_version mysql_cluster glassfish_registered and so on)
  • Return local metrics of any kind (apache_error_rate, network_connections)

Just think about any possible command we can run on our servers and how its output might be useful in our manifests to model the resources we provide to our servers.

We can write custom facts in two ways:

  • As Ruby code, distributed to clients via pluginsync in our modules
  • Using the /etc/facter/facts.d directory where we can place plain text, JSON, YAML files, or executable scripts

Ruby facts distributed via pluginsync

We can create a custom fact editing, in one of our modules, files called lib/facter/<fact_name>.rb, for example, a fact named role should be placed in a file called lib/facter/role.rb and may have content as follows:

require 'facter'

We require the facter class and then we use the add method for our custom fact called role:

Facter.add("role") do

We can restrict the execution of this fact only to specific hosts, according to any other facts' values. This is done using the confine statement, here based on the kernel fact:

  confine :kernel => [ 'Linux' , 'SunOS' , 'FreeBSD' , 'Darwin' ]

We can also set a maximum number, in seconds, to wait for its execution:

     timeout = 10

We can even have different facts with the same name and give a weight to each of them; facts with higher weight value are executed first and facts with lower weight are executed only if no value is already returned:

  has_weight = 40

We use the setcode method of the Facter class to define what our fact does; what is returned from the block of code contained here (between do and end) is the value of our fact:

  setcode do

We can have access to other facts values with Facter.value, in our case, the role value is a simple extrapolation of the hostname (basically the hostname without numbers). If we have different naming schemes for our nodes and we can deduce their role from their name, we can easily use other Ruby string functions to extrapolate it:

    host = Facter.value(:hostname)
    host.gsub(/d|W/,"")
  end
end

Often, the output of a fact is simply the execution of a command; for this case, there is a specific wrapper method called Facter::Util::Resolution.exec, which expects the command to execute as a parameter.

The following fact, called last_run, simply contains the output of the command date:

require 'facter'
Facter.add("last_run") do
  confine :kernel => [ 'Linux' , 'SunOS' , 'FreeBSD' , 'Darwin' ]
  setcode do
    Facter::Util::Resolution.exec('date')
  end
end

We can have different facts, with different names, on the same Ruby file, and we can also provide dynamic facts, for example, the following code creates one different fact, returning the installed version for each package. Here, the confinement according to the osfamily is done outside the fact definition:

if Facter.osfamily == 'RedHat'
  IO.popen('yum list installed').readlines.collect do |line|
      array = line.split
      Facter.add("#{array[0]}_version") do
          setcode do
              "#{array[1]}"
          end
      end
  end
end
if Facter.osfamily == 'Debian'
  IO.popen('dpkg -l').readlines.collect do |line|
      array = line.split
      Facter.add("#{array[1]}_version") do
          setcode do
              "#{array[2]}"
          end
      end
  end
end

Remember that to have access, from the shell, to the facts we distribute via pluginsync, we need to use the --puppet(-p) argument:

facter -p

Otherwise, we need to set the FACTERLIB environment variable pointing to the lib directory of the module that provides it, for example:

export FACTERLIB=/opt/puppetlabs/puppet/cache/lib/facter

External facts in facts.d directory

Puppet Labs's stdlib module provides a very powerful addendum to the facts, a feature called external facts, which has proven to be so useful to deserve inclusion directly into core Facter since version 1.7.

We can define new facts without even the need to write Ruby code, just by placing files in the /opt/puppetlabs/facter/facts.d/ or /etc/facter/facts.d/ directory (/etc/puppetlabs/facter/facts.d with Puppet Enterprise and C:ProgramDataPuppetLabsfacterfacts.d on Windows).

These files can be simple .txt files such as /etc/facter/facts.d/node.txt, with facts declared with the following syntax:

role=webserver
env=prod

YAML files such as /etc/facter/facts.d/node.yaml, which for the same sample facts would appear like this:

---
  role: webserver
  env: prod

Also, JSON files files such as /etc/facter/facts.d/node.json with:

{
  'role': 'webserver',
  'env': 'prod'
}

We can also place plain commands in any language; on Unix, any executable file present in /etc/facter/facts.d/ is run and is expected to return the facts' values with an output like this:

role=webserver
env=prod

On Windows, we can place files with .com, .exe, .bat, .cmd, or .ps1 extensions.

Since Puppet 3.4.0, external facts can also be automatically propagated to clients with pluginsync; in this case, the directory synced is <modulename>/facts.d (note that this is at the same level of the lib directory).

Alternatively, we can use other methods to place our external facts, for example, in post installation scripts during the initial provisioning of a server, or using directly Puppet's file resources, or having them generated by custom scripts or cron jobs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset