Skip to content

Exit codes

Every sadame command ends with an exit status a script can act on: 0 when it did what was asked, 1 when apply failed or drift found a difference, and 2 when sadame could not do its job at all: a wrong command line, a broken recipe, a state it could not read, a host it could not reach. 2 never means "the machine differs"; a CI job can tell "the servers drifted" from "the check itself is broken".

Minimal example

sadame drift site.rb -g all
case $? in
  0) echo "every host matches" ;;
  1) echo "drift: run apply, or update the recipe" ;;
  *) echo "the check could not run" ; exit 2 ;;
esac

By command

Status plan apply drift hosts
0 plan computed, with or without changes every resource and notification succeeded every host matches the recipe hosts listed
1 not used a resource or a notification failed a host differs from the recipe not used
2 usage or recipe error, a state could not be read, a host could not be reached usage or recipe error, a host could not be reached usage or recipe error, a state could not be read, a host could not be reached usage or inventory error

plan exits 0 even when there are changes: it answers "what would change", not "is anything different". Use drift for that question.

Several hosts

With remote targets, each host gets a result, and the run exits with the highest status among them:

Host result Meaning Status
ok sadame finished on the host with status 0 0
drift drift found a difference on the host 1
failed apply had a failed resource on the host 1
error sadame could not run on the host, or exited with 2 there 2
unreachable ssh could not connect 2

One host that drifted makes the whole run exit 1:

$ sadame drift site.rb --limit 'web*'
[web2.example.com] sadame 0.5.1 drift on web2.example.com (Debian GNU/Linux 12 (bookworm))
[web2.example.com]
[web1.example.com] sadame 0.5.1 drift on web1.example.com (Debian GNU/Linux 12 (bookworm))
[web1.example.com]
[web2.example.com] ~ file[/etc/demo-app/web.conf]
[web2.example.com]     --- /etc/demo-app/web.conf
[web2.example.com]     +++ /etc/demo-app/web.conf (desired)
[web2.example.com]     @@ -1,3 +1,3 @@
[web2.example.com]      # Managed by sadame.
[web2.example.com]      environment = production
[web2.example.com]     -workers     = 12
[web2.example.com]     +workers     = 8
[web2.example.com]
[web1.example.com] No drift: 6 resources match the recipe.
[web2.example.com] Drift: 1 of 6 resources differs from the recipe.

Hosts
  web1.example.com  ok
  web2.example.com  drift

drift on 2 hosts: 1 ok, 1 drift. Finished in 0.22s.

$ echo $?
1

One host that cannot be reached makes it exit 2, and the other hosts still run:

$ sadame plan site.rb -g db -h deploy@db2.example.com
[db1.example.com] sadame 0.5.1 plan on db1.example.com (Debian GNU/Linux 12 (bookworm))
[db1.example.com]
[db1.example.com] Plan: 0 to create, 0 to update, 0 to remove, 0 to run, 6 unchanged.

Hosts
  db1.example.com  ok
  db2.example.com  unreachable  connect failed: ssh: Could not resolve hostname db2.example.com: Name or service not known

plan on 2 hosts: 1 ok, 1 unreachable. Finished in 0.22s.

$ echo $?
2

Scenarios

Fail a scheduled CI job only on drift. Treat 1 and 2 differently, so a broken check does not look like drift (and drift does not look like a broken check). Drift in CI has complete GitHub Actions and GitLab CI pipelines.

Gate a deployment on a clean apply. In a shell script, set -e stops at the first failing host's exit status:

set -e
sadame apply site.rb -g db
sadame apply site.rb -g web

Read the status per host. --json reports each host's result and exit status, for when one number for the whole run is not enough (see JSON output).

Common mistakes

  • Checking plan's status for changes. It is 0 either way; use drift.
  • Treating every non-zero status as drift. 2 means the check did not complete; the servers may be fine.
  • Unguarded exec resources. An exec without creates:, not_if: or only_if: always differs, so drift always exits 1.

See also