Pre-flight Guardrails: Refusing a Bad Run
Scenario: Someone ran the production deploy with -e app_version=latest. 'latest' was an untested nightly build. Nothing in the automation objected.
New words, in plain English
assert- a module that fails the task unless every listed condition is true.fail_msg- the message shown when an assertion fails - this is what the operator reads at 3 AM.fail- unconditionally fail with a message, usually guarded by awhen.- Fail fast - stopping before any host is modified, rather than half-way through.
pre_tasks- tasks that run before every role in the play.
Automation applies your instructions faithfully, including the wrong ones. Guardrails are where you encode the things a human reviewer would have objected to.
assert takes a list of conditions under that: and fails unless all are true. Put assertions in pre_tasks so they run before any role touches anything - a run rejected before the first change is an inconvenience; a run rejected half-way through is an incident.
What is worth asserting is domain knowledge, not syntax: that a version looks like a real version rather than a moving tag; that a deploy user is set; that target_env matches the inventory you are actually pointed at; that a required credential is present; that you are not about to run a database migration against the wrong cluster.
fail_msg deserves real care. It is read by someone under pressure who did not write the playbook. "Assertion failed" is useless; "app_version must be x.y.z, got 'latest' - use a pinned release, not a moving tag" tells them exactly what to do next.
run_once: true on an assertion avoids repeating the same failure for every host in the inventory, which keeps the output readable.
Analogy: A guardrail is the height bar at the entrance to a ride. It does not make the ride safer for people who fit; it stops the one case that would have been a disaster, before the ride starts rather than at the first corner.
A worked example
- name: Deploy the application
hosts: production
pre_tasks:
- name: Refuse to deploy without valid inputs
ansible.builtin.assert:
that:
- app_version is defined
- app_version is match('^\d+\.\d+\.\d+