github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/terraform/node_module_variable.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/cycloidio/terraform/addrs" 9 "github.com/cycloidio/terraform/configs" 10 "github.com/cycloidio/terraform/dag" 11 "github.com/cycloidio/terraform/instances" 12 "github.com/cycloidio/terraform/lang" 13 "github.com/cycloidio/terraform/tfdiags" 14 "github.com/zclconf/go-cty/cty" 15 "github.com/zclconf/go-cty/cty/convert" 16 ) 17 18 // nodeExpandModuleVariable is the placeholder for an variable that has not yet had 19 // its module path expanded. 20 type nodeExpandModuleVariable struct { 21 Addr addrs.InputVariable 22 Module addrs.Module 23 Config *configs.Variable 24 Expr hcl.Expression 25 } 26 27 var ( 28 _ GraphNodeDynamicExpandable = (*nodeExpandModuleVariable)(nil) 29 _ GraphNodeReferenceOutside = (*nodeExpandModuleVariable)(nil) 30 _ GraphNodeReferenceable = (*nodeExpandModuleVariable)(nil) 31 _ GraphNodeReferencer = (*nodeExpandModuleVariable)(nil) 32 _ graphNodeTemporaryValue = (*nodeExpandModuleVariable)(nil) 33 _ graphNodeExpandsInstances = (*nodeExpandModuleVariable)(nil) 34 ) 35 36 func (n *nodeExpandModuleVariable) expandsInstances() {} 37 38 func (n *nodeExpandModuleVariable) temporaryValue() bool { 39 return true 40 } 41 42 func (n *nodeExpandModuleVariable) DynamicExpand(ctx EvalContext) (*Graph, error) { 43 var g Graph 44 expander := ctx.InstanceExpander() 45 for _, module := range expander.ExpandModule(n.Module) { 46 o := &nodeModuleVariable{ 47 Addr: n.Addr.Absolute(module), 48 Config: n.Config, 49 Expr: n.Expr, 50 ModuleInstance: module, 51 } 52 g.Add(o) 53 } 54 return &g, nil 55 } 56 57 func (n *nodeExpandModuleVariable) Name() string { 58 return fmt.Sprintf("%s.%s (expand)", n.Module, n.Addr.String()) 59 } 60 61 // GraphNodeModulePath 62 func (n *nodeExpandModuleVariable) ModulePath() addrs.Module { 63 return n.Module 64 } 65 66 // GraphNodeReferencer 67 func (n *nodeExpandModuleVariable) References() []*addrs.Reference { 68 69 // If we have no value expression, we cannot depend on anything. 70 if n.Expr == nil { 71 return nil 72 } 73 74 // Variables in the root don't depend on anything, because their values 75 // are gathered prior to the graph walk and recorded in the context. 76 if len(n.Module) == 0 { 77 return nil 78 } 79 80 // Otherwise, we depend on anything referenced by our value expression. 81 // We ignore diagnostics here under the assumption that we'll re-eval 82 // all these things later and catch them then; for our purposes here, 83 // we only care about valid references. 84 // 85 // Due to our GraphNodeReferenceOutside implementation, the addresses 86 // returned by this function are interpreted in the _parent_ module from 87 // where our associated variable was declared, which is correct because 88 // our value expression is assigned within a "module" block in the parent 89 // module. 90 refs, _ := lang.ReferencesInExpr(n.Expr) 91 return refs 92 } 93 94 // GraphNodeReferenceOutside implementation 95 func (n *nodeExpandModuleVariable) ReferenceOutside() (selfPath, referencePath addrs.Module) { 96 return n.Module, n.Module.Parent() 97 } 98 99 // GraphNodeReferenceable 100 func (n *nodeExpandModuleVariable) ReferenceableAddrs() []addrs.Referenceable { 101 return []addrs.Referenceable{n.Addr} 102 } 103 104 // nodeModuleVariable represents a module variable input during 105 // the apply step. 106 type nodeModuleVariable struct { 107 Addr addrs.AbsInputVariableInstance 108 Config *configs.Variable // Config is the var in the config 109 Expr hcl.Expression // Expr is the value expression given in the call 110 // ModuleInstance in order to create the appropriate context for evaluating 111 // ModuleCallArguments, ex. so count.index and each.key can resolve 112 ModuleInstance addrs.ModuleInstance 113 } 114 115 // Ensure that we are implementing all of the interfaces we think we are 116 // implementing. 117 var ( 118 _ GraphNodeModuleInstance = (*nodeModuleVariable)(nil) 119 _ GraphNodeExecutable = (*nodeModuleVariable)(nil) 120 _ graphNodeTemporaryValue = (*nodeModuleVariable)(nil) 121 _ dag.GraphNodeDotter = (*nodeModuleVariable)(nil) 122 ) 123 124 func (n *nodeModuleVariable) temporaryValue() bool { 125 return true 126 } 127 128 func (n *nodeModuleVariable) Name() string { 129 return n.Addr.String() 130 } 131 132 // GraphNodeModuleInstance 133 func (n *nodeModuleVariable) Path() addrs.ModuleInstance { 134 // We execute in the parent scope (above our own module) because 135 // expressions in our value are resolved in that context. 136 return n.Addr.Module.Parent() 137 } 138 139 // GraphNodeModulePath 140 func (n *nodeModuleVariable) ModulePath() addrs.Module { 141 return n.Addr.Module.Module() 142 } 143 144 // GraphNodeExecutable 145 func (n *nodeModuleVariable) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { 146 // If we have no value, do nothing 147 if n.Expr == nil { 148 return nil 149 } 150 151 // Otherwise, interpolate the value of this variable and set it 152 // within the variables mapping. 153 var vals map[string]cty.Value 154 var err error 155 156 switch op { 157 case walkValidate: 158 vals, err = n.evalModuleCallArgument(ctx, true) 159 diags = diags.Append(err) 160 if diags.HasErrors() { 161 return diags 162 } 163 default: 164 vals, err = n.evalModuleCallArgument(ctx, false) 165 diags = diags.Append(err) 166 if diags.HasErrors() { 167 return diags 168 } 169 } 170 171 // Set values for arguments of a child module call, for later retrieval 172 // during expression evaluation. 173 _, call := n.Addr.Module.CallInstance() 174 ctx.SetModuleCallArguments(call, vals) 175 176 return evalVariableValidations(n.Addr, n.Config, n.Expr, ctx) 177 } 178 179 // dag.GraphNodeDotter impl. 180 func (n *nodeModuleVariable) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { 181 return &dag.DotNode{ 182 Name: name, 183 Attrs: map[string]string{ 184 "label": n.Name(), 185 "shape": "note", 186 }, 187 } 188 } 189 190 // evalModuleCallArgument produces the value for a particular variable as will 191 // be used by a child module instance. 192 // 193 // The result is written into a map, with its key set to the local name of the 194 // variable, disregarding the module instance address. A map is returned instead 195 // of a single value as a result of trying to be convenient for use with 196 // EvalContext.SetModuleCallArguments, which expects a map to merge in with any 197 // existing arguments. 198 // 199 // validateOnly indicates that this evaluation is only for config 200 // validation, and we will not have any expansion module instance 201 // repetition data. 202 func (n *nodeModuleVariable) evalModuleCallArgument(ctx EvalContext, validateOnly bool) (map[string]cty.Value, error) { 203 name := n.Addr.Variable.Name 204 expr := n.Expr 205 206 if expr == nil { 207 // Should never happen, but we'll bail out early here rather than 208 // crash in case it does. We set no value at all in this case, 209 // making a subsequent call to EvalContext.SetModuleCallArguments 210 // a no-op. 211 log.Printf("[ERROR] attempt to evaluate %s with nil expression", n.Addr.String()) 212 return nil, nil 213 } 214 215 var moduleInstanceRepetitionData instances.RepetitionData 216 217 switch { 218 case validateOnly: 219 // the instance expander does not track unknown expansion values, so we 220 // have to assume all RepetitionData is unknown. 221 moduleInstanceRepetitionData = instances.RepetitionData{ 222 CountIndex: cty.UnknownVal(cty.Number), 223 EachKey: cty.UnknownVal(cty.String), 224 EachValue: cty.DynamicVal, 225 } 226 227 default: 228 // Get the repetition data for this module instance, 229 // so we can create the appropriate scope for evaluating our expression 230 moduleInstanceRepetitionData = ctx.InstanceExpander().GetModuleInstanceRepetitionData(n.ModuleInstance) 231 } 232 233 scope := ctx.EvaluationScope(nil, moduleInstanceRepetitionData) 234 val, diags := scope.EvalExpr(expr, cty.DynamicPseudoType) 235 236 // FIXME: This check is only necessary for v1.1, and is will never be 237 // present in the v1.2 branch. 238 // 239 // Default values are currently being handled in two places, the graph 240 // transformation where a synthetic default expression is created if there 241 // was no input expression, and in the evaluator when a reference to the 242 // variable is evaluated. Unfortunately neither of these covers the case 243 // where a non-nullable variable default needs to be checked within a 244 // validation statement 245 // 246 // Rather than try and fix the overall variable handling here, which runs 247 // the risk of encountering more unexpected behavior, we are going to fixup 248 // this one case to ensure a null value doesn't continue to slip into 249 // validation. A more extensive refactoring of variable handling has been 250 // completed in v1.2, and this code will only be relevant for the v1.1 251 // branch. 252 if !diags.HasErrors() && val.IsNull() && 253 !n.Config.Nullable && 254 n.Config.Default != cty.NilVal && !n.Config.Default.IsNull() { 255 // replace the evaluated value with the actual default 256 val = n.Config.Default 257 } 258 259 // We intentionally passed DynamicPseudoType to EvalExpr above because 260 // now we can do our own local type conversion and produce an error message 261 // with better context if it fails. 262 var convErr error 263 val, convErr = convert.Convert(val, n.Config.ConstraintType) 264 if convErr != nil { 265 diags = diags.Append(&hcl.Diagnostic{ 266 Severity: hcl.DiagError, 267 Summary: "Invalid value for module argument", 268 Detail: fmt.Sprintf( 269 "The given value is not suitable for child module variable %q defined at %s: %s.", 270 name, n.Config.DeclRange.String(), convErr, 271 ), 272 Subject: expr.Range().Ptr(), 273 }) 274 // We'll return a placeholder unknown value to avoid producing 275 // redundant downstream errors. 276 val = cty.UnknownVal(n.Config.Type) 277 } 278 279 // If there is no default, we have to ensure that a null value is allowed 280 // for this variable. 281 if n.Config.Default == cty.NilVal && !n.Config.Nullable && val.IsNull() { 282 // The value cannot be null, and there is no configured default. 283 diags = diags.Append(&hcl.Diagnostic{ 284 Severity: hcl.DiagError, 285 Summary: `Invalid variable value`, 286 Detail: fmt.Sprintf(`The variable %q is required, but the given value is null.`, n.Addr), 287 Subject: &n.Config.DeclRange, 288 }) 289 // Stub out our return value so that the semantic checker doesn't 290 // produce redundant downstream errors. 291 val = cty.UnknownVal(n.Config.Type) 292 } 293 294 vals := make(map[string]cty.Value) 295 vals[name] = val 296 297 return vals, diags.ErrWithWarnings() 298 }