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