Automation and CI

Use dynomate-cli requests run in scripts and CI jobs. Ask for JSON, check the exit code, and approve the request in advance with --approval never.

JSON Output

--format json prints one JSON document to standard output, also when the request fails. This is the running example with endpointUrl = "http://localhost:8000", against DynamoDB Local:

$ dynomate-cli requests run ./orders/find-and-update-order.dnml --approval never --format json
{
  "version": "1.0",
  "request": "find-and-update-order",
  "status": "success",
  "startedAt": "2026-09-23T06:45:48.976Z",
  "finishedAt": "2026-09-23T06:45:49.456Z",
  "error": null,
  "outcomes": [
    {
      "name": "Find recent order",
      "type": "dynamodb.query",
      "status": "success",
      "startedAt": "2026-09-23T06:45:48.978Z",
      "durationMs": 456,
      "destructive": false,
      "endpoint": "http://localhost:8000",
      "endpointUses": [
        {
          "operationName": "Find recent order",
          "operationKind": "dynamodb.query",
          "role": "operation",
          "service": "dynamodb",
          "profile": "commerce-dev",
          "region": "ap-southeast-2",
          "endpoint": "http://localhost:8000",
          "source": "documentOverride"
        }
      ],
      "result": {
        "count": 1,
        "items": [
          {
            "accountId": "account#42",
            "orderId": "order#9812",
            "status": "PENDING",
            "total": 42
          }
        ],
        "lastEvaluatedKey": "eyJvcmRlcklkIjp7IlMiOiJvcmRlciM5ODEyIn0sImFjY291bnRJZCI6eyJTIjoiYWNjb3VudCM0MiJ9fQ==",
        "pages": 1,
        "requestId": "280d3dd1-c3f2-4cd0-94cd-6ed02fa5d258",
        "scannedCount": 1
      }
    },
    {
      "name": "Mark reviewed",
      "type": "dynamodb.update",
      "status": "success",
      "startedAt": "2026-09-23T06:45:49.435Z",
      "durationMs": 9,
      "destructive": true,
      "endpoint": "http://localhost:8000",
      "endpointUses": [
        {
          "operationName": "Mark reviewed",
          "operationKind": "dynamodb.update",
          "role": "operation",
          "service": "dynamodb",
          "profile": "commerce-dev",
          "region": "ap-southeast-2",
          "endpoint": "http://localhost:8000",
          "source": "documentOverride"
        }
      ],
      "result": {
        "attributes": {
          "accountId": "account#42",
          "orderId": "order#9812",
          "status": "REVIEWED",
          "total": 42
        },
        "requestId": "4a75c4cf-47a1-4115-a8d1-85242d4cf39f"
      }
    },
    {
      "name": "Read back",
      "type": "dynamodb.get",
      "status": "success",
      "startedAt": "2026-09-23T06:45:49.444Z",
      "durationMs": 11,
      "destructive": false,
      "endpoint": "http://localhost:8000",
      "endpointUses": [
        {
          "operationName": "Read back",
          "operationKind": "dynamodb.get",
          "role": "operation",
          "service": "dynamodb",
          "profile": "commerce-dev",
          "region": "ap-southeast-2",
          "endpoint": "http://localhost:8000",
          "source": "documentOverride"
        }
      ],
      "result": {
        "found": true,
        "item": {
          "accountId": "account#42",
          "orderId": "order#9812",
          "status": "REVIEWED",
          "total": 42
        },
        "requestId": "1ebbbe86-9637-4e7f-8fe2-f0048f0609c4"
      }
    }
  ]
}
FieldDescription
versionThe DNML version, "1.0".
requestThe request name.
statussuccess, partial or failed.
startedAt, finishedAtUTC times of the run.
errorA request-level error, for example from validation, or null.
outcomesOne outcome for each operation, in run order. It is empty if the run did not start.

Each outcome has the name, type and status of the operation, and a result, error or skipReason. An operation that started also has startedAt and durationMs. It can also have destructive, endpoint and endpointUses.

The Operation reference shows the result of each operation type.

Parse the Result

Read the JSON with a tool such as jq:

# Overall status: success, partial or failed
jq -r '.status' result.json

# One line per operation, with the skip reason when there is one
jq -r '.outcomes[] | "\(.name): \(.status) \(.skipReason // "")"' result.json

# Error code of the request, or of every failed operation
jq -r '.error.code // empty, (.outcomes[] | .error.code // empty)' result.json

# A value from one operation's result
jq -r '.outcomes[] | select(.name == "Read back") | .result.item.status' result.json

Standard output contains only the result, so you can parse it safely.

Exit Codes in CI

Use the exit code to pass or fail a CI step:

  • 0: the request succeeded.
  • 1: the status is partial or failed.
  • 2: the command line is not valid. Do not parse the empty output.

This step prints each operation status and keeps the exit code:

#!/usr/bin/env bash
set -uo pipefail

dynomate-cli requests run ./orders/find-and-update-order.dnml \
  --approval never \
  --format json > result.json
code=$?

if [ "$code" -eq 2 ]; then
  echo "dynomate-cli was called with invalid arguments" >&2
  exit 2
fi

jq -r '.outcomes[] | "\(.name): \(.status)"' result.json
exit "$code"

Approval in Automation

A CI job has no terminal. A request that needs approval fails with APPROVAL_FAILED and exit code 1.

With the default --approval auto, a request with dynamodb.deleteTable, dynamodb.truncate or an endpointUrl needs approval.

To run the request, use --approval never. Do not use --approval always in automation, because each run needs a terminal.

Pass Inputs and Secrets

The CLI does not read shell environment variables. Give each value with --input:

dynomate-cli requests run ./orders/nightly-check.dnml \
  --approval never \
  --format json \
  --input ORDERS_TABLE_ARN="$ORDERS_TABLE_ARN" \
  --input ACCOUNT_ID="$ACCOUNT_ID" > result.json
  • Do not declare these names in [variables], because request variables override inputs.
  • A missing input fails the request before any operation runs.

Share App Data

The CLI uses the saved profile endpoints and global environments of the app. To use a different directory, set DYNO_APP_DATA_DIR:

$ DYNO_APP_DATA_DIR="$HOME/dynomate-data" dynomate-cli requests run --collection ./orders --request find-and-update-order --global-env staging

A CI runner usually has no app data. Write the endpoint in the request, and give values with --input or --collection-env.

DynamoDB Local in CI

To test in CI with no AWS credentials:

  1. Start DynamoDB Local with -sharedDb:
    docker run -d -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb -inMemory
  2. Set endpointUrl in [defaults] or on an operation:
    [defaults]
    profileName = "commerce-dev"
    endpointUrl = "http://localhost:8000"
  3. Create the tables and add seed data.
  4. Run the request with --approval never.
  • Without -sharedDb, DynamoDB Local keeps separate data for each access key and region. Dynomate uses its own placeholder keys, so the request cannot see tables from other tools.
  • Dynomate accepts plain HTTP only for localhost, IPv4 loopback addresses such as 127.0.0.1, and ::1.
  • For localhost, 127.0.0.1 and ::1, Dynomate does not use the AWS credentials of the profile.
  • With -sharedDb, you do not change the table ARNs in the request.

To add seed data with table import, use a profile with the local endpoint_url and placeholder keys.