Skip to content

Commit

Permalink
ephemeral: tests for setting unplanned vars during apply
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielMSchmidt committed Nov 4, 2024
1 parent f82dcb2 commit 92c559a
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 17 deletions.
29 changes: 12 additions & 17 deletions internal/backend/local/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
Expand Down
91 changes: 91 additions & 0 deletions internal/command/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 92c559a

Please sign in to comment.