]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
107c1cdb | 4 | "fmt" |
bae9f6d2 | 5 | |
107c1cdb ND |
6 | "github.com/hashicorp/hcl2/hcl/hclsyntax" |
7 | "github.com/hashicorp/terraform/tfdiags" | |
8 | "github.com/zclconf/go-cty/cty" | |
9 | ||
10 | "github.com/hashicorp/hcl2/hcl" | |
11 | "github.com/hashicorp/terraform/configs" | |
bae9f6d2 JC |
12 | ) |
13 | ||
14 | // ModuleVariableTransformer is a GraphTransformer that adds all the variables | |
15 | // in the configuration to the graph. | |
16 | // | |
107c1cdb ND |
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. | |
bae9f6d2 | 24 | type ModuleVariableTransformer struct { |
107c1cdb | 25 | Config *configs.Config |
bae9f6d2 JC |
26 | } |
27 | ||
28 | func (t *ModuleVariableTransformer) Transform(g *Graph) error { | |
107c1cdb | 29 | return t.transform(g, nil, t.Config) |
bae9f6d2 JC |
30 | } |
31 | ||
107c1cdb ND |
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 { | |
bae9f6d2 JC |
35 | return nil |
36 | } | |
37 | ||
107c1cdb ND |
38 | // Transform all the children first. |
39 | for _, cc := range c.Children { | |
40 | if err := t.transform(g, c, cc); err != nil { | |
bae9f6d2 JC |
41 | return err |
42 | } | |
43 | } | |
44 | ||
107c1cdb ND |
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). | |
bae9f6d2 JC |
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 { | |
107c1cdb | 51 | if err := t.transformSingle(g, parent, c); err != nil { |
bae9f6d2 JC |
52 | return err |
53 | } | |
54 | } | |
55 | ||
56 | return nil | |
57 | } | |
58 | ||
107c1cdb ND |
59 | func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, c *configs.Config) error { |
60 | ||
61 | // Our addressing system distinguishes between modules and module instances, | |
62 | // but we're not yet ready to make that distinction here (since we don't | |
63 | // support "count"/"for_each" on modules) and so we just do a naive | |
64 | // transform of the module path into a module instance path, assuming that | |
65 | // no keys are in use. This should be removed when "count" and "for_each" | |
66 | // are implemented for modules. | |
67 | path := c.Path.UnkeyedInstanceShim() | |
68 | _, call := path.Call() | |
69 | ||
70 | // Find the call in the parent module configuration, so we can get the | |
71 | // expressions given for each input variable at the call site. | |
72 | callConfig, exists := parent.Module.ModuleCalls[call.Name] | |
73 | if !exists { | |
74 | // This should never happen, since it indicates an improperly-constructed | |
75 | // configuration tree. | |
76 | panic(fmt.Errorf("no module call block found for %s", path)) | |
bae9f6d2 JC |
77 | } |
78 | ||
107c1cdb ND |
79 | // We need to construct a schema for the expected call arguments based on |
80 | // the configured variables in our config, which we can then use to | |
81 | // decode the content of the call block. | |
82 | schema := &hcl.BodySchema{} | |
83 | for _, v := range c.Module.Variables { | |
84 | schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{ | |
85 | Name: v.Name, | |
86 | Required: v.Default == cty.NilVal, | |
87 | }) | |
bae9f6d2 | 88 | } |
107c1cdb ND |
89 | |
90 | content, contentDiags := callConfig.Config.Content(schema) | |
91 | if contentDiags.HasErrors() { | |
92 | // Validation code elsewhere should deal with any errors before we | |
93 | // get in here, but we'll report them out here just in case, to | |
94 | // avoid crashes. | |
95 | var diags tfdiags.Diagnostics | |
96 | diags = diags.Append(contentDiags) | |
97 | return diags.Err() | |
bae9f6d2 JC |
98 | } |
99 | ||
107c1cdb ND |
100 | for _, v := range c.Module.Variables { |
101 | var expr hcl.Expression | |
102 | if attr := content.Attributes[v.Name]; attr != nil { | |
103 | expr = attr.Expr | |
104 | } else { | |
105 | // No expression provided for this variable, so we'll make a | |
106 | // synthetic one using the variable's default value. | |
107 | expr = &hclsyntax.LiteralValueExpr{ | |
108 | Val: v.Default, | |
109 | SrcRange: v.DeclRange, // This is not exact, but close enough | |
bae9f6d2 JC |
110 | } |
111 | } | |
112 | ||
107c1cdb ND |
113 | // For now we treat all module variables as "applyable", even though |
114 | // such nodes are valid to use on other walks too. We may specialize | |
115 | // this in future if we find reasons to employ different behaviors | |
116 | // in different scenarios. | |
bae9f6d2 | 117 | node := &NodeApplyableModuleVariable{ |
107c1cdb ND |
118 | Addr: path.InputVariable(v.Name), |
119 | Config: v, | |
120 | Expr: expr, | |
bae9f6d2 | 121 | } |
bae9f6d2 JC |
122 | g.Add(node) |
123 | } | |
124 | ||
125 | return nil | |
126 | } |