diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/configs/parser_config.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/configs/parser_config.go | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/configs/parser_config.go b/vendor/github.com/hashicorp/terraform/configs/parser_config.go new file mode 100644 index 0000000..7f2ff27 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/configs/parser_config.go | |||
@@ -0,0 +1,247 @@ | |||
1 | package configs | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | ) | ||
6 | |||
7 | // LoadConfigFile reads the file at the given path and parses it as a config | ||
8 | // file. | ||
9 | // | ||
10 | // If the file cannot be read -- for example, if it does not exist -- then | ||
11 | // a nil *File will be returned along with error diagnostics. Callers may wish | ||
12 | // to disregard the returned diagnostics in this case and instead generate | ||
13 | // their own error message(s) with additional context. | ||
14 | // | ||
15 | // If the returned diagnostics has errors when a non-nil map is returned | ||
16 | // then the map may be incomplete but should be valid enough for careful | ||
17 | // static analysis. | ||
18 | // | ||
19 | // This method wraps LoadHCLFile, and so it inherits the syntax selection | ||
20 | // behaviors documented for that method. | ||
21 | func (p *Parser) LoadConfigFile(path string) (*File, hcl.Diagnostics) { | ||
22 | return p.loadConfigFile(path, false) | ||
23 | } | ||
24 | |||
25 | // LoadConfigFileOverride is the same as LoadConfigFile except that it relaxes | ||
26 | // certain required attribute constraints in order to interpret the given | ||
27 | // file as an overrides file. | ||
28 | func (p *Parser) LoadConfigFileOverride(path string) (*File, hcl.Diagnostics) { | ||
29 | return p.loadConfigFile(path, true) | ||
30 | } | ||
31 | |||
32 | func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnostics) { | ||
33 | |||
34 | body, diags := p.LoadHCLFile(path) | ||
35 | if body == nil { | ||
36 | return nil, diags | ||
37 | } | ||
38 | |||
39 | file := &File{} | ||
40 | |||
41 | var reqDiags hcl.Diagnostics | ||
42 | file.CoreVersionConstraints, reqDiags = sniffCoreVersionRequirements(body) | ||
43 | diags = append(diags, reqDiags...) | ||
44 | |||
45 | content, contentDiags := body.Content(configFileSchema) | ||
46 | diags = append(diags, contentDiags...) | ||
47 | |||
48 | for _, block := range content.Blocks { | ||
49 | switch block.Type { | ||
50 | |||
51 | case "terraform": | ||
52 | content, contentDiags := block.Body.Content(terraformBlockSchema) | ||
53 | diags = append(diags, contentDiags...) | ||
54 | |||
55 | // We ignore the "terraform_version" attribute here because | ||
56 | // sniffCoreVersionRequirements already dealt with that above. | ||
57 | |||
58 | for _, innerBlock := range content.Blocks { | ||
59 | switch innerBlock.Type { | ||
60 | |||
61 | case "backend": | ||
62 | backendCfg, cfgDiags := decodeBackendBlock(innerBlock) | ||
63 | diags = append(diags, cfgDiags...) | ||
64 | if backendCfg != nil { | ||
65 | file.Backends = append(file.Backends, backendCfg) | ||
66 | } | ||
67 | |||
68 | case "required_providers": | ||
69 | reqs, reqsDiags := decodeRequiredProvidersBlock(innerBlock) | ||
70 | diags = append(diags, reqsDiags...) | ||
71 | file.ProviderRequirements = append(file.ProviderRequirements, reqs...) | ||
72 | |||
73 | default: | ||
74 | // Should never happen because the above cases should be exhaustive | ||
75 | // for all block type names in our schema. | ||
76 | continue | ||
77 | |||
78 | } | ||
79 | } | ||
80 | |||
81 | case "provider": | ||
82 | cfg, cfgDiags := decodeProviderBlock(block) | ||
83 | diags = append(diags, cfgDiags...) | ||
84 | if cfg != nil { | ||
85 | file.ProviderConfigs = append(file.ProviderConfigs, cfg) | ||
86 | } | ||
87 | |||
88 | case "variable": | ||
89 | cfg, cfgDiags := decodeVariableBlock(block, override) | ||
90 | diags = append(diags, cfgDiags...) | ||
91 | if cfg != nil { | ||
92 | file.Variables = append(file.Variables, cfg) | ||
93 | } | ||
94 | |||
95 | case "locals": | ||
96 | defs, defsDiags := decodeLocalsBlock(block) | ||
97 | diags = append(diags, defsDiags...) | ||
98 | file.Locals = append(file.Locals, defs...) | ||
99 | |||
100 | case "output": | ||
101 | cfg, cfgDiags := decodeOutputBlock(block, override) | ||
102 | diags = append(diags, cfgDiags...) | ||
103 | if cfg != nil { | ||
104 | file.Outputs = append(file.Outputs, cfg) | ||
105 | } | ||
106 | |||
107 | case "module": | ||
108 | cfg, cfgDiags := decodeModuleBlock(block, override) | ||
109 | diags = append(diags, cfgDiags...) | ||
110 | if cfg != nil { | ||
111 | file.ModuleCalls = append(file.ModuleCalls, cfg) | ||
112 | } | ||
113 | |||
114 | case "resource": | ||
115 | cfg, cfgDiags := decodeResourceBlock(block) | ||
116 | diags = append(diags, cfgDiags...) | ||
117 | if cfg != nil { | ||
118 | file.ManagedResources = append(file.ManagedResources, cfg) | ||
119 | } | ||
120 | |||
121 | case "data": | ||
122 | cfg, cfgDiags := decodeDataBlock(block) | ||
123 | diags = append(diags, cfgDiags...) | ||
124 | if cfg != nil { | ||
125 | file.DataResources = append(file.DataResources, cfg) | ||
126 | } | ||
127 | |||
128 | default: | ||
129 | // Should never happen because the above cases should be exhaustive | ||
130 | // for all block type names in our schema. | ||
131 | continue | ||
132 | |||
133 | } | ||
134 | } | ||
135 | |||
136 | return file, diags | ||
137 | } | ||
138 | |||
139 | // sniffCoreVersionRequirements does minimal parsing of the given body for | ||
140 | // "terraform" blocks with "required_version" attributes, returning the | ||
141 | // requirements found. | ||
142 | // | ||
143 | // This is intended to maximize the chance that we'll be able to read the | ||
144 | // requirements (syntax errors notwithstanding) even if the config file contains | ||
145 | // constructs that might've been added in future Terraform versions | ||
146 | // | ||
147 | // This is a "best effort" sort of method which will return constraints it is | ||
148 | // able to find, but may return no constraints at all if the given body is | ||
149 | // so invalid that it cannot be decoded at all. | ||
150 | func sniffCoreVersionRequirements(body hcl.Body) ([]VersionConstraint, hcl.Diagnostics) { | ||
151 | rootContent, _, diags := body.PartialContent(configFileVersionSniffRootSchema) | ||
152 | |||
153 | var constraints []VersionConstraint | ||
154 | |||
155 | for _, block := range rootContent.Blocks { | ||
156 | content, _, blockDiags := block.Body.PartialContent(configFileVersionSniffBlockSchema) | ||
157 | diags = append(diags, blockDiags...) | ||
158 | |||
159 | attr, exists := content.Attributes["required_version"] | ||
160 | if !exists { | ||
161 | continue | ||
162 | } | ||
163 | |||
164 | constraint, constraintDiags := decodeVersionConstraint(attr) | ||
165 | diags = append(diags, constraintDiags...) | ||
166 | if !constraintDiags.HasErrors() { | ||
167 | constraints = append(constraints, constraint) | ||
168 | } | ||
169 | } | ||
170 | |||
171 | return constraints, diags | ||
172 | } | ||
173 | |||
174 | // configFileSchema is the schema for the top-level of a config file. We use | ||
175 | // the low-level HCL API for this level so we can easily deal with each | ||
176 | // block type separately with its own decoding logic. | ||
177 | var configFileSchema = &hcl.BodySchema{ | ||
178 | Blocks: []hcl.BlockHeaderSchema{ | ||
179 | { | ||
180 | Type: "terraform", | ||
181 | }, | ||
182 | { | ||
183 | Type: "provider", | ||
184 | LabelNames: []string{"name"}, | ||
185 | }, | ||
186 | { | ||
187 | Type: "variable", | ||
188 | LabelNames: []string{"name"}, | ||
189 | }, | ||
190 | { | ||
191 | Type: "locals", | ||
192 | }, | ||
193 | { | ||
194 | Type: "output", | ||
195 | LabelNames: []string{"name"}, | ||
196 | }, | ||
197 | { | ||
198 | Type: "module", | ||
199 | LabelNames: []string{"name"}, | ||
200 | }, | ||
201 | { | ||
202 | Type: "resource", | ||
203 | LabelNames: []string{"type", "name"}, | ||
204 | }, | ||
205 | { | ||
206 | Type: "data", | ||
207 | LabelNames: []string{"type", "name"}, | ||
208 | }, | ||
209 | }, | ||
210 | } | ||
211 | |||
212 | // terraformBlockSchema is the schema for a top-level "terraform" block in | ||
213 | // a configuration file. | ||
214 | var terraformBlockSchema = &hcl.BodySchema{ | ||
215 | Attributes: []hcl.AttributeSchema{ | ||
216 | { | ||
217 | Name: "required_version", | ||
218 | }, | ||
219 | }, | ||
220 | Blocks: []hcl.BlockHeaderSchema{ | ||
221 | { | ||
222 | Type: "backend", | ||
223 | LabelNames: []string{"type"}, | ||
224 | }, | ||
225 | { | ||
226 | Type: "required_providers", | ||
227 | }, | ||
228 | }, | ||
229 | } | ||
230 | |||
231 | // configFileVersionSniffRootSchema is a schema for sniffCoreVersionRequirements | ||
232 | var configFileVersionSniffRootSchema = &hcl.BodySchema{ | ||
233 | Blocks: []hcl.BlockHeaderSchema{ | ||
234 | { | ||
235 | Type: "terraform", | ||
236 | }, | ||
237 | }, | ||
238 | } | ||
239 | |||
240 | // configFileVersionSniffBlockSchema is a schema for sniffCoreVersionRequirements | ||
241 | var configFileVersionSniffBlockSchema = &hcl.BodySchema{ | ||
242 | Attributes: []hcl.AttributeSchema{ | ||
243 | { | ||
244 | Name: "required_version", | ||
245 | }, | ||
246 | }, | ||
247 | } | ||