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"
}
}
]
} | Field | Description |
|---|---|
version | The DNML version, "1.0". |
request | The request name. |
status | success, partial or failed. |
startedAt, finishedAt | UTC times of the run. |
error | A request-level error, for example from validation, or null. |
outcomes | One 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 ispartialorfailed.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.
DynamoDB Local in CI
To test in CI with no AWS credentials:
-
Start DynamoDB Local with
-sharedDb:docker run -d -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb -inMemory -
Set
endpointUrlin[defaults]or on an operation:[defaults] profileName = "commerce-dev" endpointUrl = "http://localhost:8000" - Create the tables and add seed data.
- 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 as127.0.0.1, and::1. - For
localhost,127.0.0.1and::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.