Skip to content

Commit

Permalink
allow environment variables to fill in ephemeral values
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielMSchmidt committed Nov 4, 2024
1 parent 7b75360 commit ada3bf6
Showing 1 changed file with 75 additions and 73 deletions.
148 changes: 75 additions & 73 deletions internal/backend/local/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,16 +247,19 @@ func (b *Local) opApply(
continue
}

if v.SourceType == terraform.ValueFromCLIArg || v.SourceType == terraform.ValueFromNamedFile {
var rng *hcl.Range
if v.HasSourceRange() {
rng = v.SourceRange.ToHCL().Ptr()
}
var rng *hcl.Range
if v.HasSourceRange() {
rng = v.SourceRange.ToHCL().Ptr()
}

// If the variable isn't declared in config at all, take
// this opportunity to give the user a helpful error,
// rather than waiting for a less helpful one later.
decl, ok := lr.Config.Module.Variables[varName]
decl, ok := lr.Config.Module.Variables[varName]

// If the variable isn't declared in config at all, take
// this opportunity to give the user a helpful error,
// rather than waiting for a less helpful one later.
// We are ok with over-supplying variables through environment variables
// since it would be a breaking change to disallow it.
if v.SourceType == terraform.ValueFromCLIArg || v.SourceType == terraform.ValueFromNamedFile {
if !ok {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Expand All @@ -266,80 +269,79 @@ func (b *Local) opApply(
})
continue
}
}

// If the var is declared as ephemeral in config, go ahead and handle it
if decl.Ephemeral {
// Determine whether this is an apply-time variable, i.e. an
// ephemeral variable that was set (non-null) during the
// planning phase.
applyTimeVar := false
for avName := range plan.ApplyTimeVariables.All() {
if varName == avName {
applyTimeVar = true
}
}

// If this isn't an apply-time variable, it's not valid to
// set it during apply.
if !applyTimeVar {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Ephemeral variable was not set during planning",
Detail: fmt.Sprintf(
"The ephemeral input variable %q was not set during the planning phase, and so must remain unset during the apply phase.",
varName,
),
Subject: rng,
})
continue
}

// Get the value of the variable, because we'll need it for
// the next two steps.
val, valDiags := rawV.ParseVariableValue(decl.ParsingMode)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
continue
// If the var is declared as ephemeral in config, go ahead and handle it
if decl.Ephemeral {
// Determine whether this is an apply-time variable, i.e. an
// ephemeral variable that was set (non-null) during the
// planning phase.
applyTimeVar := false
for avName := range plan.ApplyTimeVariables.All() {
if varName == avName {
applyTimeVar = true
}
}

// If this is an apply-time variable, the user must supply a
// value during apply: it can't be null.
if applyTimeVar && val.Value.IsNull() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Ephemeral variable must be set for apply",
Detail: fmt.Sprintf(
"The ephemeral input variable %q was set during the planning phase, and so must be set again during the apply phase.",
varName,
),
})
continue
}
// If this isn't an apply-time variable, it's not valid to
// set it during apply.
if !applyTimeVar {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Ephemeral variable was not set during planning",
Detail: fmt.Sprintf(
"The ephemeral input variable %q was not set during the planning phase, and so must remain unset during the apply phase.",
varName,
),
Subject: rng,
})
continue
}

// If we get here, we are in possession of a non-null
// ephemeral apply-time input variable, and need only pass
// its value on to the ApplyOpts.
applyTimeValues[varName] = val
} else {
// TODO: We should probably actually tolerate this if the new
// value is equal to the value that was saved in the plan, since
// that'd make it possible to, for example, reuse a .tfvars file
// containing a mixture of ephemeral and non-ephemeral definitions
// during the apply phase, rather than having to split ephemeral
// and non-ephemeral definitions into separate files. For initial
// experiment we'll keep things a little simpler, though, and
// just skip this check if we're doing a combined plan/apply where
// the apply phase will therefore always have exactly the same
// inputs as the plan phase.
// Get the value of the variable, because we'll need it for
// the next two steps.
val, valDiags := rawV.ParseVariableValue(decl.ParsingMode)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
continue
}

// If this is an apply-time variable, the user must supply a
// value during apply: it can't be null.
if applyTimeVar && val.Value.IsNull() {
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,
Summary: "Ephemeral variable must be set for apply",
Detail: fmt.Sprintf(
"The ephemeral input variable %q was set during the planning phase, and so must be set again during the apply phase.",
varName,
),
})
continue
}

// If we get here, we are in possession of a non-null
// ephemeral apply-time input variable, and need only pass
// its value on to the ApplyOpts.
applyTimeValues[varName] = val
} else {
// TODO: We should probably actually tolerate this if the new
// value is equal to the value that was saved in the plan, since
// that'd make it possible to, for example, reuse a .tfvars file
// containing a mixture of ephemeral and non-ephemeral definitions
// during the apply phase, rather than having to split ephemeral
// and non-ephemeral definitions into separate files. For initial
// experiment we'll keep things a little simpler, though, and
// just skip this check if we're doing a combined plan/apply where
// the apply phase will therefore always have exactly the same
// inputs as the plan phase.

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,
})
}
}
applyOpts = &terraform.ApplyOpts{
Expand Down

0 comments on commit ada3bf6

Please sign in to comment.