Cubicle

Commands and operations

How exec runs a command in a Box (argv without a shell, no stdin or TTY, your own guest account, sudo and API keys), its results and limits, the grant and running Box it needs, generations, operation states, who can read an operation, idempotency keys and running several commands at once.

Exec runs one command in a running Box and returns its exit code and output once the command ends. Each exec, snapshot and restore request creates an operation: a durable record of the request that you read until it finishes. For step-by-step instructions, see Run commands. For an interactive shell or a long session, use SSH.

A command is an argv, not a shell line

  • argv is an array. Its first item is the program and the rest are its arguments. Cubicle runs it directly, without a shell.
  • Pipes, redirects, &&, globs and $VARIABLES need a shell: ["sh", "-c", "ls /var/log | wc -l"].
  • The command's standard input is /dev/null. It has no TTY and runs in a new session, so programs that prompt for input read end-of-file.
  • The exec ends when the command has exited and its standard output and error are both closed. A background process that keeps either of them open holds the exec until timeoutMs, when the command is killed and the result has timedOut: true and exit code 124.
  • Exec is for commands that finish. Do not rely on a process you start in the background to keep running after the exec returns.
  • Output arrives only when the command ends. Streaming output and a file upload or download API are not available yet; see Move files into and out of a Box.

The command runs as your guest account

  • An exec runs as the guest account of whoever sent it, person or service account. SSH logins use the same account.

  • The working directory is cwd when you set it, or the account's home directory.

  • Cubicle starts the command with only these environment variables; your local environment is not passed on. Profile files such as ~/.profile are not read; use ["bash", "-lc", "…"] for a login shell's environment.

    VariableValue
    HOMEThe account's home directory
    USER, LOGNAMEThe account name
    SHELL/bin/bash
    PATH/usr/local/bin:/usr/bin:/bin
    LANGC.UTF-8
  • Exec has no option to set other environment variables. Run commands shows how to pass values to a command.

Root and sudo

Once you have added an SSH key to your account, your guest account has passwordless sudo, so a command can run as root: ["sudo", "id", "-u"] prints 0.

Commands sent with an API key cannot rely on sudo

  • A guest account is locked and expired in the Box unless its owner holds a grant on the Box and has added an SSH key. Service accounts cannot add SSH keys, so every service account's guest account is locked and expired.
  • Exec still runs commands as a locked account. sudo checks whether the account has expired, though, so do not rely on sudo from it. A program that uses an API key may not be able to install system packages, for example with apt-get.

Instead, install software that does not need root into the home directory and run it by its full path, because exec's PATH does not include directories such as ~/.local/bin. For system packages, have a person install them over SSH, take a snapshot of that Box and create the program's Boxes from the snapshot; see Build a template Box.

Limits

Each exec has a time limit, timeoutMs, and an output limit, maxOutputBytes, which stdout and stderr share. Set a timeout and an output limit shows how to set them, and Limits and defaults lists their values.

Results

A finished exec has a result with exitCode, stdout, stderr, timedOut and truncated. The SDK's ExecResult adds the operationId. Read the result explains each field and the exit codes that come from Cubicle rather than from your program.

  • A non-zero exit code is a result, not an error: the operation is still done.
  • Check timedOut, not exit code 124, to detect a timeout: the command itself can exit with 124.
const slow = await box.exec(["sleep", "5"], { timeoutMs: 1000 })
// => { exitCode: 124, stdout: "", stderr: "", timedOut: true, truncated: false, operationId: "…" }

Exec needs a grant and a running Box

  • Exec needs an explicit grant on the Box, which its creator gets. The organization's owner or admin role is not enough: without a grant the request returns 403 forbidden, an explicit box grant is required.
  • The Box's desired state must be running, or the request returns 409 conflict, box is not running. The Box must also actually be running when the command reaches its Node, or the exec fails, so wait for ssh.ready, or use the SDK's wake: true, which starts the Box when it is not meant to be running and waits until it is ready.

An exec is bound to a generation

An exec records the Box's desired.generation when it is accepted (see generations).

  • Before the command starts, a start, stop or hibernation moves the Box to a new generation, and the exec fails with box is not running at the requested generation. After a deletion it fails with box deleted or box no longer exists. It fails with requester is not authorized on this box when its sender has lost the grant or membership in the meantime.
  • While the command runs, a stop, hibernation (including automatic hibernation), deletion or host restart interrupts it, and the exec ends unknown. A start on a Box that is already running does not interrupt it.

Operations record every request

Exec, snapshot and restore requests return 202 with an operation. Read it with GET /v1/operations/{operation} (reference), or let the SDK wait for it. Its key fields:

FieldMeaning
id, boxIdThe operation's ID and its Box's ID.
typeexec, snapshot or restore.
stateWhere the operation is; see the table below.
resultFor a done exec, its result. For snapshots and restores, reserved; do not read it.
errorWhy the operation failed or is unknown, otherwise null.
createdAt, startedAt, completedAtISO 8601 timestamps; the last two are null until they happen.

The SDK's Operation type lists every field.

StateMeaning
pendingAccepted, and waiting for the Box's Node to pick it up.
runningThe Node is carrying it out.
doneFinished. A done exec has its result, whatever its exit code.
failedEnded with the reason in error, for example box is not running at the requested generation. For an exec, this is about running the command, not its exit code.
unknownExec only. The Node lost track of the command: its service restarted while the command ran, or a lifecycle change interrupted it. The command may or may not have run, in whole or in part.
  • done, failed and unknown are final.
  • An unknown exec is never run again. Cubicle cannot tell whether the command ran, so it leaves the decision to you. Run it again only if repeating it is safe.
  • The SDK rejects with a CubicleOperationError when an operation ends failed (code operation_failed) or unknown (code operation_unknown). The CLI prints exec failed: or exec unknown: and exits with status 1.

Who can read an operation

  • Any active member of the Box's organization can read its operations, without a grant: GET /v1/operations/{operation} for one, or GET /v1/boxes/{box}/operations (reference) for the Box's 50 most recent, newest first. The Dashboard's Operations tab shows the same list.
  • An exec's result, with its output, is visible only to the account that sent it. Everyone else sees result: null.
  • An operation does not include the command's argv.
  • An API key sees only operations in its own organization. Others return 404.
  • You can still read an operation by ID after its Box is deleted.

Idempotency keys make retries safe

Send an Idempotency-Key header with an exec: when the response is lost and you retry with the same key and body, you get the original operation back and the command does not run twice. The SDK sends a new key with every exec call and reuses it when it retries that request itself; the CLI's exec sends none, so running it again runs the command again. Idempotency keys has the full rules, and Retry safely with idempotency keys shows each surface.

Several commands can run at once

  • Several execs on one Box can run at the same time.
  • Each Node carries out a limited number of operations at once. Others wait as pending.
  • An exec does not block start, stop, hibernate or delete. Stop, hibernate and delete interrupt it; a start on the running Box does not, and its Node applies that start once the exec has finished.
  • A snapshot or restore has the Box to itself: start, stop, hibernate and delete return 409 conflict while one is pending or running. See Snapshots.

On this page