From 2b80df016377171970ec8268a9b39222d9bfedfe Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 9 Oct 2018 12:19:24 -0700 Subject: [PATCH] backend/local: Require caller to set PlanOutBackend with PlanOutPath We can't generate a valid plan file without a backend configuration to write into it, but it's the responsibility of the caller (the command package) to manage the backend configuration mechanism, so we require it to tell us what to write here. This feels a little strange because the backend in principle knows its own config, but in practice the backend only knows the _processed_ version of the config, not the raw configuration value that was used to configure it. --- backend/local/backend_plan.go | 12 +++++++----- backend/local/backend_plan_test.go | 26 ++++++++++++++++++++++++++ backend/local/testing.go | 4 ++++ plans/planfile/tfplan.go | 7 +++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/backend/local/backend_plan.go b/backend/local/backend_plan.go index 6d2639d323..a50426f3a1 100644 --- a/backend/local/backend_plan.go +++ b/backend/local/backend_plan.go @@ -114,12 +114,14 @@ func (b *Local) opPlan( // Save the plan to disk if path := op.PlanOutPath; path != "" { - if op.PlanOutBackend != nil { - plan.Backend = *op.PlanOutBackend - } else { - op.PlanOutBackend = &plans.Backend{} - plan.Backend = *op.PlanOutBackend + if op.PlanOutBackend == nil { + // This is always a bug in the operation caller; it's not valid + // to set PlanOutPath without also setting PlanOutBackend. + diags = diags.Append(fmt.Errorf("PlanOutPath set without also setting PlanOutBackend (this is a bug in Terraform)")) + b.ReportResult(runningOp, diags) + return } + plan.Backend = *op.PlanOutBackend // We may have updated the state in the refresh step above, but we // will freeze that updated state in the plan file for now and diff --git a/backend/local/backend_plan_test.go b/backend/local/backend_plan_test.go index 7ce9e84521..541a58ad8a 100644 --- a/backend/local/backend_plan_test.go +++ b/backend/local/backend_plan_test.go @@ -174,6 +174,18 @@ func TestLocal_planDestroy(t *testing.T) { op.Destroy = true op.PlanRefresh = true op.PlanOutPath = planPath + cfg := cty.ObjectVal(map[string]cty.Value{ + "path": cty.StringVal(b.StatePath), + }) + cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type()) + if err != nil { + t.Fatal(err) + } + op.PlanOutBackend = &plans.Backend{ + // Just a placeholder so that we can generate a valid plan file. + Type: "local", + Config: cfgRaw, + } run, err := b.Operation(context.Background(), op) if err != nil { @@ -213,6 +225,18 @@ func TestLocal_planOutPathNoChange(t *testing.T) { op, configCleanup := testOperationPlan(t, "./test-fixtures/plan") defer configCleanup() op.PlanOutPath = planPath + cfg := cty.ObjectVal(map[string]cty.Value{ + "path": cty.StringVal(b.StatePath), + }) + cfgRaw, err := plans.NewDynamicValue(cfg, cfg.Type()) + if err != nil { + t.Fatal(err) + } + op.PlanOutBackend = &plans.Backend{ + // Just a placeholder so that we can generate a valid plan file. + Type: "local", + Config: cfgRaw, + } run, err := b.Operation(context.Background(), op) if err != nil { @@ -314,6 +338,8 @@ func testPlanState() *states.State { } func testReadPlan(t *testing.T, path string) *plans.Plan { + t.Helper() + p, err := planfile.Open(path) if err != nil { t.Fatalf("err: %s", err) diff --git a/backend/local/testing.go b/backend/local/testing.go index 8b4710c22a..9266c9ccbe 100644 --- a/backend/local/testing.go +++ b/backend/local/testing.go @@ -34,6 +34,10 @@ func TestLocal(t *testing.T) (*Local, func()) { var diags tfdiags.Diagnostics diags = diags.Append(vals...) for _, diag := range diags { + // NOTE: Since the caller here is not directly the TestLocal + // function, t.Helper doesn't apply and so the log source + // isn't correctly shown in the test log output. This seems + // unavoidable as long as this is happening so indirectly. t.Log(diag.Description().Summary) if local.CLI != nil { local.CLI.Error(diag.Description().Summary) diff --git a/plans/planfile/tfplan.go b/plans/planfile/tfplan.go index 2db909b9fb..e1deeb083a 100644 --- a/plans/planfile/tfplan.go +++ b/plans/planfile/tfplan.go @@ -351,6 +351,13 @@ func writeTfplan(plan *plans.Plan, w io.Writer) error { rawPlan.Variables[name] = valueToTfplan(val) } + if plan.Backend.Type == "" || plan.Backend.Config == nil { + // This suggests a bug in the code that created the plan, since it + // ought to always have a backend populated, even if it's the default + // "local" backend with a local state file. + return fmt.Errorf("plan does not have a backend configuration") + } + rawPlan.Backend = &planproto.Backend{ Type: plan.Backend.Type, Config: valueToTfplan(plan.Backend.Config),