eval variables with unknown expansion data

While we don't have any expansion info during validation, we can try to
evaluate variable expressions to catch some basic errors. Do this by
creating module instance RepetitionData with unknown values. This
unfortunately will still miss the incorrect usage of count/each values,
but that would require the module call's each mode, which is not
available at this time.
This commit is contained in:
James Bardin 2020-04-07 19:30:18 -04:00
parent c59ecac870
commit d060a3d0e8
5 changed files with 38 additions and 27 deletions

View File

@ -1435,7 +1435,7 @@ resource "aws_instance" "foo" {
module "nested" { module "nested" {
count = 2 count = 2
source = "./nested" source = "./nested"
input = 2 input = count.index
} }
`, `,
"mod/nested/main.tf": ` "mod/nested/main.tf": `

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/instances"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/convert"
@ -42,12 +43,12 @@ type EvalModuleCallArgument struct {
Expr hcl.Expression Expr hcl.Expression
ModuleInstance addrs.ModuleInstance ModuleInstance addrs.ModuleInstance
// If this flag is set, any diagnostics are discarded and this operation
// will always succeed, though may produce an unknown value in the
// event of an error.
IgnoreDiagnostics bool
Values map[string]cty.Value Values map[string]cty.Value
// validateOnly indicates that this evaluation is only for config
// validation, and we will not have any expansion module instance
// repetition data.
validateOnly bool
} }
func (n *EvalModuleCallArgument) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalModuleCallArgument) Eval(ctx EvalContext) (interface{}, error) {
@ -69,9 +70,24 @@ func (n *EvalModuleCallArgument) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil return nil, nil
} }
// Get the repetition data for this module instance, var moduleInstanceRepetitionData instances.RepetitionData
// so we can create the appropriate scope for evaluating our expression
moduleInstanceRepetitionData := ctx.InstanceExpander().GetModuleInstanceRepetitionData(n.ModuleInstance) switch {
case n.validateOnly:
// the instance expander does not track unknown expansion values, so we
// have to assume all RepetitionData is unknown.
moduleInstanceRepetitionData = instances.RepetitionData{
CountIndex: cty.UnknownVal(cty.Number),
EachKey: cty.UnknownVal(cty.String),
EachValue: cty.DynamicVal,
}
default:
// Get the repetition data for this module instance,
// so we can create the appropriate scope for evaluating our expression
moduleInstanceRepetitionData = ctx.InstanceExpander().GetModuleInstanceRepetitionData(n.ModuleInstance)
}
scope := ctx.EvaluationScope(nil, moduleInstanceRepetitionData) scope := ctx.EvaluationScope(nil, moduleInstanceRepetitionData)
val, diags := scope.EvalExpr(expr, cty.DynamicPseudoType) val, diags := scope.EvalExpr(expr, cty.DynamicPseudoType)
@ -96,9 +112,6 @@ func (n *EvalModuleCallArgument) Eval(ctx EvalContext) (interface{}, error) {
} }
n.Values[name] = val n.Values[name] = val
if n.IgnoreDiagnostics {
return nil, nil
}
return nil, diags.ErrWithWarnings() return nil, diags.ErrWithWarnings()
} }
@ -116,15 +129,10 @@ type evalVariableValidations struct {
// This will be nil for root module variables, because their values come // This will be nil for root module variables, because their values come
// from outside the configuration. // from outside the configuration.
Expr hcl.Expression Expr hcl.Expression
// If this flag is set, this node becomes a no-op.
// This is here for consistency with EvalModuleCallArgument so that it
// can be populated with the same value, where needed.
IgnoreDiagnostics bool
} }
func (n *evalVariableValidations) Eval(ctx EvalContext) (interface{}, error) { func (n *evalVariableValidations) Eval(ctx EvalContext) (interface{}, error) {
if n.Config == nil || n.IgnoreDiagnostics || len(n.Config.Validations) == 0 { if n.Config == nil || len(n.Config.Validations) == 0 {
log.Printf("[TRACE] evalVariableValidations: not active for %s, so skipping", n.Addr) log.Printf("[TRACE] evalVariableValidations: not active for %s, so skipping", n.Addr)
return nil, nil return nil, nil
} }

View File

@ -11,8 +11,6 @@ import (
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
) )
type ConcreteVariableNodeFunc func(n *nodeExpandModuleVariable) dag.Vertex
// nodeExpandModuleVariable is the placeholder for an variable that has not yet had // nodeExpandModuleVariable is the placeholder for an variable that has not yet had
// its module path expanded. // its module path expanded.
type nodeExpandModuleVariable struct { type nodeExpandModuleVariable struct {
@ -176,15 +174,25 @@ func (n *nodeModuleVariable) EvalTree() EvalNode {
Nodes: []EvalNode{ Nodes: []EvalNode{
&EvalOpFilter{ &EvalOpFilter{
Ops: []walkOperation{walkRefresh, walkPlan, walkApply, Ops: []walkOperation{walkRefresh, walkPlan, walkApply,
walkDestroy, walkValidate}, walkDestroy},
Node: &EvalModuleCallArgument{ Node: &EvalModuleCallArgument{
Addr: n.Addr.Variable, Addr: n.Addr.Variable,
Config: n.Config, Config: n.Config,
Expr: n.Expr, Expr: n.Expr,
ModuleInstance: n.ModuleInstance, ModuleInstance: n.ModuleInstance,
Values: vals, Values: vals,
},
},
IgnoreDiagnostics: false, &EvalOpFilter{
Ops: []walkOperation{walkValidate},
Node: &EvalModuleCallArgument{
Addr: n.Addr.Variable,
Config: n.Config,
Expr: n.Expr,
ModuleInstance: n.ModuleInstance,
Values: vals,
validateOnly: true,
}, },
}, },
@ -197,8 +205,6 @@ func (n *nodeModuleVariable) EvalTree() EvalNode {
Addr: n.Addr, Addr: n.Addr,
Config: n.Config, Config: n.Config,
Expr: n.Expr, Expr: n.Expr,
IgnoreDiagnostics: false,
}, },
}, },
} }

View File

@ -50,8 +50,6 @@ func (n *NodeRootVariable) EvalTree() EvalNode {
Addr: addrs.RootModuleInstance.InputVariable(n.Addr.Name), Addr: addrs.RootModuleInstance.InputVariable(n.Addr.Name),
Config: n.Config, Config: n.Config,
Expr: nil, // not set for root module variables Expr: nil, // not set for root module variables
IgnoreDiagnostics: false,
} }
} }

View File

@ -113,7 +113,6 @@ func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs
Config: v, Config: v,
Expr: expr, Expr: expr,
} }
g.Add(node) g.Add(node)
} }