Skip to content

Commit

Permalink
Fix diagnostic & avoid variable override via environment (#36435)
Browse files Browse the repository at this point in the history
* add changie entry

* add test

* Fix diagnostic & avoid variable override via environment
  • Loading branch information
radeksimko authored Feb 5, 2025
1 parent 5508e9e commit 70a1fd5
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changes/backported/BUG FIXES-20250205-36435.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: Attempting to override a variable during `apply` via `TF_VAR_` environment variable will now yield warning instead of misleading error.
time: 2025-02-05T12:53:26.000+00:00
custom:
Issue: "36435"
53 changes: 43 additions & 10 deletions internal/backend/local/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,16 +340,49 @@ func (b *Local) opApply(
})
} else {
// The user can't override the planned variables, so we
// error when possible to avoid confusion. If the parsed
// variables comes from an auto-file however, it's not input
// directly by the user so we have to ignore it.
if parsedVar.Value.Equals(plannedVar).False() && parsedVar.SourceType != terraform.ValueFromAutoFile {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
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, tfdiags.CompactValueStr(parsedVar.Value), tfdiags.CompactValueStr(plannedVar), parsedVar.SourceType.DiagnosticLabel()),
Subject: rng,
})
// error when possible to avoid confusion.
if parsedVar.Value.Equals(plannedVar).False() {
switch parsedVar.SourceType {
case terraform.ValueFromAutoFile:
// If the parsed variables comes from an auto-file,
// it's not input directly by the user so we have to ignore it.
continue
case terraform.ValueFromEnvVar:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Ignoring variable when applying a saved plan",
Detail: fmt.Sprintf("The variable %s cannot be overriden 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, tfdiags.CompactValueStr(plannedVar), tfdiags.CompactValueStr(parsedVar.Value),
parsedVar.SourceType.DiagnosticLabel()),
Subject: rng,
})
case terraform.ValueFromCLIArg, terraform.ValueFromNamedFile:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
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, tfdiags.CompactValueStr(plannedVar), tfdiags.CompactValueStr(parsedVar.Value),
parsedVar.SourceType.DiagnosticLabel()),
Subject: rng,
})
default:
// Other SourceTypes should never reach this point because
// - ValueFromConfig - supplied plan already contains the original configuration
// - ValueFromInput - we disable prompt when plan file is supplied
// - ValueFromCaller - only used in tests
panic(fmt.Sprintf("Attempted to change variable %s when applying a saved plan. "+
"The saved plan specifies %s as the value whereas during apply the value %s was %s. "+
"This is a bug in Terraform, please report it.",
varName, tfdiags.CompactValueStr(plannedVar), tfdiags.CompactValueStr(parsedVar.Value),
parsedVar.SourceType.DiagnosticLabel()))
}
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions internal/command/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,64 @@ func TestApply_planUndeclaredVars(t *testing.T) {
}
}

func TestApply_planWithEnvVars(t *testing.T) {
_, snap := testModuleWithSnapshot(t, "apply-output-only")
plan := testPlan(t)

addr, diags := addrs.ParseAbsOutputValueStr("output.shadow")
if diags.HasErrors() {
t.Fatal(diags.Err())
}

shadowVal := mustNewDynamicValue("noot", cty.DynamicPseudoType)
plan.VariableValues = map[string]plans.DynamicValue{
"shadow": shadowVal,
}
plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{
Addr: addr,
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
After: shadowVal,
},
})
planPath := testPlanFileMatchState(
t,
snap,
states.NewState(),
plan,
statemgr.SnapshotMeta{},
)

statePath := testTempFile(t)

p := applyFixtureProvider()
view, done := testView(t)
c := &ApplyCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
View: view,
},
}

t.Setenv("TF_VAR_shadow", "env")

args := []string{
"-state", statePath,
"-no-color",
planPath,
}
code := c.Run(args)
output := done(t)
if code != 0 {
t.Fatal("unexpected failure: ", output.All())
}

expectedWarn := "Warning: Ignoring variable when applying a saved plan\n"
if !strings.Contains(output.Stdout(), expectedWarn) {
t.Fatalf("expected warning in output, given: %q", output.Stdout())
}
}

// A saved plan includes a list of "apply-time variables", i.e. ephemeral
// input variables that were set during the plan, and must therefore be set
// during apply. No other variables may be set during apply.
Expand Down
7 changes: 7 additions & 0 deletions internal/command/testdata/apply-output-only/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
variable "shadow" {
type = string
}

output "foo" {
value = var.shadow
}

0 comments on commit 70a1fd5

Please sign in to comment.