Skip to content

Commit 9dd16a7

Browse files
command: "terraform apply" accepts variable values with saved plan
To support ephemeral values we need a more complicated set of rules about what input variables can and must be set when applying a saved plan. The command itself does not have enough information to implement those rules itself, so we'll let them pass through and check this in the local backend's apply phase instead. The local backend's apply phase already had basic support for dealing with apply-time variable values, but it'll now also be responsible for rejecting attempts to set variables when the experiment isn't enabled, to keep all of this logic in roughly the same place.
1 parent 598e67a commit 9dd16a7

3 files changed

Lines changed: 38 additions & 11 deletions

File tree

internal/backend/local/backend_apply.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,34 @@ func (b *Local) opApply(
258258
applyOpts = &terraform.ApplyOpts{
259259
SetVariables: applyTimeValues,
260260
}
261+
} else {
262+
// When the ephemeral values experiment isn't enabled, no variables
263+
// may be _explicitly_ set during the apply phase at all, but it's
264+
// valid for variable to show up from more implicit locations like
265+
// environment variables and .auto.tfvars files.
266+
if len(op.Variables) != 0 && !combinedPlanApply {
267+
for _, rawV := range op.Variables {
268+
// We're "parsing" only to get the resulting value's SourceType,
269+
// so we'll use configs.VariableParseLiteral just because it's
270+
// the most liberal interpretation and so least likely to
271+
// fail with an unrelated error.
272+
v, _ := rawV.ParseVariableValue(configs.VariableParseLiteral)
273+
if v == nil {
274+
// We'll ignore any that don't parse at all, because
275+
// they'll fail elsewhere in this process anyway.
276+
continue
277+
}
278+
if v.SourceType == terraform.ValueFromCLIArg || v.SourceType == terraform.ValueFromNamedFile {
279+
diags = diags.Append(tfdiags.Sourceless(
280+
tfdiags.Error,
281+
"Can't set variables when applying a saved plan",
282+
"The -var and -var-file options cannot be used when applying a saved plan file, because a saved plan includes the variable values that were set when it was created.",
283+
))
284+
op.ReportResult(runningOp, diags)
285+
return
286+
}
287+
}
288+
}
261289
}
262290

263291
// Start the apply in a goroutine so that we can be interrupted.

internal/command/apply.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,6 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
7171
return 1
7272
}
7373

74-
// Check for invalid combination of plan file and variable overrides
75-
if planFile != nil && !args.Vars.Empty() {
76-
diags = diags.Append(tfdiags.Sourceless(
77-
tfdiags.Error,
78-
"Can't set variables when applying a saved plan",
79-
"The -var and -var-file options cannot be used when applying a saved plan file, because a saved plan includes the variable values that were set when it was created.",
80-
))
81-
view.Diagnostics(diags)
82-
return 1
83-
}
84-
8574
// FIXME: the -input flag value is needed to initialize the backend and the
8675
// operation, but there is no clear path to pass this value down, so we
8776
// continue to mutate the Meta object state for now.

internal/command/apply_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,16 @@ func TestApply_planWithVarFile(t *testing.T) {
900900
}
901901

902902
func TestApply_planVars(t *testing.T) {
903+
// This test ensures that it isn't allowed to set input variables
904+
// when applying from a saved plan file, since in that case the
905+
// variable values come from the saved plan file.
906+
//
907+
// This situation was originally checked by the apply command itself,
908+
// and that's what this test was originally exercising. This rule
909+
// is now enforced by the "local" backend instead, but this test
910+
// is still valid since the command instance delegates to the
911+
// local backend.
912+
903913
planPath := applyFixturePlanFile(t)
904914
statePath := testTempFile(t)
905915

0 commit comments

Comments
 (0)