Skip to content

Commit

Permalink
check for cancellation before apply confirmation
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jbardin committed May 2, 2022
1 parent 488853b commit df0a70b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 0 deletions.
20 changes: 20 additions & 0 deletions internal/backend/local/backend_apply.go
Expand Up @@ -2,6 +2,7 @@ package local

import (
"context"
"errors"
"fmt"
"log"

Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
33 changes: 33 additions & 0 deletions internal/backend/local/backend_apply_test.go
Expand Up @@ -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")
}

}

0 comments on commit df0a70b

Please sign in to comment.