Skip to content

Commit 1996c85

Browse files
dbanckMaed223
andauthored
Merge pull request #36453 from hashicorp/IPL-7602/saved-plan-apply-hangs-with-auto-approve-flag (#36532)
Fix: Saved plan apply hangs with `-auto-approve` flag using cloud backend Co-authored-by: Mark DeCrane <[email protected]>
1 parent 1f2b2f7 commit 1996c85

File tree

3 files changed

+77
-3
lines changed

3 files changed

+77
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: Fixes hanging behavior seen when applying a saved plan with -auto-approve using the cloud backend
3+
time: 2025-02-06T15:50:25.767607-05:00
4+
custom:
5+
Issue: "36453"

internal/cloud/backend_apply.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backendrun.Opera
8383

8484
var r *tfe.Run
8585
var err error
86-
87-
if cp, ok := op.PlanFile.Cloud(); ok {
86+
cp, hasSavedPlanFile := op.PlanFile.Cloud()
87+
if hasSavedPlanFile {
8888
log.Printf("[TRACE] Loading saved cloud plan for apply")
8989
// Check hostname first, for a more actionable error than a generic 404 later
9090
if cp.Hostname != b.Hostname {
@@ -182,7 +182,9 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backendrun.Opera
182182
}
183183

184184
// Do the apply!
185-
if !op.AutoApprove && err != errRunApproved {
185+
// If we have a saved plan file, we proceed to apply the run without confirmation
186+
// regardless of the value of AutoApprove.
187+
if (!op.AutoApprove || hasSavedPlanFile) && err != errRunApproved {
186188
if err = b.client.Runs.Apply(stopCtx, r.ID, tfe.RunApplyOptions{}); err != nil {
187189
return r, b.generalError("Failed to approve the apply command", err)
188190
}

internal/cloud/backend_apply_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,73 @@ func TestCloud_applyWithCloudPlan(t *testing.T) {
508508
}
509509
}
510510

511+
func TestCloud_applyAutoApprove_with_CloudPlan(t *testing.T) {
512+
b, bCleanup := testBackendWithName(t)
513+
defer bCleanup()
514+
515+
op, configCleanup, done := testOperationApply(t, "./testdata/apply-json")
516+
defer configCleanup()
517+
defer done(t)
518+
519+
op.AutoApprove = true
520+
op.UIOut = b.CLI
521+
op.Workspace = testBackendSingleWorkspaceName
522+
523+
mockSROWorkspace(t, b, op.Workspace)
524+
525+
ws, err := b.client.Workspaces.Read(context.Background(), b.Organization, b.WorkspaceMapping.Name)
526+
if err != nil {
527+
t.Fatalf("Couldn't read workspace: %s", err)
528+
}
529+
530+
planRun, err := b.plan(context.Background(), context.Background(), op, ws)
531+
if err != nil {
532+
t.Fatalf("Couldn't perform plan: %s", err)
533+
}
534+
535+
// Synthesize a cloud plan file with the plan's run ID
536+
pf := &cloudplan.SavedPlanBookmark{
537+
RemotePlanFormat: 1,
538+
RunID: planRun.ID,
539+
Hostname: b.Hostname,
540+
}
541+
op.PlanFile = planfile.NewWrappedCloud(pf)
542+
543+
// Start spying on the apply output (now that the plan's done)
544+
stream, close := terminal.StreamsForTesting(t)
545+
546+
b.renderer = &jsonformat.Renderer{
547+
Streams: stream,
548+
Colorize: mockColorize(),
549+
}
550+
551+
// Try apply
552+
run, err := b.Operation(context.Background(), op)
553+
if err != nil {
554+
t.Fatalf("error starting operation: %v", err)
555+
}
556+
557+
<-run.Done()
558+
output := close(t)
559+
if run.Result != backendrun.OperationSuccess {
560+
t.Fatal("expected apply operation to succeed")
561+
}
562+
if run.PlanEmpty {
563+
t.Fatalf("expected plan to not be empty")
564+
}
565+
566+
gotOut := output.Stdout()
567+
if !strings.Contains(gotOut, "1 added, 0 changed, 0 destroyed") {
568+
t.Fatalf("expected apply summary in output: %s", gotOut)
569+
}
570+
571+
stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
572+
// An error suggests that the state was not unlocked after apply
573+
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
574+
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
575+
}
576+
}
577+
511578
func TestCloud_applyWithoutRefresh(t *testing.T) {
512579
b, bCleanup := testBackendWithName(t)
513580
defer bCleanup()

0 commit comments

Comments
 (0)