Working with hosts

otto host provides direct access to host operations from the command line – running commands, transferring files, opening an interactive shell, and invoking host capabilities – without writing a test suite or instruction.

Hosts are defined in lab.json — see Lab Configuration. This section is about using them.

otto host --help

otto host --help __ __ ____ / /_/ /_____ / __ \/ __/ __/ __ \ / /_/ / /_/ /_/ /_/ / \____/\__/\__/\____/ Usage: otto host [OPTIONS] [HOST_ID] COMMAND [ARGS]... Run commands and transfer files on lab hosts. ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ │ [host_id] TEXT Host ID to operate on. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Options ────────────────────────────────────────────────────────────────────╮ │ --hop TEXT Host ID to use as an SSH hop to reach the │ │ target. │ │ --term TEXT Override the terminal protocol for this session. │ │ --transfer TEXT Override the file transfer protocol for this │ │ session. │ │ --list-hosts Show all valid host IDs. │ │ --help -h Show this message and exit. │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ───────────────────────────────────────────────────────────────────╮ │ cp Copy *src* to *dst* on the host (``cp``; *recursive* → │ │ ``-r``). │ │ exists Return True when *path* exists on the host (``test -e``). │ │ get Transfer files from remote host to the local machine. │ │ install Stage, then install every product. │ │ is-installed Return True iff there is at least one product and all are │ │ installed. │ │ is-uninstalled Inverse of :meth:`is_installed`. │ │ load Insert a kernel module: stage the .ko to the host, then │ │ ``insmod`` it. │ │ login Open an interactive shell bridged to the local terminal. │ │ ls List entry names in *path* (``ls -1``; *all* adds ``-A`` for │ │ dotfiles). │ │ lsmod List the kernel modules currently loaded on the host. │ │ mkdir Create directory *path* (``mkdir``; *parents* adds ``-p``). │ │ mv Move/rename *src* to *dst* on the host (``mv``). │ │ power Power this host ``'on'``/``'off'``, or toggle when *state* │ │ is None. │ │ put Transfer files from local machine to remote host. │ │ read-file Return the text contents of *path*. │ │ reboot Reboot this host. │ │ rm Remove *path* (``rm``; *recursive* → ``-r``, *force* → │ │ ``-f``). │ │ run Execute one or more commands on the host via the persistent │ │ shell session. │ │ shutdown │ │ stage Stage every product onto this host (transfer/place, no │ │ install). │ │ uninstall Uninstall every product (best-effort). │ │ unload Remove a kernel module (``rmmod``). │ │ write-file Write *data* to *path* (overwrite, or append). │ ╰──────────────────────────────────────────────────────────────────────────────╯

Syntax

The host ID comes before the subcommand, so all host-level options apply to every action:

otto host <host_id> <command> [ARGS...] [OPTIONS]

The host verb model

Every otto host action is a verb on the host. The four core verbs – run, put, get, and login – are built in (see Core commands). Every other verb is a capability verb: a host method marked @cli_exposed that otto turns into a subcommand automatically, scoped to that host’s class. otto host <host_id> --help lists exactly the verbs the chosen host supports. See Capability verbs for the capability verbs and Netcat transfers, Connections, and Configuration for transport and tuning.

Listing hosts

Use --list-hosts to see which host IDs are available in the loaded lab:

otto --lab my_lab host --list-hosts

This is the same --list-hosts option available on the top-level otto command.

Tab completion

Host ids tab-complete from the loaded lab, including the built-in local host:

otto host <TAB><TAB> example-device local $ otto host

Once a host id is typed, the verb candidates narrow to that host’s class — only the verbs the chosen host actually supports:

otto host example-device <TAB><TAB> cp exists get install is-installed is-uninstalled load login ls lsmod mkdir mv power put read-file reboot rm run shutdown stage uninstall unload write-file $ otto host example-device

See The host subsystem for how completion is synthesized from the same class-scoped mechanism as the verbs themselves.

Dry run

Like all otto commands, --dry-run (or -n) previews what would happen without executing commands or transferring files:

otto --lab my_lab --dry-run host router1 run "make install"

From Python

The otto host subcommands map directly to methods on the BaseHost class. Everything otto host does from the CLI can also be done inside instructions and test suites:

>>> host = LocalHost()
>>> result = run(host.run(["echo hello", "echo world"]))
>>> result.status
<Status.Success: 0>
>>> [cr.value.strip() for cr in result]
['hello', 'world']

File transfers work the same way – put and get map to put() and get():

from pathlib import Path

# Upload
res = await host.put(
    src_files=[Path("firmware.bin")],
    dest_dir=Path("/tmp"),
)
if not res:
    logger.error(f"upload failed: {res.msg}")

# Download
res = await host.get(
    src_files=[Path("/var/log/syslog")],
    dest_dir=Path("./logs"),
)
if not res:
    logger.error(f"download failed: {res.msg}")

Note

put and get are available on all host types, with per-class semantics: LocalHost copies files within the local filesystem, UnixHost transfers between the local machine and the remote host, and EmbeddedHost provides its own console/tftp transfer path; see Embedded Hosts.

Exit codes

Every otto host <name> <verb> invocation derives its exit code from the verb’s returned Result family, via Result.exit_code. Command results are ssh-like: the shell’s retcode when the command ran, 255 when it never ran. (exec is Python-only — it is not a CLI verb, so these rows apply to run.)

Situation

Exit code

Verb succeeded (incl. Status.Skipped)

0

run: a command failed

that command’s shell retcode (ssh-like: run 'exit 42' exits 42)

run: the command never ran (connection failure)

255 (matches ssh’s convention)

Any other verb: Status.Failed

1

Any other verb: Status.Error

2 (note: Click also uses 2 for CLI usage errors)

Any other verb: Status.Unstable

3

Custom verbs on third-party host classes may return plain values instead of a Result; the CLI prints them as-is and exits 0.

Extending

Hosts are otto’s most extensible area: register new connection or transfer backends (Extending otto with custom connection and transfer backends) and bring up embedded targets otto doesn’t ship support for (Extending otto for new embedded targets). The registry machinery behind every seam is described in Extension points.