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.
This commit is contained in:
parent
88984aaca0
commit
2b80df0163
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue