mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-04-30 16:03:37 -04:00
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.
This commit is contained in:
@@ -2,6 +2,7 @@ package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
@@ -16,6 +17,9 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// test hook called between plan+apply during opApply
|
||||
var testHookStopPlanApply func()
|
||||
|
||||
func (b *Local) opApply(
|
||||
stopCtx context.Context,
|
||||
cancelCtx context.Context,
|
||||
@@ -88,6 +92,22 @@ func (b *Local) opApply(
|
||||
mustConfirm := hasUI && !op.AutoApprove && !trivialPlan
|
||||
op.View.Plan(plan, schemas)
|
||||
|
||||
if testHookStopPlanApply != nil {
|
||||
testHookStopPlanApply()
|
||||
}
|
||||
|
||||
// Check if we've been stopped before going through confirmation, or
|
||||
// skipping confirmation in the case of -auto-approve.
|
||||
// This can currently happen if a single stop request was received
|
||||
// during the final batch of resource plan calls, so no operations were
|
||||
// forced to abort, and no errors were returned from Plan.
|
||||
if stopCtx.Err() != nil {
|
||||
diags = diags.Append(errors.New("execution halted"))
|
||||
runningOp.Result = backend.OperationFailure
|
||||
op.ReportResult(runningOp, diags)
|
||||
return
|
||||
}
|
||||
|
||||
if mustConfirm {
|
||||
var desc, query string
|
||||
switch op.PlanMode {
|
||||
|
||||
@@ -351,3 +351,36 @@ func applyFixtureSchema() *terraform.ProviderSchema {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestApply_applyCanceledAutoApprove(t *testing.T) {
|
||||
b := TestLocal(t)
|
||||
|
||||
TestLocalProvider(t, b, "test", applyFixtureSchema())
|
||||
|
||||
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
|
||||
op.AutoApprove = true
|
||||
defer configCleanup()
|
||||
defer func() {
|
||||
output := done(t)
|
||||
if !strings.Contains(output.Stderr(), "execution halted") {
|
||||
t.Fatal("expected 'execution halted', got:\n", output.All())
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
testHookStopPlanApply = cancel
|
||||
defer func() {
|
||||
testHookStopPlanApply = nil
|
||||
}()
|
||||
|
||||
run, err := b.Operation(ctx, op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
|
||||
<-run.Done()
|
||||
if run.Result == backend.OperationSuccess {
|
||||
t.Fatal("expected apply operation to fail")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user