Local management with Rex
Automation challenges often focus on remote endpoints, while several use cases require automating locally as well. Typical situations range from managing a personal machine, through setups where the managed fleet includes the host managing the whole, to pull-style models where each node manages itself.
Rex, the friendly automation framework supports any combination of local and remote execution. This post summarizes the main orientation points about local management.
TL;DR
Rex provides several options for local management to choose from, allowing authors to express their intent explicitly according to their use case at hand:
Basic anatomy of Rex task definitions
For the majority of use cases, I find it best to think about the most important parts of task definitions like this:
task $task_name, @targets, sub {
... # task steps
};
The above uses the task
command to create a task named $task_name
, intended
to manage a list of @targets
, and the anonymous subroutine attached at the
end contains the steps to execute.
For the sake of completeness, the full situation looks more like this, though:
optional_modifier task $task_name, @target_hosts_or_host_groups, sub {
... # task steps
}, { task_option => $value };
I’ll not go into all details here to allow us staying aware of further internals while focusing on the general approach.
Tasks without an explicit target
Tasks without any explicit target get executed on the local machine directly:
task $task_name, sub {
... # task steps
};
In my experience this pattern works great in situations with unknown targets at the time of writing. For example when publishing automation solutions for consumption by others in- or outside of your organization.
It allows users to choose their intended target according to their situation by
passing their targets via the -H
or -G
options on the command line, or the
on
option of run_task
from code.
Users may need protection against accidental local usage, for example when the
task manages disk partitions. In this case the task can check for that
situation and abort the operation either at the beginning of the steps, or as
a before_task_start
hook.
See also the Create a local task (a server independent task) section in the documentation.
Tasks targeting <local>
explicitly
One may intentionally design a task to manage the local machine by using the
special <local>
hostname recognized by Rex:
task $task_name, '<local>', sub {
... # task steps
};
This works because Rex internally represents local targets as the otherwise
invalid hostname of <local>
.
Users may still override their intended target on the command line with the
-H
or -G
options, or with the on
option of
run_task
from code.
Override the task’s target to <local>
The -H
command line option supports the same special <local>
hostname:
$ rex -H '<local>' task_name
and it’s also supported by the on
option of
run_task
from code:
task $task_name, sub {
... # task steps
};
task $other_task, sub {
... # task steps
run_task $task_name, on => '<local>';
... # task steps
};
This approach allows users to intentionally choose local execution, regardless of a task having an explicit target or not.
Disable SSH per task with no_ssh task
The no_ssh
command hints individual tasks to not use SSH:
no_ssh task $task_name, sub {
... # task steps
};
This command serves as a convenience wrapper to append { no_ssh => 1 }
as
a task option, making the above the same as:
task $task_name, sub {
... # task steps
}, { no_ssh => 1 };
This approach works great when some tasks apply solely to the local machine, or when the task interacts solely with external APIs. For example with systems such as monitoring, inventory, libvirt, or cloud providers.
See also the no_ssh documentation.
Disable SSH globally with no_ssh
In case none of the tasks need SSH to connect to their targets, the no_ssh
command can disable it globally:
no_ssh;
task $task_name, sub {
... # task steps
};
Again, it serves as a convenience wrapper to append { no_ssh => 1 }
to all
tasks.
This approach works great when all tasks interact solely with external APIs as above, or when all steps should strictly apply to the node executing the code alone.
See also the no_ssh documentation.
Running parts of a task within LOCAL{}
blocks
Wrapping parts of the task steps in a LOCAL{}
block forces these to run on
the local machine instead of on the target in effect otherwise:
task $task_name, $target, sub {
say run 'hostname'; # say output of `hostname` from $target
LOCAL {
say run 'hostname'; # say output of `hostname` from local
}
};
This works great when a task needs to check something locally to make decisions about the remote management. For example retrieving some local state or value to use in the rest of the steps.
See also the LOCAL documentation.
Connect to local host over SSH
Using the identity of the local machine as target makes it possible to treat it the same way as remotes, including connecting to it using SSH like to any remote.
This works great to ensure taking the same code path during execution for all hosts, or when all hosts in the fleet requires the capability to manage the whole.