github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/node_module_variable.go (about) 1 package durgaform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/eliastor/durgaform/internal/addrs" 9 "github.com/eliastor/durgaform/internal/configs" 10 "github.com/eliastor/durgaform/internal/dag" 11 "github.com/eliastor/durgaform/internal/instances" 12 "github.com/eliastor/durgaform/internal/lang" 13 "github.com/eliastor/durgaform/internal/tfdiags" 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 // nodeExpandModuleVariable is the placeholder for an variable that has not yet had 18 // its module path expanded. 19 type nodeExpandModuleVariable struct { 20 Addr addrs.InputVariable 21 Module addrs.Module 22 Config *configs.Variable 23 Expr hcl.Expression 24 } 25 26 var ( 27 _ GraphNodeDynamicExpandable = (*nodeExpandModuleVariable)(nil) 28 _ GraphNodeReferenceOutside = (*nodeExpandModuleVariable)(nil) 29 _ GraphNodeReferenceable = (*nodeExpandModuleVariable)(nil) 30 _ GraphNodeReferencer = (*nodeExpandModuleVariable)(nil) 31 _ graphNodeTemporaryValue = (*nodeExpandModuleVariable)(nil) 32 _ graphNodeExpandsInstances = (*nodeExpandModuleVariable)(nil) 33 ) 34 35 func (n *nodeExpandModuleVariable) expandsInstances() {} 36 37 func (n *nodeExpandModuleVariable) temporaryValue() bool { 38 return true 39 } 40 41 func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, error) { 42 var g Graph 43 expander := ctx.InstanceExpander() 44 for _, module := range expander.ExpandModule(n.Module) { 45 o := &nodeModuleVariable{ 46 Addr: n.Addr.Absolute(module), 47 Config: n.Config, 48 Expr: n.Expr, 49 ModuleInstance: module, 50 } 51 g.Add(o) 52 } 53 return &g, nil 54 } 55 56 func (n *nodeExpandModuleVariable) Name() string { 57 return fmt.Sprintf("%s.%s (expand)", n.Module, n.Addr.String()) 58 } 59 60 // GraphNodeModulePath 61 func (n *nodeExpandModuleVariable) ModulePath() addrs.Module { 62 return n.Module 63 } 64 65 // GraphNodeReferencer 66 func (n *nodeExpandModuleVariable) References() []*addrs.Reference { 67 68 // If we have no value expression, we cannot depend on anything. 69 if n.Expr == nil { 70 return nil 71 } 72 73 // Variables in the root don't depend on anything, because their values 74 // are gathered prior to the graph walk and recorded in the context. 75 if len(n.Module) == 0 { 76 return nil 77 } 78 79 // Otherwise, we depend on anything referenced by our value expression. 80 // We ignore diagnostics here under the assumption that we'll re-eval 81 // all these things later and catch them then; for our purposes here, 82 // we only care about valid references. 83 // 84 // Due to our GraphNodeReferenceOutside implementation, the addresses 85 // returned by this function are interpreted in the _parent_ module from 86 // where our associated variable was declared, which is correct because 87 // our value expression is assigned within a "module" block in the parent 88 // module. 89 refs, _ := lang.ReferencesInExpr(n.Expr) 90 return refs 91 } 92 93 // GraphNodeReferenceOutside implementation 94 func (n *nodeExpandModuleVariable) ReferenceOutside() (selfPath, referencePath addrs.Module) { 95 return n.Module, n.Module.Parent() 96 } 97 98 // GraphNodeReferenceable 99 func (n *nodeExpandModuleVariable) ReferenceableAddrs() []addrs.Referenceable { 100 return []addrs.Referenceable{n.Addr} 101 } 102 103 // nodeModuleVariable represents a module variable input during 104 // the apply step. 105 type nodeModuleVariable struct { 106 Addr addrs.AbsInputVariableInstance 107 Config *configs.Variable // Config is the var in the config 108 Expr hcl.Expression // Expr is the value expression given in the call 109 // ModuleInstance in order to create the appropriate context for evaluating 110 // ModuleCallArguments, ex. so count.index and each.key can resolve 111 ModuleInstance addrs.ModuleInstance 112 } 113 114 // Ensure that we are implementing all of the interfaces we think we are 115 // implementing. 116 var ( 117 _ GraphNodeModuleInstance = (*nodeModuleVariable)(nil) 118 _ GraphNodeExecutable = (*nodeModuleVariable)(nil) 119 _ graphNodeTemporaryValue = (*nodeModuleVariable)(nil) 120 _ dag.GraphNodeDotter = (*nodeModuleVariable)(nil) 121 ) 122 123 func (n *nodeModuleVariable) temporaryValue() bool { 124 return true 125 } 126 127 func (n *nodeModuleVariable) Name() string { 128 return n.Addr.String() 129 } 130 131 // GraphNodeModuleInstance 132 func (n *nodeModuleVariable) Path() addrs.ModuleInstance { 133 // We execute in the parent scope (above our own module) because 134 // expressions in our value are resolved in that context. 135 return n.Addr.Module.Parent() 136 } 137 138 // GraphNodeModulePath 139 func (n *nodeModuleVariable) ModulePath() addrs.Module { 140 return n.Addr.Module.Module() 141 } 142 143 // GraphNodeExecutable 144 func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { 145 log.Printf("[TRACE] nodeModuleVariable: evaluating %s", n.Addr) 146 147 var val cty.Value 148 var err error 149 150 switch op { 151 case walkValidate: 152 val, err = n.evalModuleVariable(ctx, true) 153 diags = diags.Append(err) 154 default: 155 val, err = n.evalModuleVariable(ctx, false) 156 diags = diags.Append(err) 157 } 158 if diags.HasErrors() { 159 return diags 160 } 161 162 // Set values for arguments of a child module call, for later retrieval 163 // during expression evaluation. 164 _, call := n.Addr.Module.CallInstance() 165 ctx.SetModuleCallArgument(call, n.Addr.Variable, val) 166 167 return evalVariableValidations(n.Addr, n.Config, n.Expr, ctx) 168 } 169 170 // dag.GraphNodeDotter impl. 171 func (n *nodeModuleVariable) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { 172 return &dag.DotNode{ 173 Name: name, 174 Attrs: map[string]string{ 175 "label": n.Name(), 176 "shape": "note", 177 }, 178 } 179 } 180 181 // evalModuleVariable produces the value for a particular variable as will 182 // be used by a child module instance. 183 // 184 // The result is written into a map, with its key set to the local name of the 185 // variable, disregarding the module instance address. A map is returned instead 186 // of a single value as a result of trying to be convenient for use with 187 // EvalContext.SetModuleCallArguments, which expects a map to merge in with any 188 // existing arguments. 189 // 190 // validateOnly indicates that this evaluation is only for config 191 // validation, and we will not have any expansion module instance 192 // repetition data. 193 func (n *nodeModuleVariable) evalModuleVariable(ctx EvalContext, validateOnly bool) (cty.Value, error) { 194 var diags tfdiags.Diagnostics 195 var givenVal cty.Value 196 var errSourceRange tfdiags.SourceRange 197 if expr := n.Expr; expr != nil { 198 var moduleInstanceRepetitionData instances.RepetitionData 199 200 switch { 201 case validateOnly: 202 // the instance expander does not track unknown expansion values, so we 203 // have to assume all RepetitionData is unknown. 204 moduleInstanceRepetitionData = instances.RepetitionData{ 205 CountIndex: cty.UnknownVal(cty.Number), 206 EachKey: cty.UnknownVal(cty.String), 207 EachValue: cty.DynamicVal, 208 } 209 210 default: 211 // Get the repetition data for this module instance, 212 // so we can create the appropriate scope for evaluating our expression 213 moduleInstanceRepetitionData = ctx.InstanceExpander().GetModuleInstanceRepetitionData(n.ModuleInstance) 214 } 215 216 scope := ctx.EvaluationScope(nil, moduleInstanceRepetitionData) 217 val, moreDiags := scope.EvalExpr(expr, cty.DynamicPseudoType) 218 diags = diags.Append(moreDiags) 219 if moreDiags.HasErrors() { 220 return cty.DynamicVal, diags.ErrWithWarnings() 221 } 222 givenVal = val 223 errSourceRange = tfdiags.SourceRangeFromHCL(expr.Range()) 224 } else { 225 // We'll use cty.NilVal to represent the variable not being set at all. 226 givenVal = cty.NilVal 227 errSourceRange = tfdiags.SourceRangeFromHCL(n.Config.DeclRange) // we use the declaration range as a fallback for an undefined variable 228 } 229 230 // We construct a synthetic InputValue here to pretend as if this were 231 // a root module variable set from outside, just as a convenience so we 232 // can reuse the InputValue type for this. 233 rawVal := &InputValue{ 234 Value: givenVal, 235 SourceType: ValueFromConfig, 236 SourceRange: errSourceRange, 237 } 238 239 finalVal, moreDiags := prepareFinalInputVariableValue(n.Addr, rawVal, n.Config) 240 diags = diags.Append(moreDiags) 241 242 return finalVal, diags.ErrWithWarnings() 243 }