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" {
count = 2
source = "./nested"
input = 2
input = count.index
}
`,
"mod/nested/main.tf": `

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/instances"
"github.com/hashicorp/terraform/tfdiags"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
@ -42,12 +43,12 @@ type EvalModuleCallArgument struct {
Expr hcl.Expression
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
// 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) {
@ -69,9 +70,24 @@ func (n *EvalModuleCallArgument) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil
}
// 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)
var moduleInstanceRepetitionData instances.RepetitionData
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)
val, diags := scope.EvalExpr(expr, cty.DynamicPseudoType)
@ -96,9 +112,6 @@ func (n *EvalModuleCallArgument) Eval(ctx EvalContext) (interface{}, error) {
}
n.Values[name] = val
if n.IgnoreDiagnostics {
return nil, nil
}
return nil, diags.ErrWithWarnings()
}
@ -116,15 +129,10 @@ type evalVariableValidations struct {
// This will be nil for root module variables, because their values come
// from outside the configuration.
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) {
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)
return nil, nil
}

View File

@ -11,8 +11,6 @@ import (
"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
// its module path expanded.
type nodeExpandModuleVariable struct {
@ -176,15 +174,25 @@ func (n *nodeModuleVariable) EvalTree() EvalNode {
Nodes: []EvalNode{
&EvalOpFilter{
Ops: []walkOperation{walkRefresh, walkPlan, walkApply,
walkDestroy, walkValidate},
walkDestroy},
Node: &EvalModuleCallArgument{
Addr: n.Addr.Variable,
Config: n.Config,
Expr: n.Expr,
ModuleInstance: n.ModuleInstance,
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,
Config: n.Config,
Expr: n.Expr,
IgnoreDiagnostics: false,
},
},
}

View File

@ -50,8 +50,6 @@ func (n *NodeRootVariable) EvalTree() EvalNode {
Addr: addrs.RootModuleInstance.InputVariable(n.Addr.Name),
Config: n.Config,
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,
Expr: expr,
}
g.Add(node)
}