defer
The defer function registers a call to run when the enclosing script or
steps.parallel task finishes, whether it returned normally, stopped
with fail(), or was cancelled. Use it to tear down what a script started, such as an emulator, a temporary
stack, or a lock, without a finally block the language does not have.
Usage
defer(fn, *args, **kwargs)
Arguments
fn- Required. The function to call later. Any callable works, including a
lambdaor a loaded function. *args,**kwargs(Optional) Arguments passed to
fnwhen it runs. They are captured at the time of thedefercall, as in Go, so a loop variable keeps the value it had when the call was registered.
Returns
The return value is None.
Behavior
- Last in, first out. Call
deferas many times as you like. Deferred calls run in reverse order of registration, so teardown mirrors setup. - Scoped to the script or task. A call registered at the top level or inside any function runs when the script
body finishes. A call registered inside a
steps.taskfunction runs when that task attempt finishes, before the next attempt or task, with the task's output prefix. Each retry attempt unwinds its own deferred calls. - Runs after
fail(). The original error is still the script's error. A failure inside a deferred call is reported after it and never masks it. When the body succeeded, a failing deferred call fails the script. The remaining deferred calls still run. - Runs after cancellation. The first Ctrl-C (SIGINT) cancels the script's own context, runs the deferred calls on
a fresh context with a 30 second grace period, so
exec.runandatmos.*calls inside them still work, and then exits with status 130. The exit is silent: Atmos prints no error box, like a shell, unless a deferred call fails, in which case that failure is reported and the status is still 130. A second Ctrl-C exits immediately without waiting for the remaining deferred calls. This holds for every embedded script session (standalone scripts,scriptsteps, custom commands, workflows, and hooks). It is the automation-language form of a shelltrap ... EXIT; scripts never install signal handlers themselves. - Nested registration. A deferred call may itself call
defer; the new call runs after it.
Examples
Tear down an emulator and a stack in reverse order
atmos.emulator("up", "aws", flags = {"stack": "advanced"})
defer(lambda: atmos.emulator("down", "aws", flags = {"stack": "advanced"}, check = False))
atmos.aws("cfn", "deploy", "artifacts", flags = {"stack": "advanced"})
defer(lambda: atmos.aws("cfn", "delete", "artifacts", flags = {"stack": "advanced", "auto-approve": True}, check = False))
atmos.aws("cfn", "deploy", "app", flags = {"stack": "advanced"})
If the app deploy fails, the artifacts stack is deleted first and the emulator stopped second, and the deploy
error is what Atmos reports.
Capture arguments at registration time
def remove(path):
exec.run(["rm", "-f", path])
for path in ["a.tmp", "b.tmp"]:
defer(remove, path)
Both files are removed; each deferred call keeps its own path.
Clean up per task
def test(stack):
lock = exec.run(["./lock", stack], output = "capture").stdout.strip()
defer(exec.run, ["./unlock", lock], check = False)
atmos.terraform("plan", "vpc", stack)
steps.parallel(tasks = [steps.task(name = s, function = test, args = [s]) for s in ["dev", "staging"]])
Each task unlocks its own stack when it finishes, even if the plan fails.
Errors
- A missing argument, or a first argument that is not callable, fails with an argument error.
- A deferred call that fails reports
deferred <name> failed: <reason>.
Related
when: alwaysruns a later YAML step after an earlier step failed. Usedeferfor cleanup that belongs inside one script or one task.steps.parallelandsteps.taskdefine the task scope.- Atmos Automation Language and the script step