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 > /dev/null 2>&1' when "Debian", "Ubuntu" isinstalled = system 'dpkg -l psa 2>&1 | egrep '(^ii|^hi)' > /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 (>=PLESK9) Facter.add(:pleskclients) do confine :pleskmajorversion => %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.