Skip to content
 

Facter + Puppet: Writing custom facts to manage Plesk servers

What I’ll describe here is the process of writing some custom facts to retrieve useful information from Parallels Plesk servers, so that you can use them in your Puppet recipes.

For those of you who don’t know what Facter is, I’ll quote the manufacturer’s description:

Reductive’s analysis tool for reporting system configuration and status, used to parametrize Puppet configurations

If you don’t know what Puppet is all about also, check it out right now or this article won’t make sense to you.

Scenario

When you are writing Puppet recipes to manage your servers, sometimes you’d rely on the data (a fact) captured by Facter to make decisions. One of the most used facts is operatingsystem, which will of course give you the operating system name, like CentOS, RedHat or Debian.

Why is this useful? Well… Let’s assume you are trying to standardize your php.ini file between your LAMP servers. The default location in Debian/Ubuntu is /etc/php5/apache2/php.ini, while in RedHat/Fedora/CentOS you have /etc/php.ini. Assuming that you want Puppet to serve the same php.ini file to both types of servers, here’s a sample recipe:

file {
    name => $operatingsystem ? {
        debian => "/etc/php5/apache2/php.ini",
        default => "/etc/php.ini",
    }
    owner => root,
    group => root,
    mode => 644,
    source => "puppet://php/php.ini"
}

I hope you have noticed the use of the $operatingsystem variable, which will determine where the file will be placed at. That variable is obtained by facter and made available to you on your recipes. Nice! But Facter also retrieves many other facts that you can use in your recipes, just run facter from the command line and you’ll see them all.

Custom Facts

But what if those facts aren’t enough? Who should you complain to? Well, you can just STFU and write your own facts! In my case, I have to manage several Plesk servers, and it would be great to (automatically) know which version is running on each server.

The first thing I had to know is how can I get the version number in a Plesk server. Fortunately, it’s quite easy, it is stored in Plesk’s database:

mysql -u admin -p`cat /etc/psa/.psa.shadow` psa -N -e \
"SELECT val FROM misc WHERE param = 'version'"

As described in the documentation, the best way to learn how to write your own facts is by looking at the facts that already ship with Facter. They are located in RUBYLIBDIR/facter (in my case it’s /usr/lib/ruby/site_ruby/1.8/facter).

After reading some of them, I came up with the following code (plesk.rb file):

# mysql command line to execute queries
mysqlcmd = 'mysql -u admin -p`cat /etc/psa/.psa.shadow` psa -N -e '
 
