aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/configs/parser_config.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/configs/parser_config.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/parser_config.go247
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 @@
1package configs
2
3import (
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.
21func (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.
28func (p *Parser) LoadConfigFileOverride(path string) (*File, hcl.Diagnostics) {
29 return p.loadConfigFile(path, true)
30}
31
32func (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.
150func 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.
177var 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.
214var 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
232var configFileVersionSniffRootSchema = &hcl.BodySchema{
233 Blocks: []hcl.BlockHeaderSchema{
234 {
235 Type: "terraform",
236 },
237 },
238}
239
240// configFileVersionSniffBlockSchema is a schema for sniffCoreVersionRequirements
241var configFileVersionSniffBlockSchema = &hcl.BodySchema{
242 Attributes: []hcl.AttributeSchema{
243 {
244 Name: "required_version",
245 },
246 },
247}