(R)?ex the friendly automation framework

News

2020-03-05
Rex-1.8.2

The Rex-1.8.2 release is now available on CPAN, fixing a bug related to task hooks.

2020-02-05
Rex-1.8.1

The Rex-1.8.1 release is now available on CPAN, improving release automation, packaging, and documentation.

2020-01-05
Rex-1.8.0

The Rex-1.8.0 release is now available on CPAN, adding initial support to manage Void Linux systems.

2019-12-05
Rex-1.7.1

The Rex-1.7.1 release is now available on CPAN, fixing a Windows testing issue, and updating some docs.

2019-11-05
Rex-1.7.0

The Rex-1.7.0 release is now available on CPAN.

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 » Rex book » Writing modules » Writing custom resources

Writing custom resources

Resources are the units that are responsible to manage your configurations. Resources have a state. Compared to remote execution functions, that means, that a resource is only applied if the remote system is not in the specific state.

Rex has simple remote execution functions (like mkdir, is_dir, is_file and more). But it has also resources (like file, pkg, service and more).

Currently the resources and remote execution functions are mostly all in the Rex::Commands namespace but in the next major release (2.0) resources will have their own namespace (Rex::Resource).

Every command that has the option ensure is a resource.

Another difference between resources and remote execution functions is that resources will be reported (if you are using the reporting feature) and have an on_change attribute .

Writing Resources for Rex 1.x (1.3+)

Architecture of a Resource

A resource always exists of at least 2 files. The module which creates the resource definition and exports the function (if wanted). And a so called provider which provides the functionality for a specific implementation. For example the firewall resource has a provider for ufw and for iptables.

The resource definition defines the parameters which are valid for the resource, load the wanted or auto-detected provider class and execute the requested state.

After the execution of the requested state it will also emit the change, so the reporting module get notified.

Hello World Resource

To build a new resource you first have to create a new Rex module. To do this, just create a new folder.

$ mkdir -p lib/HelloWorld

Now we need to create 2 files. One __module__.pm for the resource definition and one meta.yml where we can define the dependencies our resource will have. This file is needed if you want to upload your module to a git server and share it between projects.

In the meta.yml file you can just put the following lines.

Name: HelloWorld
Description: A hello world resource
License: YourLicense
# we don't require anything
# Requires:

The __module__.pm file which creates the resource.

package HelloWorld;
⁠
⁠use strict;
⁠use warnings;
⁠
⁠use Rex -minimal;          # for Rex < 1.4 use Rex -base;use Rex::Resource::Common; # load resource functions# load the Gather functions, so we have the `operating_system`# function.use Rex::Commands::Gather;
⁠
⁠use Carp;
⁠
⁠# list the available providersmy $__provider = {
⁠    default => "HelloWorld::greet::Provider::default",
⁠    CentOS  => "HelloWorld::greet::Provider::centos",
⁠};
⁠
⁠# create a resource HelloWorld::greetresource "greet", sub {
⁠    my $rule_name = resource_name;
⁠
⁠    my $rule_config = {
⁠        ensure  => param_lookup( "ensure",  "present" ),
⁠        message => param_lookup( "message", "<default value>" ),
⁠    };
⁠
⁠    # get the right provider if it is not defined via the operating system    # and the list from above.    my $provider =
⁠      param_lookup( "provider", case ( lc(operating_system), $__provider ) );
⁠
⁠    # load the provider class    $provider->require;
⁠
⁠    # create a new instance of the provider class    my $provider_o = $provider->new();
⁠
⁠    # and execute the requested state.    if ( $rule_config->{ensure} eq "present" ) {
⁠        if ( $provider_o->present($rule_config) ) {
⁠            emit created, "HelloWorld::greet resource created.";
⁠        }
⁠    }
⁠    elsif ( $rule_config->{ensure} eq "absent" ) {
⁠        if ( $provider_o->absent($rule_config) ) {
⁠            emit removed, "HelloWorld::greet resource removed.";
⁠        }
⁠    }
⁠    else {
⁠        die "Error: $rule_config->{ensure} not a valid option for 'ensure'.";
⁠    }
⁠
⁠};
⁠
⁠1; # this need to be the last line in the file

Now, after creating the resource definition we need to create our providers. To do so, you need to create the following directory.

$ mkdir -p lib/HelloWorld/greet/Provider

And place the two providers inside this directory.

$ touch lib/HelloWorld/greet/Provider/default.pm
$ touch lib/HelloWorld/greet/Provider/centos.pm

In this example we will create the default.pm provider and the centos.pm provider which will just inherit every method from the default.pm provider.

In Rex < 2.0 we don't use any object system for perl so we need to create the bare class by our self.

# File: lib/HelloWorld/greet/Provider/default.pmpackage HelloWorld::greet::Provider::default;
⁠
⁠use strict;
⁠use warnings;
⁠
⁠use Rex::Commands::File;
⁠
⁠# the constructorsub new {
⁠    my $that  = shift;
⁠    my $proto = ref($that) || $that;
⁠    my $self  = {@_};
⁠
⁠    bless( $self, $proto );
⁠
⁠    return $self;
⁠}
⁠
⁠# the ensure methodssub present {
⁠    my ( $self, $rule_config ) = @_;
⁠
⁠    my $changed = 0;
⁠
⁠    file "/etc/motd",
⁠      content   => $rule_config->{message},
⁠      owner     => "root",
⁠      group     => "root",
⁠      mode      => '0644',
⁠      on_change => sub { $changed = 1; };
⁠
⁠    return $changed;
⁠}
⁠
⁠sub absent {
⁠    my ( $self, $rule_config ) = @_;
⁠
⁠    my $changed = 0;
⁠
⁠    file "/etc/motd",
⁠      ensure    => "absent",
⁠      on_change => sub { $changed = 1; };
⁠
⁠    return $changed;
⁠}
⁠
⁠1; # this need to be the last line in the file

The next file is the centos provider. We will just inherit from the default provider without overriding any functions. This is just for demonstration purpose how to do inheritance.

# File: lib/HelloWorld/greet/Provider/centos.pmpackage HelloWorld::greet::Provider::centos;
⁠
⁠use strict;
⁠use warnings;
⁠
⁠use Rex::Commands::File;
⁠use parent qw(HelloWorld::greet::Provider::default);
⁠
⁠# the constructorsub new {
⁠    my $that  = shift;
⁠    my $proto = ref($that) || $that;
⁠
⁠    # here we call the constructor of the parent class    my $self = $proto->SUPER::new(@_);
⁠
⁠    bless( $self, $proto );
⁠
⁠    return $self;
⁠}
⁠
⁠1; # this need to be the last line in the file

Using your resource

After creating your module with the greet resource you can use this resource in your Rexfile.

# Rexfileuse Rex -feature => ['1.4'];
⁠use HelloWorld;
⁠
⁠group myservers => "srv[01..10]";
⁠
⁠task "prepare",
⁠  group => "myservers",
⁠  sub {
⁠    HelloWorld::greet "mygreeting",
⁠      message   => "Welcome to my server",
⁠      ensure    => "present",
⁠      on_change => sub {
⁠        say "server greeting has changed.";
⁠      };
⁠  };

Proudly powered by Statocles

Google Group / Twitter / GitHub / Mailinglist / irc.freenode.net #rex   -.ô.-   Disclaimer