This commit is contained in:
Christian Mesh 2025-02-22 19:52:38 +00:00 committed by GitHub
commit 6506e085d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 69 additions and 65 deletions

View File

@ -12,7 +12,6 @@ import (
"sort"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/backend"
@ -265,31 +264,7 @@ func (b *Local) localRunForPlanFile(op *backend.Operation, pf *planfile.Reader,
// we need to apply the plan.
run.Plan = plan
subCall := op.RootCall.WithVariables(func(variable *configs.Variable) (cty.Value, hcl.Diagnostics) {
var diags hcl.Diagnostics
name := variable.Name
v, ok := plan.VariableValues[name]
if !ok {
if variable.Required() {
// This should not happen...
return cty.DynamicVal, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing plan variable " + variable.Name,
})
}
return variable.Default, nil
}
parsed, parsedErr := v.Decode(cty.DynamicPseudoType)
if parsedErr != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: parsedErr.Error(),
})
}
return parsed, diags
})
subCall := op.RootCall.WithVariables(plan.VariableMapper())
loader := configload.NewLoaderFromSnapshot(snap)
config, configDiags := loader.LoadConfig(snap.Modules[""].Dir, subCall)
@ -299,6 +274,37 @@ func (b *Local) localRunForPlanFile(op *backend.Operation, pf *planfile.Reader,
}
run.Config = config
// Check that all provided variables are in the configuration
_, undeclaredDiags := backend.ParseUndeclaredVariableValues(op.Variables, config.Module.Variables)
diags = diags.Append(undeclaredDiags)
// Check that all variables provided match
for varName, varCfg := range config.Module.Variables {
if _, ok := op.Variables[varName]; ok {
// Variable provided via cli/files/env/etc...
inputValue, inputDiags := op.RootCall.Variables()(varCfg)
// Variable provided via the plan
planValue, planDiags := subCall.Variables()(varCfg)
diags = diags.Append(inputDiags).Append(planDiags)
if inputDiags.HasErrors() || planDiags.HasErrors() {
return nil, snap, diags
}
if inputValue.Equals(planValue).False() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Mismatch between input and plan variable value",
// TODO this is not a great error message
fmt.Sprintf("Value %s for %s was provided, but does not match plan's value of %s", inputValue, varName, planValue),
))
}
}
}
if diags.HasErrors() {
return nil, snap, diags
}
// NOTE: We're intentionally comparing the current locks with the
// configuration snapshot, rather than the lock snapshot in the plan file,
// because it's the current locks which dictate our plugin selections

View File

@ -88,17 +88,6 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
return 1
}
// Check for invalid combination of plan file and variable overrides
if planFile != nil && !args.Vars.Empty() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Can't set variables when applying a saved plan",
"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.",
))
view.Diagnostics(diags)
return 1
}
// FIXME: the -input flag value is needed to initialize the backend and the
// operation, but there is no clear path to pass this value down, so we
// continue to mutate the Meta object state for now.

View File

@ -12,9 +12,6 @@ import (
"os"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/cloud"
"github.com/opentofu/opentofu/internal/cloud/cloudplan"
@ -371,31 +368,7 @@ func getDataFromPlanfileReader(planReader *planfile.Reader, rootCall configs.Sta
return nil, nil, nil, err
}
subCall := rootCall.WithVariables(func(variable *configs.Variable) (cty.Value, hcl.Diagnostics) {
var diags hcl.Diagnostics
name := variable.Name
v, ok := plan.VariableValues[name]
if !ok {
if variable.Required() {
// This should not happen...
return cty.DynamicVal, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing plan variable " + variable.Name,
})
}
return variable.Default, nil
}
parsed, parsedErr := v.Decode(cty.DynamicPseudoType)
if parsedErr != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: parsedErr.Error(),
})
}
return parsed, diags
})
subCall := rootCall.WithVariables(plan.VariableMapper())
// Get config
config, diags := planReader.ReadConfig(subCall)

View File

@ -51,6 +51,10 @@ func NewStaticModuleCall(addr addrs.Module, vars StaticModuleVariables, rootPath
}
}
func (s StaticModuleCall) Variables() StaticModuleVariables {
return s.vars
}
func (s StaticModuleCall) WithVariables(vars StaticModuleVariables) StaticModuleCall {
return StaticModuleCall{
addr: s.addr,

View File

@ -9,9 +9,11 @@ import (
"sort"
"time"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/lang/globalref"
"github.com/opentofu/opentofu/internal/states"
@ -198,6 +200,36 @@ func (p *Plan) ProviderAddrs() []addrs.AbsProviderConfig {
return ret
}
// Variable mapper checks that all of the provided variables match what has been provided in the plan
// They may be sourced from the environment, from cli args, and autoloaded tfvars files
func (plan *Plan) VariableMapper() configs.StaticModuleVariables {
return func(variable *configs.Variable) (cty.Value, hcl.Diagnostics) {
var diags hcl.Diagnostics
name := variable.Name
v, ok := plan.VariableValues[name]
if !ok {
if variable.Required() {
// This should not happen...
return cty.DynamicVal, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing plan variable " + variable.Name,
})
}
return variable.Default, nil
}
parsed, parsedErr := v.Decode(cty.DynamicPseudoType)
if parsedErr != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: parsedErr.Error(),
})
}
return parsed, diags
}
}
// Backend represents the backend-related configuration and other data as it
// existed when a plan was created.
type Backend struct {