Facter.add(:pleskversion) do
  setcode do
    is_installed = false
    os = Facter.value('operatingsystem')
    case os
      when "RedHat", "CentOS", "SuSE", "Fedora"
        is_installed = system 'rpm -q psa > /dev/null 2>&1'
      when "Debian", "Ubuntu"
        is_installed = system 'dpkg -l psa > /dev/null 2>&1'
      else
    end
    if is_installed then
      %x[#{mysqlcmd} "SELECT val FROM misc WHERE param = 'version'"].strip
    end
  end
end

It’s very simple to create your own fact. Let’s take a look at it in small steps.

This is the method you call to add your custom fact:

Facter.add(:pleskversion) do

It requires the fact name and the block of code that will be executed to generate the output

Here’s how you specify your block of Ruby code (setcode is just a method from a Facter class):

setcode do

Inside the block is the real thing. First I’d like to check if Plesk is really installed. Notice the use of another fact (operatingsystem) inside the code:

    is_installed = false
    os = Facter.value('operatingsystem')
    case os
      when "RedHat", "CentOS", "SuSE", "Fedora"
        is_installed = system 'rpm -q psa > /dev/null 2>&1'
      when "Debian", "Ubuntu"
        is_installed = system 'dpkg -l psa > /dev/null 2>&1'
      else
    end

Ok, now that I now if Plesk is installed, what should be returned to Facter?

    if is_installed then
      %x[#{mysqlcmd} "SELECT val FROM misc WHERE param = 'version'"].strip
    end

That’s simple. If Plesk is installed, Facter will retrieve the output of the command above (everything inside the %x[]) and use it as the value for pleskversion fact and therefore the $pleskversion variable in your Puppet recipes. If Plesk is not installed, this fact simply won’t be available at all.

Testing the fact from the command line:

$ facter pleskversion
0901

Now I know that it’s a server with Plesk 9.0.1 installed!

More Plesk facts!

Based on my first fact, I have created a few helpful others:

pleskmajorversion
retrieves only the major version
pleskdomains
how many domains
pleskclients
how many clients
pleskresellers
how many resellers
pleskkeyexpirationdate
license key expiration date

And here’s the complete code:

This code is hosted @ github.com.

# plesk.rb
# Facts related to Parallels Plesk Control Panel
#
# Pedro Padron
<ppadron@w3p.com.br>
# http://ppadron.w3p.com.br/
 
# mysql command line to execute queries
mysqlcmd = 'mysql -u admin -p`cat /etc/psa/.psa.shadow` psa -N -e '
 
Facter.add(:pleskversion) do
  setcode do
    isinstalled = false
    os = Facter.value('operatingsystem')
    case os
      when "RedHat", "CentOS", "SuSE", "Fedora"
        isinstalled = system 'rpm -q psa &gt; /dev/null 2&gt;&amp;1'
      when "Debian", "Ubuntu"
        isinstalled = system 'dpkg -l psa 2&gt;&amp;1 | egrep '(^ii|^hi)' &gt; /dev/null'
      else
    end
    if isinstalled then
      %x[#{mysqlcmd} "SELECT val FROM misc WHERE param = 'version'"].to_s.strip
    end
  end
end
 
pleskversion = Facter.value('pleskversion')
 
if pleskversion then
  # retrieves only the major version number
  Facter.add(:pleskmajorversion) do
    setcode do
      pleskversion.slice(1..1)
    end
  end
 
  # number of domains
  Facter.add(:pleskdomains) do
    setcode "#{mysqlcmd} 'SELECT COUNT(*) FROM domains'"
  end
 
  # number of clients in the machine (&gt;=PLESK9)
  Facter.add(:pleskclients) do
    confine :pleskmajorversion =&gt; %w{9}
    setcode do
      %x[#{mysqlcmd} "SELECT COUNT(*) FROM clients WHERE type = 'client'"]
    end
  end
 
  # number of clients in the server (<PLESK9)
  Facter.add(:pleskclients) do
    confine :pleskmajorversion => %w{7 8}
    setcode do
      %x[#{mysqlcmd} "SELECT COUNT(*) FROM clients"].strip
    end
  end
 
  # number of resellers in the server (>=PLESK9)
  Facter.add(:pleskclients) do
    confine :pleskmajorversion =>  %w{9}
    setcode do
      %x[#{mysqlcmd} "SELECT COUNT(*) FROM clients WHERE type = 'reseller'"].strip
    end
  end
 
  # license key expiration date
  Facter.add(:pleskkeyexpirationdate) do
    setcode do
      lim_date = %x[/usr/local/psa/bin/keyinfo --list | grep lim_date]
      lim_date.sub('lim_date: ', '').strip
    end
  end
end

Making your facts available to Puppet

The standard way to use your custom facts with Puppet is to put them inside your Puppet modules. If you don’t know how to create a Puppet module, I suggest you read this wiki page.

Similar to the php.ini example, suppose you want to provide different psa.conf according to the Plesk version. In your puppetmaster, let’s create a module called plesk-config with the following directory structure:

(where MODULEPATH represents the modulepath variable in /etc/puppet/puppet.conf – defaults to /var/lib/puppet/modules)

MODULEPATH/plesk-config
MODULEPATH/plesk-config/plugins
MODULEPATH/plesk-config/plugins/facter
MODULEPATH/plesk-config/plugins/facter/plesk.rb
MODULEPATH/plesk-config/files/8/conf/psa.conf
MODULEPATH/plesk-config/files/9/conf/psa.conf
MODULEPATH/plesk-config/manifests
MODULEPATH/plesk-config/manifests/init.pp

Here’s the init.pp file:

class PleskConfig {
    file {
        name => "/etc/psa/psa.conf",
        owner => root,
        group => root,
        mode => 644,
        source => "puppet://plesk-module/conf/$pleskmajorversion/psa.conf"
    }
}

Just make sure you have in your puppet clients the pluginsync variable set to true (/etc/puppet/puppet.conf), this way when they check for updates in your puppetmaster, they will download your custom facts.

That’s it! The next time you run your puppet client the correct file will be provided to the correct server. You can check for the custom facts in the report file /var/lib/puppet/state/state.yaml.

Update:Thanks to Andre Timmermann for pointing out my mistake with dpkg.

Leave a Reply