Skip to main content

exec.run

The exec.run function runs an external program with an argument list, without a shell, and returns its standard output, standard error, and exit code. By default the output streams live and a nonzero exit code raises an error.

Usage​

exec.run(argv, working_directory = ..., env = ..., output = "stream", check = True, timeout = "", retry = None)

Arguments​

argv

Required. A non-empty list or tuple of strings. The first element is the program and the rest are its arguments, passed exactly as written. A single string such as "ls -l" is rejected: split it into a list.

working_directory

(Optional) The directory to run in. Defaults to the script's working directory: the step's working_directory, or the process directory when none is set. A relative path resolves against that directory.

env

(Optional) A dictionary of extra environment variables with string keys and string values. These layer over the inherited execution environment, and a value given here replaces an inherited variable of the same name.

output

(Optional) "stream" (the default) shows the program's output live and also captures it. The value "capture" captures stdout and stderr without showing them, which suits secret-bearing responses. Any other value is an error.

check

(Optional) Defaults to True: a nonzero exit code raises an error. Pass False to receive the result and inspect exit_code yourself.

The timeout and retry options apply to this individual process call:

timeout
A positive duration such as "30s" or "5m". It bounds the entire call, including all attempts and retry delays. Omit it for no additional deadline; an enclosing task or script can still cancel the call.
retry
A dictionary using the same policy fields as steps.task, including max_attempts, initial_delay, and backoff_strategy. Optional conditions are regular expressions matched against the failed attempt's stdout and stderr. At least one must match when supplied. Omit the policy to run once.

Only ordinary nonzero exits are retried. Cancellation, timeout, failure to start, signals, and I/O errors stop the call. With check=False, an ordinary nonzero exit is returned immediately and does not trigger retries. Captured output contains only the final attempt; streaming displays each attempt as it runs. Retrying repeats the command's effects, so use it only for operations safe to repeat. Set max_attempts or timeout to bound a retry policy.

Returns​

A result with captured output and a decoded JSON view:

data
The JSON value decoded from stdout on first access. Objects become dictionaries, arrays become lists, and JSON scalars retain their types. Decoded collections are read-only and safe to share with parallel tasks. Empty or invalid JSON raises an error only when you access this attribute.
stdout
Everything the program wrote to standard output, as a string.
stderr
Everything the program wrote to standard error, as a string.
exit_code
The exit code, as an integer.

With output="stream", the program's stdout also goes to the script's own stdout, so it becomes the step's value when the script does not assign output. Use output="capture" to keep it out of the step value. component.exec accepts the same options. The atmos.run function and other atmos.* wrappers share the result fields and capture/check options; use a steps.task to apply a timeout or retry policy to those wrappers.

Reading data does not change stdout. Use json.decode(result.stdout) when you need a mutable copy. Serializing the whole result with json.encode(result) preserves the raw fields and does not access data. With check=False, inspect exit_code before reading data if the command may return a non-JSON error.

Errors​

  • An empty list, a non-list argv, or a non-string element fails with an argument error.
  • With check=True, a nonzero exit code raises an error that names the command and the exit code, and shows the last lines of stderr.
  • A program that cannot start, such as a missing command or a nonexistent directory, always raises, whatever check says. A canceled run and a program stopped by a signal raise as well.
  • A handled nonzero exit does not trigger a steps.task retry. Call fail() when the result should fail the task.

Tools pinned with dependencies.tools are on the PATH of every call that follows.

Examples​

Bound a command and retry transient failures​

result = exec.run(
["curl", "--fail", "https://example.com/"],
output = "capture",
timeout = "30s",
retry = {"max_attempts": 3, "initial_delay": "1s"},
)
print(result.stdout)

Run a program and use its output​

result = exec.run(["git", "rev-parse", "--short", "HEAD"], output = "capture")
sha = result.stdout.strip()
output = {"sha": sha}

Set the directory and environment​

exec.run(
["./deploy.sh", "api"],
working_directory = "scripts",
env = {"AWS_REGION": "us-east-1", "DEPLOY_ENV": "dev"},
)

Assert an expected failure​

result = exec.run(["terraform", "validate"], check = False, output = "capture")
if result.exit_code == 0:
fail("expected validation to fail")
if "Missing required argument" not in result.stderr:
fail("validation failed for an unexpected reason:\n" + result.stderr)

Run several programs in parallel​

def lint(directory):
return exec.run(["tflint"], working_directory = directory, output = "capture").stdout

output = steps.parallel(
tasks = [steps.task(name = d, function = lint, args = [d]) for d in ["vpc", "eks", "rds"]],
max_concurrency = 2,
)

Inside a parallel group with more than one task, each streamed line is prefixed with the task name.