diff --git a/internal/backend/local/backend_apply.go b/internal/backend/local/backend_apply.go index 60b63bcf50..a87edb29cd 100644 --- a/internal/backend/local/backend_apply.go +++ b/internal/backend/local/backend_apply.go @@ -328,35 +328,30 @@ func (b *Local) opApply( applyTimeValues[varName] = val } else { // If a non-ephemeral variable is set differently between plan and apply, we should emit a diagnostic. - value, ok := plan.VariableValues[varName] + plannedVariableValue, ok := plan.VariableValues[varName] if !ok { - if v.Value.IsNull() { - continue - } else { - // TODO: Add test for this case - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Can't set variable when applying a saved plan", - Detail: fmt.Sprintf("The variable %s cannot be set using the -var and -var-file options when applying a saved plan file, because a saved plan includes the variable values that were set when it was created. To declare an ephemeral variable which is not saved in the plan file, use ephemeral = true.", varName), - Subject: rng, - }) - } + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Can't set variable when applying a saved plan", + Detail: fmt.Sprintf("The variable %s cannot be set using the -var and -var-file options when applying a saved plan file, because it is neither ephemeral nor has it been declared during the plan operation. To declare an ephemeral variable which is not saved in the plan file, use ephemeral = true.", varName), + Subject: rng, + }) + continue } - val, err := value.Decode(cty.DynamicPseudoType) + val, err := plannedVariableValue.Decode(cty.DynamicPseudoType) if err != nil { - // TODO: Reword error message diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: "Variable was set with a different type when applying a saved plan", - Detail: fmt.Sprintf("The variable %s was set to a different type of value during plan than during apply. Please either don't supply the value or supply the same value if the variable.", varName), + Summary: "Could not decode variable value from plan", + Detail: fmt.Sprintf("The variable %s could not be decoded from the plan. %s. This is a bug in Terraform, please report it.", varName, err), Subject: rng, }) } else { if v.Value.Equals(val) == cty.False { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: "Can't set variable when applying a saved plan", + Summary: "Can't change variable when applying a saved plan", Detail: fmt.Sprintf("The variable %s cannot be set using the -var and -var-file options when applying a saved plan file, because a saved plan includes the variable values that were set when it was created. The saved plan specifies %s as the value whereas during apply the value %s was %s. To declare an ephemeral variable which is not saved in the plan file, use ephemeral = true.", varName, viewsjson.CompactValueStr(v.Value), viewsjson.CompactValueStr(val), v.SourceType.DiagnosticLabel()), Subject: rng, }) diff --git a/internal/command/apply_test.go b/internal/command/apply_test.go index 7222a0dd3e..16340d0546 100644 --- a/internal/command/apply_test.go +++ b/internal/command/apply_test.go @@ -860,6 +860,7 @@ func TestApply_planWithVarFile(t *testing.T) { t.Fatalf("err: %s", err) } + // The value of foo is the same as in the var file planPath := applyFixturePlanFileWithVariableValue(t, "bar") statePath := testTempFile(t) @@ -901,6 +902,96 @@ func TestApply_planWithVarFile(t *testing.T) { } } +func TestApply_planWithVarFilePreviouslyUnset(t *testing.T) { + varFileDir := testTempDir(t) + varFilePath := filepath.Join(varFileDir, "terraform.tfvars") + if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + // The value of foo is not set + planPath := applyFixturePlanFile(t) + statePath := testTempFile(t) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + if err := os.Chdir(varFileDir); err != nil { + t.Fatalf("err: %s", err) + } + defer os.Chdir(cwd) + + p := applyFixtureProvider() + view, done := testView(t) + c := &ApplyCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + View: view, + }, + } + + args := []string{ + "-state-out", statePath, + planPath, + } + code := c.Run(args) + output := done(t) + if code == 0 { + t.Fatalf("expected to fail, but succeeded. \n\n%s", output.All()) + } + + expectedTitle := "Can't set variable when applying a saved plan" + if !strings.Contains(output.Stderr(), expectedTitle) { + t.Fatalf("Expected stderr to contain %q, got %q", expectedTitle, output.Stderr()) + } +} + +func TestApply_planWithVarFileChangingVariableValue(t *testing.T) { + varFileDir := testTempDir(t) + varFilePath := filepath.Join(varFileDir, "terraform.tfvars") + if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { + t.Fatalf("err: %s", err) + } + + // The value of foo is differnet from the var file + planPath := applyFixturePlanFileWithVariableValue(t, "lorem ipsum") + statePath := testTempFile(t) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + if err := os.Chdir(varFileDir); err != nil { + t.Fatalf("err: %s", err) + } + defer os.Chdir(cwd) + + p := applyFixtureProvider() + view, done := testView(t) + c := &ApplyCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + View: view, + }, + } + + args := []string{ + "-state-out", statePath, + planPath, + } + code := c.Run(args) + output := done(t) + if code == 0 { + t.Fatalf("expected to fail, but succeeded. \n\n%s", output.All()) + } + + expectedTitle := "Can't change variable when applying a saved plan" + if !strings.Contains(output.Stderr(), expectedTitle) { + t.Fatalf("Expected stderr to contain %q, got %q", expectedTitle, output.Stderr()) + } +} + func TestApply_planVars(t *testing.T) { // This test ensures that it isn't allowed to set non-ephemeral input // variables when applying from a saved plan file, since in that case the