(R)?ex Deployment & Configuration Management

News

2019-07-02
Deprecating official Rex packages

As part of our efforts to simplify maintenance around RexOps projects, we have decided to stop building Rex packages on our side. We recommend relying on OS packages maintained by the various distributions in their respective upstream repositories, or to install Rex from CPAN.

2019-06-09
Post-migration updates & clean-up

Over the course of the past weeks many clean-ups have been done, reaping the benefits provided by our new Statocles-based site:

2019-05-19
New site engine for rexify.org

After months of work on a new site backend, we reached the MVP today for our site to be switched over to a new engine: from now on, our site will be maintained using the static site generator Statocles.

Conferences

2016-06-21

Need Help?

Rex is a pure open source project, you can find community support in the following places:

Professional support is also available.

» Home » Docs » Guides » Using Modules and Templates

Using Modules and Templates

Uploading hardcoded configuration files to your servers often isn't enough. Sometimes you must add values dynamically inside a file. For example if you want to bind a service to a special network interface or to configure different database servers for your application for your different environments. This can be easily achieved with templates.

In this example you will learn how to build a ntp module that uploads custom ntp.conf files for your test-, pre-prod-, and production environment.

First, we create a new module. For this guide you need at least Rex 0.41 (because of the --create-module command and the case keyword.) Execute the following command in the same directory where your Rexfile lives.

rexify Service::NTP --create-module

This will create some new directories and files in your lib directory.

.
├── Rexfile
└── lib
    └── Service
        └── NTP
            ├── __module__.pm
            └── meta.yml

The meta.yml file can be ignored. This file is only important if you want to share your module on the Rex modules directory.

The important file is __module__.pm. Open this file in an editor. This file is a normal Perl module. The only special thing is the filename, but don't think too much for it at first.

package Service::NTP;
⁠use Rex -feature => ['1.3'];
⁠
⁠task prepare => sub {
⁠  my $service_name = case operating_system, {
⁠    Debian    => "ntp",
⁠      default => "ntpd",
⁠  };
⁠  pkg "ntp", ensure => "present";
⁠
⁠  file "/etc/ntp.conf",
⁠    source    => "files/etc/ntp.conf",
⁠    on_change => sub {
⁠    service $service_name => "restart";
⁠    };
⁠
⁠  service $service_name, ensure => "started";
⁠};
⁠
⁠1;

First the module checks if the OS is a debian (or ubuntu) and set the service name to "ntp" otherwise to "ntpd". After that it installs the "ntp" package and uploads the configuration file. Notice the on_change hook here. This will restart the ntp service if the file changes. At last Rex verifies that the service will start on system boot. Now it is time to create a basic ntp.conf file. Create the directory lib/Service/NTP/files/etc and place the ntp.conf file in there.

# /etc/ntp.conf, managed by Rex
⁠driftfile /var/run/ntp.drift
⁠
⁠statistics loopstats peerstats clockstats
⁠filegen loopstats file loopstats type day enable
⁠filegen peerstats file peerstats type day enable
⁠filegen clockstats file clockstats type day enable
⁠
⁠server ntp01.company.tld
⁠server ntp02.company.tld

If you now want to distribute different ntp.conf files per environment you can add multiple ntp.conf files to that directory. Rex will than decide with the help of the -E $env cli parameter which file to use. Rex first try to find a file named ntp.conf.$environment and if that file does not exist it fallback to ntp.conf.

⁠.
⁠├── Rexfile
⁠└── lib
⁠    └── Service
⁠        └── NTP
⁠            ├── __module__.pm
⁠            ├── files
⁠            │   └── etc
⁠            │       ├── ntp.conf
⁠            │       ├── ntp.conf.preprod
⁠            │       ├── ntp.conf.prod
⁠            │       └── ntp.conf.test
⁠            └── meta.yml

But if you want to change a parameter in your ntp.conf file you have to edit 4 files. This is not realy cool. To prevent that you can use templates.

⁠package Service::NTP;
⁠use Rex -feature => ['1.0'];
⁠
⁠task prepare => sub {
⁠  my $service_name = case operating_system, {
⁠    Debian    => "ntp",
⁠      default => "ntpd",
⁠  };
⁠
⁠  my $ntp_server = case environment, {
⁠    prod      => [ "ntp01.company.tld", "ntp02.company.tld" ],
⁠      preprod => ["ntp01.preprod.company.tld"],
⁠      test    => ["ntp01.test.company.tld"],
⁠      default => ["ntp01.company.tld"],
⁠  };
⁠
⁠  pkg "ntp", ensure => "present";
⁠
⁠  file "/etc/ntp.conf",
⁠    content   => template( "files/etc/ntp.conf", ntp_servers => $ntp_server ),
⁠    on_change => sub {
⁠    service $service_name => "restart";
⁠    };
⁠
⁠  service $service_name, ensure => "started";
⁠};
⁠
⁠1;

Now we can create our template. The default template of Rex looks similar to other templating engines. But you can also use other template engines like Template::Toolkit.

# /etc/ntp.conf, managed by Rex

driftfile /var/run/ntp.drift

statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable

<% for my $server (@{ $ntp_servers }) { %>
server <%= $server %>
<% } %>

Predefined Variables

There are also some predefined variables you can use in your templates. For example the ip address of a network device if you want to bind a service on a specific ip. You can dump all predefined variable of a system with the following code.

⁠task "get_infos", "server01", sub {
⁠  dump_system_information;
⁠};

For example this is the output of a CentOS VM

$kernelversion = '#1 SMP Tue May 15 22:09:39 BST 2012'
$memory_cached = '357'
$memory_total = '498'
$kernelrelease = '2.6.32-220.17.1.el6.i686'
$Kernel = {
      kernelversion => '#1 SMP Tue May 15 22:09:39 BST 2012'
      architecture => 'i686'
      kernel => 'Linux'
      kernelrelease => '2.6.32-220.17.1.el6.i686'
   }
$hostname = 'c6test0232'
$operatingsystem = 'CentOS'
$operatingsystemrelease = '6.2'
$architecture = 'i686'
$domain = 'test.rexify.org'
$eth0_mac = '00:1C:42:9E:0E:28'
$kernel = 'Linux'
$swap_free = '2005'
$VirtInfo = {
      virtualization_role => 'guest'
      virtualization_type => 'parallels'
   }
$memory_shared = '0'
$Network = {
      networkdevices => [
         'eth0'
      ]
      networkconfiguration => {
         eth0 => {
            broadcast => '10.211.55.255'
            ip => '10.211.55.60'
            netmask => '255.255.255.0'
            mac => '00:1C:42:9E:0E:28'
         }
      }
   }
$memory_used = '440'
$kernelname = 'Linux'
$Swap = {
      free => '2005'
      used => '10'
      total => '2015'
   }
$swap_total = '2015'
$memory_buffers = '47'
$eth0_ip = '10.211.55.60'
$swap_used = '10'
$memory_free = '57'
$manufacturer = 'Parallels Software International Inc.'
$Memory = {
      shared => '0'
      buffers => '47'
      free => '57'
      used => '440'
      total => '498'
      cached => '357'
   }
$eth0_broadcast = '10.211.55.255'
$eth0_netmask = '255.255.255.0'
$Host = {
      domain => 'test.rexify.org'
      manufacturer => 'Parallels Software International Inc.'
      kernelname => 'Linux'
      hostname => 'c6test0232'
      operatingsystemrelease => '6.2'
      operatingsystem => 'CentOS'
   }

You can use such predefined variable right in your template. Lets assume you want to bind apache only on your eth1 ip.

Listen <%= $eth1_ip %>:80

Using the Module

To use your module you have to add it to your Rexfile. This can be simply achieved with one line.

use Rex -feature => ['1.3'];
⁠user "root";
⁠password "foo";
⁠
⁠require Service::NTP;
⁠
⁠1;

Than you can list your Tasks and execute them.

$ rex -T
[2013-03-30 02:35:32] INFO - eval your Rexfile.
Tasks
  Service:NTP:prepare

$ rex -H yourserver01 Service:NTP:prepare
Google Group / Twitter / GitHub / Mailinglist / irc.freenode.net #rex   -.ô.-   Disclaimer