github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_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 11 "github.com/zclconf/go-cty/cty" 12 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/tfdiags" 15 16 "github.com/hashicorp/hcl/v2" 17 18 "github.com/opentofu/opentofu/internal/configs" 19 ) 20 21 // ModuleVariableTransformer is a GraphTransformer that adds all the variables 22 // in the configuration to the graph. 23 // 24 // Any "variable" block present in any non-root module is included here, even 25 // if a particular variable is not referenced from anywhere. 26 // 27 // The transform will produce errors if a call to a module does not conform 28 // to the expected set of arguments, but this transformer is not in a good 29 // position to return errors and so the validate walk should include specific 30 // steps for validating module blocks, separate from this transform. 31 type ModuleVariableTransformer struct { 32 Config *configs.Config 33 } 34 35 func (t *ModuleVariableTransformer) Transform(g *Graph) error { 36 return t.transform(g, nil, t.Config) 37 } 38 39 func (t *ModuleVariableTransformer) transform(g *Graph, parent, c *configs.Config) error { 40 // We can have no variables if we have no configuration. 41 if c == nil { 42 return nil 43 } 44 45 // Transform all the children first. 46 for _, cc := range c.Children { 47 if err := t.transform(g, c, cc); err != nil { 48 return err 49 } 50 } 51 52 // If we're processing anything other than the root module then we'll 53 // add graph nodes for variables defined inside. (Variables for the root 54 // module are dealt with in RootVariableTransformer). 55 // If we have a parent, we can determine if a module variable is being 56 // used, so we transform this. 57 if parent != nil { 58 if err := t.transformSingle(g, parent, c); err != nil { 59 return err 60 } 61 } 62 63 return nil 64 } 65 66 func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs.Config) error { 67 _, call := c.Path.Call() 68 69 // Find the call in the parent module configuration, so we can get the 70 // expressions given for each input variable at the call site. 71 callConfig, exists := parent.Module.ModuleCalls[call.Name] 72 if !exists { 73 // This should never happen, since it indicates an improperly-constructed 74 // configuration tree. 75 panic(fmt.Errorf("no module call block found for %s", c.Path)) 76 } 77 78 // We need to construct a schema for the expected call arguments based on 79 // the configured variables in our config, which we can then use to 80 // decode the content of the call block. 81 schema := &hcl.BodySchema{} 82 for _, v := range c.Module.Variables { 83 schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{ 84 Name: v.Name, 85 Required: v.Default == cty.NilVal, 86 }) 87 } 88 89 content, contentDiags := callConfig.Config.Content(schema) 90 if contentDiags.HasErrors() { 91 // Validation code elsewhere should deal with any errors before we 92 // get in here, but we'll report them out here just in case, to 93 // avoid crashes. 94 var diags tfdiags.Diagnostics 95 diags = diags.Append(contentDiags) 96 return diags.Err() 97 } 98 99 for _, v := range c.Module.Variables { 100 var expr hcl.Expression 101 if attr := content.Attributes[v.Name]; attr != nil { 102 expr = attr.Expr 103 } 104 105 // Add a plannable node, as the variable may expand 106 // during module expansion 107 node := &nodeExpandModuleVariable{ 108 Addr: addrs.InputVariable{ 109 Name: v.Name, 110 }, 111 Module: c.Path, 112 Config: v, 113 Expr: expr, 114 } 115 g.Add(node) 116 } 117 118 return nil 119 }