Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Backport of "Fix: Saved plan apply hangs with -auto-approve flag using cloud backend" #36532

Merged
merged 1 commit into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/v1.11/BUG FIXES-20250206-155025.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: Fixes hanging behavior seen when applying a saved plan with -auto-approve using the cloud backend
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although there are no special characters, I am somewhat surprised this isn't quoted 🤔 but it appears that changie dry run check is happy so no biggie 🤷🏻

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YAML 🤷 😂

Thanks for checking

time: 2025-02-06T15:50:25.767607-05:00
custom:
Issue: "36453"
8 changes: 5 additions & 3 deletions internal/cloud/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backendrun.Opera

var r *tfe.Run
var err error

if cp, ok := op.PlanFile.Cloud(); ok {
cp, hasSavedPlanFile := op.PlanFile.Cloud()
if hasSavedPlanFile {
log.Printf("[TRACE] Loading saved cloud plan for apply")
// Check hostname first, for a more actionable error than a generic 404 later
if cp.Hostname != b.Hostname {
Expand Down Expand Up @@ -182,7 +182,9 @@ func (b *Cloud) opApply(stopCtx, cancelCtx context.Context, op *backendrun.Opera
}

// Do the apply!
if !op.AutoApprove && err != errRunApproved {
// If we have a saved plan file, we proceed to apply the run without confirmation
// regardless of the value of AutoApprove.
if (!op.AutoApprove || hasSavedPlanFile) && err != errRunApproved {
if err = b.client.Runs.Apply(stopCtx, r.ID, tfe.RunApplyOptions{}); err != nil {
return r, b.generalError("Failed to approve the apply command", err)
}
Expand Down
67 changes: 67 additions & 0 deletions internal/cloud/backend_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,73 @@ func TestCloud_applyWithCloudPlan(t *testing.T) {
}
}

func TestCloud_applyAutoApprove_with_CloudPlan(t *testing.T) {
b, bCleanup := testBackendWithName(t)
defer bCleanup()

op, configCleanup, done := testOperationApply(t, "./testdata/apply-json")
defer configCleanup()
defer done(t)

op.AutoApprove = true
op.UIOut = b.CLI
op.Workspace = testBackendSingleWorkspaceName

mockSROWorkspace(t, b, op.Workspace)

ws, err := b.client.Workspaces.Read(context.Background(), b.Organization, b.WorkspaceMapping.Name)
if err != nil {
t.Fatalf("Couldn't read workspace: %s", err)
}

planRun, err := b.plan(context.Background(), context.Background(), op, ws)
if err != nil {
t.Fatalf("Couldn't perform plan: %s", err)
}

// Synthesize a cloud plan file with the plan's run ID
pf := &cloudplan.SavedPlanBookmark{
RemotePlanFormat: 1,
RunID: planRun.ID,
Hostname: b.Hostname,
}
op.PlanFile = planfile.NewWrappedCloud(pf)

// Start spying on the apply output (now that the plan's done)
stream, close := terminal.StreamsForTesting(t)

b.renderer = &jsonformat.Renderer{
Streams: stream,
Colorize: mockColorize(),
}

// Try apply
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("error starting operation: %v", err)
}

<-run.Done()
output := close(t)
if run.Result != backendrun.OperationSuccess {
t.Fatal("expected apply operation to succeed")
}
if run.PlanEmpty {
t.Fatalf("expected plan to not be empty")
}

gotOut := output.Stdout()
if !strings.Contains(gotOut, "1 added, 0 changed, 0 destroyed") {
t.Fatalf("expected apply summary in output: %s", gotOut)
}

stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}

func TestCloud_applyWithoutRefresh(t *testing.T) {
b, bCleanup := testBackendWithName(t)
defer bCleanup()
Expand Down
Loading