agile sysadmin

by Ferenc Erki

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:

  1. Tasks without an explicit target
  2. Tasks targeting <local> explicitly
  3. Override the task’s target to <local>
  4. Disable SSH per task with no_ssh task
  5. Disable SSH globally with no_ssh
  6. Running parts of a task within LOCAL{} blocks
  7. Connect to local host over SSH

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.