Add DeployFailed convention hook (cleanup-phase failure handler)
Summary
Closes the deferred convention from G1.5 (#370). Operator drops DeployFailed.sh in the package; Calamari runs it ONLY when the deploy actually failed.
What's in the box
| Layer | File |
|---|---|
| New opt-in interface | src/Squid.Calamari/Pipeline/IFailureAwareExecutionContext.cs |
| Pipeline sets the flag |
src/Squid.Calamari/Pipeline/ExecutionPipeline.cs (catch block) |
| Context implements interface | src/Squid.Calamari/Commands/RunScriptCommandPipeline.cs |
| Step |
src/Squid.Calamari/Commands/Conventions/DeployFailedConventionStep.cs (new) |
| Wire literal | ConventionScriptNames.DeployFailed = "DeployFailed" |
| Pipeline wiring |
RunScriptCommand.cs (cleanup phase, before CleanupTemporaryFiles) |
| Tests |
FailureAwarePipelineTests.cs (4) + DeployFailedConventionStepTests.cs (13) |
Predicate truth table
| Script exists? | ExecutionFailed flag | Main exit code | Fires? |
|---|---|---|---|
| No | — | — | No |
| Yes | false | 0 / null | No (pinned) |
| Yes | true | — | Yes |
| Yes | false | non-zero | Yes |
Key design choices
-
No re-throw on hook failure — if
DeployFailed.shitself fails, log structured warning + swallow. Original execution failure stays the canonical cause for forensics. Matches Octopus. -
Output vars merge into
context.Variables— symmetric with PreDeploy/PostDeploy/main contract. -
Cleanup-phase order — runs BEFORE
CleanupTemporaryFilesStep, so the script can read the extracted+bootstrapped temp files. -
Opt-in interface pattern — non-implementing contexts unaffected. Same lightweight extension as
IPathBasedExecutionContext.
Test plan
-
Squid.Calamari.Tests: 335/335 (was 318; +17 new) -
Squid.UnitTests: 5625/5625 (unchanged) -
Solution build: 0 errors -
Wire literal pinned: ConventionScriptNames.DeployFailed = "DeployFailed" -
Pre-merge audit GREEN on 6/6 invariants -
Staging: real .nupkgwithDeployFailed.sh+ intentionally-failing main script
Non-breaking guarantee
| Surface | Status |
|---|---|
| Existing wire literals | Unchanged |
| Standalone-script deploys (no DeployFailed.sh) | Zero impact — IsEnabled returns false |
| Successful deploys with DeployFailed.sh shipped | Zero impact — IsEnabled returns false (CRITICAL — pinned by IsEnabled_ScriptPresent_NoFailure_ReturnsFalse) |
| ExecutionPipeline contract with non-FailureAware contexts | Unchanged (pinned by ContextWithoutFailureAware_NotAffected_BackCompat) |
| SquidWeb | Untouched — file-based convention, no UI wire literal |