Skip to content

Commit df0a70b

Browse files
committed
check for cancellation before apply confirmation
When executing an apply with no plan, it's possible for a cancellation to arrive during the final batch of provider operations, resulting in no errors in the plan. The run context was next checked during the confirmation for apply, but in the case of -auto-approve that confirmation is skipped, resulting in the canceled plan being applied. Make sure we directly check for cancellation before confirming the plan.
1 parent 488853b commit df0a70b

File tree

2 files changed

+53
-0
lines changed

2 files changed

+53
-0
lines changed

internal/backend/local/backend_apply.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package local
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"log"
78

@@ -16,6 +17,9 @@ import (
1617
"github.com/hashicorp/terraform/internal/tfdiags"
1718
)
1819

20+
// test hook called between plan+apply during opApply
21+
var testHookStopPlanApply func()
22+
1923
func (b *Local) opApply(
2024
stopCtx context.Context,
2125
cancelCtx context.Context,
@@ -88,6 +92,22 @@ func (b *Local) opApply(
8892
mustConfirm := hasUI && !op.AutoApprove && !trivialPlan
8993
op.View.Plan(plan, schemas)
9094

95+
if testHookStopPlanApply != nil {
96+
testHookStopPlanApply()
97+
}
98+
99+
// Check if we've been stopped before going through confirmation, or
100+
// skipping confirmation in the case of -auto-approve.
101+
// This can currently happen if a single stop request was received
102+
// during the final batch of resource plan calls, so no operations were
103+
// forced to abort, and no errors were returned from Plan.
104+
if stopCtx.Err() != nil {
105+
diags = diags.Append(errors.New("execution halted"))
106+
runningOp.Result = backend.OperationFailure
107+
op.ReportResult(runningOp, diags)
108+
return
109+
}
110+
91111
if mustConfirm {
92112
var desc, query string
93113
switch op.PlanMode {

internal/backend/local/backend_apply_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,36 @@ func applyFixtureSchema() *terraform.ProviderSchema {
351351
},
352352
}
353353
}
354+
355+
func TestApply_applyCanceledAutoApprove(t *testing.T) {
356+
b := TestLocal(t)
357+
358+
TestLocalProvider(t, b, "test", applyFixtureSchema())
359+
360+
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
361+
op.AutoApprove = true
362+
defer configCleanup()
363+
defer func() {
364+
output := done(t)
365+
if !strings.Contains(output.Stderr(), "execution halted") {
366+
t.Fatal("expected 'execution halted', got:\n", output.All())
367+
}
368+
}()
369+
370+
ctx, cancel := context.WithCancel(context.Background())
371+
testHookStopPlanApply = cancel
372+
defer func() {
373+
testHookStopPlanApply = nil
374+
}()
375+
376+
run, err := b.Operation(ctx, op)
377+
if err != nil {
378+
t.Fatalf("error starting operation: %v", err)
379+
}
380+
381+
<-run.Done()
382+
if run.Result == backend.OperationSuccess {
383+
t.Fatal("expected apply operation to fail")
384+
}
385+
386+
}

0 commit comments

Comments
 (0)