Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

return early from opPlan when the plan is nil #32818

Merged
merged 1 commit into from
Mar 10, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions internal/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,9 @@ type RunningOperation struct {
// operation has completed.
Result OperationResult

// PlanEmpty is populated after a Plan operation completes without error
// to note whether a plan is empty or has changes.
// PlanEmpty is populated after a Plan operation completes to note whether
// a plan is empty or has changes. This is only used in the CLI to determine
// the exit status because the plan value is not available at that point.
PlanEmpty bool

// State is the final state after the operation completed. Persisting
Expand Down
24 changes: 15 additions & 9 deletions internal/backend/local/backend_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,19 @@ func (b *Local) opPlan(
// generate a partial saved plan file for external analysis.
diags = diags.Append(planDiags)

// Even if there are errors we need to handle anything that may be
// contained within the plan, so only exit if there is no data at all.
if plan == nil {
runningOp.PlanEmpty = true
op.ReportResult(runningOp, diags)
return
}

// Record whether this plan includes any side-effects that could be applied.
runningOp.PlanEmpty = !plan.CanApply()

// Save the plan to disk
if path := op.PlanOutPath; path != "" && plan != nil {
if path := op.PlanOutPath; path != "" {
if op.PlanOutBackend == nil {
// This is always a bug in the operation caller; it's not valid
// to set PlanOutPath without also setting PlanOutBackend.
Expand Down Expand Up @@ -154,15 +162,13 @@ func (b *Local) opPlan(

// Render the plan, if we produced one.
// (This might potentially be a partial plan with Errored set to true)
if plan != nil {
schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
op.ReportResult(runningOp, diags)
return
}
op.View.Plan(plan, schemas)
schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
op.ReportResult(runningOp, diags)
return
}
op.View.Plan(plan, schemas)

// If we've accumulated any diagnostics along the way then we'll show them
// here just before we show the summary and next steps. This can potentially
Expand Down
24 changes: 24 additions & 0 deletions internal/backend/local/backend_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,3 +880,27 @@ func planFixtureSchema() *terraform.ProviderSchema {
},
}
}

func TestLocal_invalidOptions(t *testing.T) {
b := TestLocal(t)
TestLocalProvider(t, b, "test", planFixtureSchema())

op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
op.PlanRefresh = true
op.PlanMode = plans.RefreshOnlyMode
op.ForceReplace = []addrs.AbsResourceInstance{mustResourceInstanceAddr("test_instance.foo")}

run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
<-run.Done()
if run.Result == backend.OperationSuccess {
t.Fatalf("plan operation failed")
}

if errOutput := done(t).Stderr(); errOutput == "" {
t.Fatal("expected error output")
}
}