JSON output¶
plan, apply and drift print a single JSON document with --json,
instead of the human-readable report. It contains everything the text
report shows: each resource's status and changes, the notifications, the
summary counts, and the exit status. Nothing else is written to standard
output, so it can be redirected to a file or piped into jq. Errors that
stop sadame before a report exists (a bad option, a recipe that does not
load) go to standard error, and standard output stays empty.
Example¶
sadame drift --json on the machine from
Plan, apply, drift, after
break-things.sh ran:
$ sudo sadame drift examples/10-drift-demo --json
{
"format_version": 1,
"sadame_version": "0.5.1",
"command": "drift",
"host": "local",
"platform": {
"name": "Debian GNU/Linux 12 (bookworm)",
"family": "debian",
"package_manager": "apt"
},
"tags": [],
"skip_tags": [],
"resources": [
{
"id": "package[nginx]",
"type": "package",
"name": "nginx",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
},
{
"id": "user[demo-app]",
"type": "user",
"name": "demo-app",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
},
{
"id": "directory[/etc/demo-app]",
"type": "directory",
"name": "/etc/demo-app",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
},
{
"id": "file[/etc/demo-app/app.conf]",
"type": "file",
"name": "/etc/demo-app/app.conf",
"status": "update",
"changes": [
{
"attribute": "mode",
"from": "0644",
"to": "0640"
},
{
"attribute": "content",
"sensitive": false,
"diff": "--- current\n+++ desired\n@@ -1,4 +1,4 @@\n # Managed by sadame. Local changes will be overwritten.\n listen = 127.0.0.1:9000\n-workers = 16\n+workers = 4\n log_level = info\n"
}
],
"notifications": [],
"error": null
},
{
"id": "service[nginx]",
"type": "service",
"name": "nginx",
"status": "update",
"changes": [
{
"attribute": "state",
"from": "stopped",
"to": "running"
}
],
"notifications": [],
"error": null
}
],
"notifications": [],
"summary": {
"create": 0,
"update": 2,
"remove": 0,
"run": 0,
"unchanged": 3,
"failed": 0
},
"drift": true,
"failed": false,
"exit_status": 1,
"duration_seconds": 0.01
}
Top level¶
| Field | Type | Description |
|---|---|---|
format_version |
integer | Version of this format, currently 1. It changes when a field is removed or changes meaning; new fields may be added without a change. |
sadame_version |
string | Version of sadame that wrote the report. |
command |
string | "plan", "apply" or "drift". |
host |
string | The machine the report is about; "local" for the machine sadame runs on. |
platform |
object | See platform. |
tags, skip_tags |
array of strings | The --tags and --skip-tags of the run; empty without them. |
resources |
array | One resource object per declared resource, in recipe order; with --tags or --skip-tags, per selected resource. |
notifications |
array | Notification runs during apply, in the order they ran. Always empty for plan and drift. |
summary |
object | Number of resources per status; see statuses. Every status of the command is present, including those with 0. |
drift |
boolean or null | plan and drift: true when at least one resource differs from the recipe. apply: null. |
failed |
boolean | true when a resource failed, or for apply, a notification failed. |
exit_status |
integer | The exit status of the command: see Plan, apply, drift. |
duration_seconds |
number | Wall-clock time of the run, rounded to hundredths of a second. |
platform¶
| Field | Type | Description |
|---|---|---|
name |
string | PRETTY_NAME from /etc/os-release, or "macOS". |
family |
string or null | "debian", "rhel", "alpine", "arch", or null when unknown. |
package_manager |
string or null | "apt", "dnf", "apk", "pacman", or null. |
resource¶
| Field | Type | Description |
|---|---|---|
id |
string | type[name], as in the text output and in notifies:. |
type |
string | "package", "file", "directory", "service", "exec" or "user". |
name |
string | The resource name: package name, path, command, user name. |
status |
string | See statuses. |
changes |
array | Change objects. Empty when the resource is unchanged, failed before comparing, or skipped. |
notifications |
array | plan and drift: what this resource would trigger. apply: what it triggered. Each has action, source, target and timing ("delayed" or "immediately"). |
error |
string or null | Why the resource failed, or null. |
change¶
An attribute that differs. Most changes have from and to:
| Field | Type | Description |
|---|---|---|
attribute |
string | "state", "mode", "owner", "group", "enabled", "uid", "home", "shell", "groups" or "command". |
from |
string, integer, boolean, array or null | Current value; null when the resource does not exist yet. |
to |
same | Desired value. |
reason |
string | Only on exec changes: why the command would run, for example "not_if exited non-zero". |
File content changes carry a diff instead of the full text:
| Field | Type | Description |
|---|---|---|
attribute |
string | "content" |
sensitive |
boolean | true when the file has sensitive: true. |
diff |
string or null | Unified diff from current to desired, or null when sensitive. |
notification run¶
| Field | Type | Description |
|---|---|---|
action |
string | The action, such as "reload" or "run". |
source |
string | Id of the resource whose change triggered it. |
target |
string | Id of the resource the action ran on. |
timing |
string | "delayed" or "immediately". |
status |
string | "ok" or "failed". |
error |
string or null | Why it failed, or null. |
Statuses¶
| Command | Status | Meaning |
|---|---|---|
plan, drift |
create |
Does not exist yet. |
update |
Exists, some attributes differ. | |
remove |
Exists, the recipe says state: :absent. |
|
run |
An exec whose guards say it must run. |
|
unchanged |
Matches the recipe. | |
failed |
The current state could not be read; see error. |
|
apply |
changed |
Was changed. |
unchanged |
Already matched; nothing was done. | |
failed |
Changing it failed; see error. |
|
skipped |
Not looked at, because an earlier resource or immediate notification failed. |
Push mode¶
When sadame runs on remote hosts (-h, -g, --limit), --json prints
one document for the whole run. Each host's report, as described above, is
nested under hosts:
| Field | Type | Description |
|---|---|---|
format_version |
integer | As above. |
sadame_version |
string | Version of sadame on the machine that ran the push. |
command |
string | "plan", "apply", "drift" or "facts". |
hosts |
array | One object per host, in the order they were selected. |
hosts[].name |
string | The host's name from the inventory or -h. |
hosts[].status |
string | "ok", "drift", "failed", "error", "unreachable" or "skipped"; see Remote hosts. |
hosts[].exit_status |
integer or null | That host's exit status; 2 when it could not be reached or prepared; null when it was skipped. |
hosts[].error |
string or null | Why the host could not be reached or prepared, or sadame's error message. |
hosts[].report |
object or null | The host's report; null when sadame did not run there. |
exit_status |
integer | The highest exit status of all hosts: the run's exit status. |
duration_seconds |
number | Wall-clock time of the whole run. |
For example, a drift check on one web server after its configuration was edited by hand:
$ sadame drift site.rb -h web2.example.com --json
{
"format_version": 1,
"sadame_version": "0.5.1",
"command": "drift",
"hosts": [
{
"name": "web2.example.com",
"status": "drift",
"exit_status": 1,
"error": null,
"report": {
"format_version": 1,
"sadame_version": "0.5.1",
"command": "drift",
"host": "web2.example.com",
"platform": {
"name": "Debian GNU/Linux 12 (bookworm)",
"family": "debian",
"package_manager": "apt"
},
"tags": [],
"skip_tags": [],
"resources": [
{
"id": "file[/etc/motd]",
"type": "file",
"name": "/etc/motd",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
},
{
"id": "package[nginx]",
"type": "package",
"name": "nginx",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
},
{
"id": "directory[/etc/demo-app]",
"type": "directory",
"name": "/etc/demo-app",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
},
{
"id": "file[/etc/demo-app/web.conf]",
"type": "file",
"name": "/etc/demo-app/web.conf",
"status": "update",
"changes": [
{
"attribute": "content",
"sensitive": false,
"diff": "--- current\n+++ desired\n@@ -1,3 +1,3 @@\n # Managed by sadame.\n environment = production\n-workers = 12\n+workers = 8\n"
}
],
"notifications": [],
"error": null
},
{
"id": "file[/etc/nginx/conf.d/demo-app.conf]",
"type": "file",
"name": "/etc/nginx/conf.d/demo-app.conf",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
},
{
"id": "service[nginx]",
"type": "service",
"name": "nginx",
"status": "unchanged",
"changes": [],
"notifications": [],
"error": null
}
],
"notifications": [],
"summary": {
"create": 0,
"update": 1,
"remove": 0,
"run": 0,
"unchanged": 5,
"failed": 0
},
"drift": true,
"failed": false,
"exit_status": 1,
"duration_seconds": 0.01
}
}
],
"exit_status": 1,
"duration_seconds": 0.22
}
validate¶
sadame validate --json prints format_version, sadame_version,
command ("validate"), recipe, platform, exit_status, and hosts:
one object per host with name, status ("ok" or "error"),
resources (how many the recipe declared; null on error) and error.
facts¶
sadame facts --json prints one document per machine. With -h, -g or
--limit, each host's document is the report of the push-mode document
below.
| Field | Type | Description |
|---|---|---|
format_version |
integer | As above. |
sadame_version |
string | Version of sadame that read the facts. |
command |
string | "facts". |
host |
string | The host's name, as in a report. |
platform |
object | See platform. |
facts |
object | Every fact by name, as in Facts; null for what could not be read. |
Using it with jq¶
Resources that differ, from a saved drift report:
jq -r '.resources[] | select(.status != "unchanged") | "\(.id): \(.status)"' drift.json
file[/etc/demo-app/app.conf]: update
service[nginx]: update
Only the counts:
jq '.summary' drift.json
{
"create": 0,
"update": 2,
"remove": 0,
"run": 0,
"unchanged": 3,
"failed": 0
}
Common mistakes¶
- Reading
failedas "drift found".failedmeans a resource could not be checked or changed. Drift is thedriftfield, and the exit status1. - Parsing the text output. Its layout is for people and may change;
the JSON fields are documented here and versioned by
format_version. - Mixing standard error into the file.
> drift.json 2>&1or&> drift.jsonputs error messages into the JSON file. Redirect only standard output:> drift.json.
See also¶
- Plan, apply, drift for exit statuses.
- Drift in CI for keeping the report as a pipeline artifact.