]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
1 | package configs |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | ||
6 | "github.com/hashicorp/hcl2/hcl" | |
7 | ||
8 | "github.com/hashicorp/terraform/addrs" | |
9 | ) | |
10 | ||
11 | // Module is a container for a set of configuration constructs that are | |
12 | // evaluated within a common namespace. | |
13 | type Module struct { | |
14 | // SourceDir is the filesystem directory that the module was loaded from. | |
15 | // | |
16 | // This is populated automatically only for configurations loaded with | |
17 | // LoadConfigDir. If the parser is using a virtual filesystem then the | |
18 | // path here will be in terms of that virtual filesystem. | |
19 | ||
20 | // Any other caller that constructs a module directly with NewModule may | |
21 | // assign a suitable value to this attribute before using it for other | |
22 | // purposes. It should be treated as immutable by all consumers of Module | |
23 | // values. | |
24 | SourceDir string | |
25 | ||
26 | CoreVersionConstraints []VersionConstraint | |
27 | ||
28 | Backend *Backend | |
29 | ProviderConfigs map[string]*Provider | |
30 | ProviderRequirements map[string][]VersionConstraint | |
31 | ||
32 | Variables map[string]*Variable | |
33 | Locals map[string]*Local | |
34 | Outputs map[string]*Output | |
35 | ||
36 | ModuleCalls map[string]*ModuleCall | |
37 | ||
38 | ManagedResources map[string]*Resource | |
39 | DataResources map[string]*Resource | |
40 | } | |
41 | ||
42 | // File describes the contents of a single configuration file. | |
43 | // | |
44 | // Individual files are not usually used alone, but rather combined together | |
45 | // with other files (conventionally, those in the same directory) to produce | |
46 | // a *Module, using NewModule. | |
47 | // | |
48 | // At the level of an individual file we represent directly the structural | |
49 | // elements present in the file, without any attempt to detect conflicting | |
50 | // declarations. A File object can therefore be used for some basic static | |
51 | // analysis of individual elements, but must be built into a Module to detect | |
52 | // duplicate declarations. | |
53 | type File struct { | |
54 | CoreVersionConstraints []VersionConstraint | |
55 | ||
56 | Backends []*Backend | |
57 | ProviderConfigs []*Provider | |
58 | ProviderRequirements []*ProviderRequirement | |
59 | ||
60 | Variables []*Variable | |
61 | Locals []*Local | |
62 | Outputs []*Output | |
63 | ||
64 | ModuleCalls []*ModuleCall | |
65 | ||
66 | ManagedResources []*Resource | |
67 | DataResources []*Resource | |
68 | } | |
69 | ||
70 | // NewModule takes a list of primary files and a list of override files and | |
71 | // produces a *Module by combining the files together. | |
72 | // | |
73 | // If there are any conflicting declarations in the given files -- for example, | |
74 | // if the same variable name is defined twice -- then the resulting module | |
75 | // will be incomplete and error diagnostics will be returned. Careful static | |
76 | // analysis of the returned Module is still possible in this case, but the | |
77 | // module will probably not be semantically valid. | |
78 | func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) { | |
79 | var diags hcl.Diagnostics | |
80 | mod := &Module{ | |
81 | ProviderConfigs: map[string]*Provider{}, | |
82 | ProviderRequirements: map[string][]VersionConstraint{}, | |
83 | Variables: map[string]*Variable{}, | |
84 | Locals: map[string]*Local{}, | |
85 | Outputs: map[string]*Output{}, | |
86 | ModuleCalls: map[string]*ModuleCall{}, | |
87 | ManagedResources: map[string]*Resource{}, | |
88 | DataResources: map[string]*Resource{}, | |
89 | } | |
90 | ||
91 | for _, file := range primaryFiles { | |
92 | fileDiags := mod.appendFile(file) | |
93 | diags = append(diags, fileDiags...) | |
94 | } | |
95 | ||
96 | for _, file := range overrideFiles { | |
97 | fileDiags := mod.mergeFile(file) | |
98 | diags = append(diags, fileDiags...) | |
99 | } | |
100 | ||
101 | return mod, diags | |
102 | } | |
103 | ||
104 | // ResourceByAddr returns the configuration for the resource with the given | |
105 | // address, or nil if there is no such resource. | |
106 | func (m *Module) ResourceByAddr(addr addrs.Resource) *Resource { | |
107 | key := addr.String() | |
108 | switch addr.Mode { | |
109 | case addrs.ManagedResourceMode: | |
110 | return m.ManagedResources[key] | |
111 | case addrs.DataResourceMode: | |
112 | return m.DataResources[key] | |
113 | default: | |
114 | return nil | |
115 | } | |
116 | } | |
117 | ||
118 | func (m *Module) appendFile(file *File) hcl.Diagnostics { | |
119 | var diags hcl.Diagnostics | |
120 | ||
121 | for _, constraint := range file.CoreVersionConstraints { | |
122 | // If there are any conflicting requirements then we'll catch them | |
123 | // when we actually check these constraints. | |
124 | m.CoreVersionConstraints = append(m.CoreVersionConstraints, constraint) | |
125 | } | |
126 | ||
127 | for _, b := range file.Backends { | |
128 | if m.Backend != nil { | |
129 | diags = append(diags, &hcl.Diagnostic{ | |
130 | Severity: hcl.DiagError, | |
131 | Summary: "Duplicate backend configuration", | |
132 | Detail: fmt.Sprintf("A module may have only one backend configuration. The backend was previously configured at %s.", m.Backend.DeclRange), | |
133 | Subject: &b.DeclRange, | |
134 | }) | |
135 | continue | |
136 | } | |
137 | m.Backend = b | |
138 | } | |
139 | ||
140 | for _, pc := range file.ProviderConfigs { | |
141 | key := pc.moduleUniqueKey() | |
142 | if existing, exists := m.ProviderConfigs[key]; exists { | |
143 | if existing.Alias == "" { | |
144 | diags = append(diags, &hcl.Diagnostic{ | |
145 | Severity: hcl.DiagError, | |
146 | Summary: "Duplicate provider configuration", | |
147 | Detail: fmt.Sprintf("A default (non-aliased) provider configuration for %q was already given at %s. If multiple configurations are required, set the \"alias\" argument for alternative configurations.", existing.Name, existing.DeclRange), | |
148 | Subject: &pc.DeclRange, | |
149 | }) | |
150 | } else { | |
151 | diags = append(diags, &hcl.Diagnostic{ | |
152 | Severity: hcl.DiagError, | |
153 | Summary: "Duplicate provider configuration", | |
154 | Detail: fmt.Sprintf("A provider configuration for %q with alias %q was already given at %s. Each configuration for the same provider must have a distinct alias.", existing.Name, existing.Alias, existing.DeclRange), | |
155 | Subject: &pc.DeclRange, | |
156 | }) | |
157 | } | |
158 | continue | |
159 | } | |
160 | m.ProviderConfigs[key] = pc | |
161 | } | |
162 | ||
163 | for _, reqd := range file.ProviderRequirements { | |
164 | m.ProviderRequirements[reqd.Name] = append(m.ProviderRequirements[reqd.Name], reqd.Requirement) | |
165 | } | |
166 | ||
167 | for _, v := range file.Variables { | |
168 | if existing, exists := m.Variables[v.Name]; exists { | |
169 | diags = append(diags, &hcl.Diagnostic{ | |
170 | Severity: hcl.DiagError, | |
171 | Summary: "Duplicate variable declaration", | |
172 | Detail: fmt.Sprintf("A variable named %q was already declared at %s. Variable names must be unique within a module.", existing.Name, existing.DeclRange), | |
173 | Subject: &v.DeclRange, | |
174 | }) | |
175 | } | |
176 | m.Variables[v.Name] = v | |
177 | } | |
178 | ||
179 | for _, l := range file.Locals { | |
180 | if existing, exists := m.Locals[l.Name]; exists { | |
181 | diags = append(diags, &hcl.Diagnostic{ | |
182 | Severity: hcl.DiagError, | |
183 | Summary: "Duplicate local value definition", | |
184 | Detail: fmt.Sprintf("A local value named %q was already defined at %s. Local value names must be unique within a module.", existing.Name, existing.DeclRange), | |
185 | Subject: &l.DeclRange, | |
186 | }) | |
187 | } | |
188 | m.Locals[l.Name] = l | |
189 | } | |
190 | ||
191 | for _, o := range file.Outputs { | |
192 | if existing, exists := m.Outputs[o.Name]; exists { | |
193 | diags = append(diags, &hcl.Diagnostic{ | |
194 | Severity: hcl.DiagError, | |
195 | Summary: "Duplicate output definition", | |
196 | Detail: fmt.Sprintf("An output named %q was already defined at %s. Output names must be unique within a module.", existing.Name, existing.DeclRange), | |
197 | Subject: &o.DeclRange, | |
198 | }) | |
199 | } | |
200 | m.Outputs[o.Name] = o | |
201 | } | |
202 | ||
203 | for _, mc := range file.ModuleCalls { | |
204 | if existing, exists := m.ModuleCalls[mc.Name]; exists { | |
205 | diags = append(diags, &hcl.Diagnostic{ | |
206 | Severity: hcl.DiagError, | |
207 | Summary: "Duplicate module call", | |
208 | Detail: fmt.Sprintf("An module call named %q was already defined at %s. Module calls must have unique names within a module.", existing.Name, existing.DeclRange), | |
209 | Subject: &mc.DeclRange, | |
210 | }) | |
211 | } | |
212 | m.ModuleCalls[mc.Name] = mc | |
213 | } | |
214 | ||
215 | for _, r := range file.ManagedResources { | |
216 | key := r.moduleUniqueKey() | |
217 | if existing, exists := m.ManagedResources[key]; exists { | |
218 | diags = append(diags, &hcl.Diagnostic{ | |
219 | Severity: hcl.DiagError, | |
220 | Summary: fmt.Sprintf("Duplicate resource %q configuration", existing.Type), | |
221 | Detail: fmt.Sprintf("A %s resource named %q was already declared at %s. Resource names must be unique per type in each module.", existing.Type, existing.Name, existing.DeclRange), | |
222 | Subject: &r.DeclRange, | |
223 | }) | |
224 | continue | |
225 | } | |
226 | m.ManagedResources[key] = r | |
227 | } | |
228 | ||
229 | for _, r := range file.DataResources { | |
230 | key := r.moduleUniqueKey() | |
231 | if existing, exists := m.DataResources[key]; exists { | |
232 | diags = append(diags, &hcl.Diagnostic{ | |
233 | Severity: hcl.DiagError, | |
234 | Summary: fmt.Sprintf("Duplicate data %q configuration", existing.Type), | |
235 | Detail: fmt.Sprintf("A %s data resource named %q was already declared at %s. Resource names must be unique per type in each module.", existing.Type, existing.Name, existing.DeclRange), | |
236 | Subject: &r.DeclRange, | |
237 | }) | |
238 | continue | |
239 | } | |
240 | m.DataResources[key] = r | |
241 | } | |
242 | ||
243 | return diags | |
244 | } | |
245 | ||
246 | func (m *Module) mergeFile(file *File) hcl.Diagnostics { | |
247 | var diags hcl.Diagnostics | |
248 | ||
249 | if len(file.CoreVersionConstraints) != 0 { | |
250 | // This is a bit of a strange case for overriding since we normally | |
251 | // would union together across multiple files anyway, but we'll | |
252 | // allow it and have each override file clobber any existing list. | |
253 | m.CoreVersionConstraints = nil | |
254 | for _, constraint := range file.CoreVersionConstraints { | |
255 | m.CoreVersionConstraints = append(m.CoreVersionConstraints, constraint) | |
256 | } | |
257 | } | |
258 | ||
259 | if len(file.Backends) != 0 { | |
260 | switch len(file.Backends) { | |
261 | case 1: | |
262 | m.Backend = file.Backends[0] | |
263 | default: | |
264 | // An override file with multiple backends is still invalid, even | |
265 | // though it can override backends from _other_ files. | |
266 | diags = append(diags, &hcl.Diagnostic{ | |
267 | Severity: hcl.DiagError, | |
268 | Summary: "Duplicate backend configuration", | |
269 | Detail: fmt.Sprintf("Each override file may have only one backend configuration. A backend was previously configured at %s.", file.Backends[0].DeclRange), | |
270 | Subject: &file.Backends[1].DeclRange, | |
271 | }) | |
272 | } | |
273 | } | |
274 | ||
275 | for _, pc := range file.ProviderConfigs { | |
276 | key := pc.moduleUniqueKey() | |
277 | existing, exists := m.ProviderConfigs[key] | |
278 | if pc.Alias == "" { | |
279 | // We allow overriding a non-existing _default_ provider configuration | |
280 | // because the user model is that an absent provider configuration | |
281 | // implies an empty provider configuration, which is what the user | |
282 | // is therefore overriding here. | |
283 | if exists { | |
284 | mergeDiags := existing.merge(pc) | |
285 | diags = append(diags, mergeDiags...) | |
286 | } else { | |
287 | m.ProviderConfigs[key] = pc | |
288 | } | |
289 | } else { | |
290 | // For aliased providers, there must be a base configuration to | |
291 | // override. This allows us to detect and report alias typos | |
292 | // that might otherwise cause the override to not apply. | |
293 | if !exists { | |
294 | diags = append(diags, &hcl.Diagnostic{ | |
295 | Severity: hcl.DiagError, | |
296 | Summary: "Missing base provider configuration for override", | |
297 | Detail: fmt.Sprintf("There is no %s provider configuration with the alias %q. An override file can only override an aliased provider configuration that was already defined in a primary configuration file.", pc.Name, pc.Alias), | |
298 | Subject: &pc.DeclRange, | |
299 | }) | |
300 | continue | |
301 | } | |
302 | mergeDiags := existing.merge(pc) | |
303 | diags = append(diags, mergeDiags...) | |
304 | } | |
305 | } | |
306 | ||
307 | if len(file.ProviderRequirements) != 0 { | |
308 | mergeProviderVersionConstraints(m.ProviderRequirements, file.ProviderRequirements) | |
309 | } | |
310 | ||
311 | for _, v := range file.Variables { | |
312 | existing, exists := m.Variables[v.Name] | |
313 | if !exists { | |
314 | diags = append(diags, &hcl.Diagnostic{ | |
315 | Severity: hcl.DiagError, | |
316 | Summary: "Missing base variable declaration to override", | |
317 | Detail: fmt.Sprintf("There is no variable named %q. An override file can only override a variable that was already declared in a primary configuration file.", v.Name), | |
318 | Subject: &v.DeclRange, | |
319 | }) | |
320 | continue | |
321 | } | |
322 | mergeDiags := existing.merge(v) | |
323 | diags = append(diags, mergeDiags...) | |
324 | } | |
325 | ||
326 | for _, l := range file.Locals { | |
327 | existing, exists := m.Locals[l.Name] | |
328 | if !exists { | |
329 | diags = append(diags, &hcl.Diagnostic{ | |
330 | Severity: hcl.DiagError, | |
331 | Summary: "Missing base local value definition to override", | |
332 | Detail: fmt.Sprintf("There is no local value named %q. An override file can only override a local value that was already defined in a primary configuration file.", l.Name), | |
333 | Subject: &l.DeclRange, | |
334 | }) | |
335 | continue | |
336 | } | |
337 | mergeDiags := existing.merge(l) | |
338 | diags = append(diags, mergeDiags...) | |
339 | } | |
340 | ||
341 | for _, o := range file.Outputs { | |
342 | existing, exists := m.Outputs[o.Name] | |
343 | if !exists { | |
344 | diags = append(diags, &hcl.Diagnostic{ | |
345 | Severity: hcl.DiagError, | |
346 | Summary: "Missing base output definition to override", | |
347 | Detail: fmt.Sprintf("There is no output named %q. An override file can only override an output that was already defined in a primary configuration file.", o.Name), | |
348 | Subject: &o.DeclRange, | |
349 | }) | |
350 | continue | |
351 | } | |
352 | mergeDiags := existing.merge(o) | |
353 | diags = append(diags, mergeDiags...) | |
354 | } | |
355 | ||
356 | for _, mc := range file.ModuleCalls { | |
357 | existing, exists := m.ModuleCalls[mc.Name] | |
358 | if !exists { | |
359 | diags = append(diags, &hcl.Diagnostic{ | |
360 | Severity: hcl.DiagError, | |
361 | Summary: "Missing module call to override", | |
362 | Detail: fmt.Sprintf("There is no module call named %q. An override file can only override a module call that was defined in a primary configuration file.", mc.Name), | |
363 | Subject: &mc.DeclRange, | |
364 | }) | |
365 | continue | |
366 | } | |
367 | mergeDiags := existing.merge(mc) | |
368 | diags = append(diags, mergeDiags...) | |
369 | } | |
370 | ||
371 | for _, r := range file.ManagedResources { | |
372 | key := r.moduleUniqueKey() | |
373 | existing, exists := m.ManagedResources[key] | |
374 | if !exists { | |
375 | diags = append(diags, &hcl.Diagnostic{ | |
376 | Severity: hcl.DiagError, | |
377 | Summary: "Missing resource to override", | |
378 | Detail: fmt.Sprintf("There is no %s resource named %q. An override file can only override a resource block defined in a primary configuration file.", r.Type, r.Name), | |
379 | Subject: &r.DeclRange, | |
380 | }) | |
381 | continue | |
382 | } | |
383 | mergeDiags := existing.merge(r) | |
384 | diags = append(diags, mergeDiags...) | |
385 | } | |
386 | ||
387 | for _, r := range file.DataResources { | |
388 | key := r.moduleUniqueKey() | |
389 | existing, exists := m.DataResources[key] | |
390 | if !exists { | |
391 | diags = append(diags, &hcl.Diagnostic{ | |
392 | Severity: hcl.DiagError, | |
393 | Summary: "Missing data resource to override", | |
394 | Detail: fmt.Sprintf("There is no %s data resource named %q. An override file can only override a data block defined in a primary configuration file.", r.Type, r.Name), | |
395 | Subject: &r.DeclRange, | |
396 | }) | |
397 | continue | |
398 | } | |
399 | mergeDiags := existing.merge(r) | |
400 | diags = append(diags, mergeDiags...) | |
401 | } | |
402 | ||
403 | return diags | |
404 | } |