Unofficial Rex Tips, Tricks, and Advice
Updated:This post is about the Rex framework, a suite of modules written in Perl, for automating the remote control of one or more computers.
Rex is a great time saver. Unfortunately, its documentation is rather disjointed and a bit cryptic. This post tries to flatten the learning curve a bit by offering some big picture ideas and fine-grained recipes to get you productively using the Rex framework faster than you would otherwise.
Tip #1: Learn Rex’s typical use case
Rex is an open-ended framework and it’s documentation doesn’t spell things out, so it can be a little confusing to know what it’s typical use case is. The most typical, easiest use case is to have one Rexfile in a single directory and all tasks split out and grouped into modules to make them maintainable. So let’s spell out how to accomplish this:
- You should have one dedicated directory on your hard drive for your rex tasks. Example:
~/rex
- Inside this directory, you will have a file named
Rexfile
. - While it’s possible to throw all your tasks into this one Rexfile, don’t do it. It’ll probably be a huge maintenance headache as it grows in size.
- Instead, create modules in your rex directory for holding your tasks. Each
module should have a collection of related tasks. For example, you might have
a module in this directory called
Local::Network::Configuration
for containing tasks related to managing your local network. You might have another directory calledWeb::Servers::Mail
for administering mail servers, etc., etc., so on and so forth. - Create a module with the following command:
rexify Your::Module::Name --create-module
. If your Rexfile already exists, your modules will be placed inside alib
directory. If the Rexfile hasn’t been created yet, your modules will be placed directly into the current directory. - Repeat step 5 for each of your modules.
- Edit/create your Rexfile. For each module you added and want to access with
the rex command, place
require Your::Module::Name;
at the top. Also be sure you haveuse Rex
along with any-feature
import argument per the documenation. - Add your tasks to the
__module__.pm
automatically generated for you inside the last directory of each module. You can safely delete the meta.yml file. It’s not needed with newer versions of Rex. - Now,
rex <command>
can access any task from inside each of the module directories you just created. - By default, you must prefix all tasks with the module name like so:
rex Your:Module:name:task_name
Note the single colon, not a double colon, between each of the path segments. Note also that when tasks are called by another task, they must also used this notation to fully qualify the task name. - You don’t have to prefix task names if you are careful
about avoiding namespace clashes by ensuring your tasks have unique names
across all your different
__module__.pm
files containing your tasks. To pull this off, simply remove thepackage Your::Module::Name;
line form the__module__.pm
file from each of your modules you’d prefer to not prefix them with a module name. NOTE: This is an unsupported hack. Use it at your own peril.
Tip #2: Set up tab completions
Tab completion allows you to quickly type in task names, group names, file
names, host names, and environment names from the command line. You simply type
in the first few letters of the name, hit the
Tab completion for rex kicks in when you type in the rex
command at the
command line and hit the
Setting up tab completion
Follow these steps to get rex’s tab completion feature working on your command line:
- Download or copy and paste the appropriate Rex tab completion script. Place the script into your desired location, usually in your home directory.
- Source the script with by running
source /path/to/script/rex-tab-completion.bash
on the command line. - If desired, add the command in step 3 to your
.bashrc
file so it will be available every time you log into your shell. - If you add more commands and entities to your Rexfile, type
exec bash
on the command line to update your completions. Note that you must have completed step 3 for this to work.
Tip #3: Run rex from any directory on your local machine
Typically, you have to go to the ~/rex
directory (or to whatever directory
contains a Rexfile) to issue Rex commands. Alternatively, with a little bit of
bash magic, you can run your rex tasks from anywhere on the machine and even
keep your bash completion. This works best when you have all your rex modules
and Rexfile in a single directory as described in Tip #1.
Here’s how to set it up:
- First, add following function into the appropriate bash script for your system (usually .bashrc or .bash_profile) so that the function loads when you log into bash:
r () { builtin cd $HOME/rex # set this to the path where you store your Rexfile rex $@ builtin cd $OLDPWD }
This function temporarily switches you to where your Rexfile is located, runs the rex command, and then hops back to the directory where you started. That’s it! Now you can just type in
r <command>
from anywhere on your machine. Don’t forget to resource your bash configuration to get it working. - If you set up bash completion in Tip #2, you’ll need to modify the completion script added in Tip #2, step 1. Carefully modify the script according to the comments, denoted with
###
, below:_rex() { ... if [[ -z $_rex_yaml ]]; then ### on the following line, change 'Rexfile' to your full Rexfile path _rex_yaml=$(rex -f $HOME/rex/Rexfile -Ty 2>/dev/null) fi ... if [ -f $HOME/rex/Rexfile ]; then ### change 'Rexfile' to your full Rexfile path ... fi ;; -E) if [ -f $HOME/rex/Rexfile ]; then #### change 'Rexfile' to your full Rexfile path ... fi ;; -G) if [ -f $HOME/rex/Rexfile ]; then ### change 'Rexfile' to your full Rexfile path ... fi ;; *) if [ -f $HOME/rex/Rexfile ]; then ### change 'Rexfile' to your full Rexfile path ... complete -F _rex rex ### Add this line complete -F _rex r ...
- Now save your file and open up a new bash process and you should now be able to do
r <partial_command><tab>
and perform rex bash completions from anywhere on your machine.
Tip #4: Use Rex::Dondley::ProcessTaskArgs to process arguments passed to your tasks
The way arguments are passed into Rex tasks is a little…weird. Command line
argument with names, those preceded with a double dashed label (e.g.
--arg_name=value
), are passed to tasks with a hash reference. Unnamed
arguments get passed in via an array reference. So the special variable for the
argument array, @_
, will look something like this:
({ --arg1=value1 --arg2=value2 }, [ arg3, arg4 ])
.
The Rex::Dondley::ProcessTaskArgs was created by yours truly to make processing these arguments a little less painful. You can easily set which arguments are required and set defaults for any arguments that aren’t passed in. Appropriate errors are set if the passed arguments don’t meet your criteria. You can think of this module as a specialized version of the Params::Validate perl module.
See the documentation for Rex::Dondley::ProcessTaskArgs for more details.
Tip #5: Document arguments to your tasks using the desc
function
As most developers can bear witness, undocumented code is useless code even you wrote it yourself. Unless you are blessed with mental superpowers, there’s simply no way you are going to remember the commands you wrote years, months, days, or even hours ago (and speaking for myself, minutes). So here’s a quick hack to painlessly document your task arguments and make them easily accessible right from the command line.
- Before each task, write a simple
desc
of the task followed by the argument it takes:desc 'this task does X | --domain=some.domain.com [ --quiet ]';
- Place optional arguments in square brackets
- For arguments that take values, provide a sample argument
- Notice the “pipe” character to help provide visual separation from the description
Tip #6: Use the CMDB feature to load information about your machines
Rex already automatically probes a remote server to get details about its hardware for you which you can access using the Rex::Hardware module. But frequently, you would like to get other bits of information about both the server you are controlling as well as for your local machine. For example, you might wish to store the path to where backups are saved. This information can be stored in what’s called a configuration management database or CMDB. In the world of IT, a CMDB helps IT administrators track all the hardware and software in use across their entire infrastructure.
This tip will show you a recipe for using Rex’s built-in CMDB feature to access properties about your servers using the default YAML file format. Just follow these steps:
- Create a
cmdb
directory in the same directory as your Rexfile - In the
cmdb
directory create the following directories:default
- A directory for each of the different kinds of operating systems you have
(
Darwin
,Debian
, etc.) - A directory for each of the different environments you run (
testing,
production
, etc.)
- In the
default
directory, create a.yml
file for each of the different hosts you have. Some exmaple file names:192.1.59.3.yml
iMac.yml
charlie.example.com.yml
- Inside each of the operating system and environment directories, create a
symlink to each machine that is a member of that operating system or
environement. For example, if your iMac machine is part of your testing
environment, create a symlink in the
Darwin
folder to thedefault/iMac.yml
file.
When you are finished, you should have a directory structure that looks something like this:
Rexfile
+-- cmdb
| +-- default
| | +-- iMac.yml
| | +-- 192.1.1.3.yml
| | +-- charlie.example.com.yml
| +-- Darwin
| | +-- iMac.yml -> ../default/iMac.yml
| | +-- 192.1.1.3.yml -> ../default/192.1.1.3.yml
| +-- Debian
| | +-- charlie.example.com.yml -> ../default/charlie.example.com.yml
| +-- production
| | +-- charlie.example.com.yml -> ../default/charlie.example.com.yml
| +-- testing
| | +-- 192.1.1.3.yml -> ../default/192.1.1.3.yml
The next step is to populate each of the yaml files in the default
directory with
your key/value pairs using yaml syntax like in the following example:
archive_dir: /path/to/archive_dir
backup_frequency: daily
sftp_dir: /path/to/sftp
user: my_user
user_password: letmein
sudo_password: letmeinsudo
ssh_port: 22
Now that your text-based cmdb is set up, you can query it from within your Rexfile and tasks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
use Rex -feature => [ qw( 1.4 ) ];
use strict;
use warnings;
# outside of a task, get cmdb() retrieves all the key/value pairs for the local machine
my $cmdb_l = get cmdb();
task 'some_task' => sub {
say $cmdb_l->{sudo_password} # access cmdb values for local machine in the task
say get cmdb('archive_dir') # access cdmb values for the remote machine
};
before_task_start 'ALL' => sub {
# save current cmdb path so we can restore it later
my $current_cmdb_path = $_[0]->{path};
# We set a simpler path to pull cmcdb values from yml files in the cmdb/default directory
set ( cmdb => {
path => 'cmdb',
type => 'YAML',
}
);
my $server = $_[0]->server->[0]->name; # get the server name
my $cmdb_r = get cmdb('', $server); # load all the values from the yaml file
# pull values form the yml file
user $cmdb_r->{user};
user_password $cmdb_r->{password};
ssh_port $cmdb_r->{port};
# restore cmdb path
set ( cmdb => {
path => $current_cmdb_path,
}
);
};
For more details on using the cmdb module, including how you can set default yaml values for your machines, refer to the Rex::CMDB module documentation
« Back