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