6 "github.com/hashicorp/hcl2/hcl"
8 "github.com/hashicorp/terraform/addrs"
11 // Module is a container for a set of configuration constructs that are
12 // evaluated within a common namespace.
14 // SourceDir is the filesystem directory that the module was loaded from.
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.
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
26 CoreVersionConstraints []VersionConstraint
29 ProviderConfigs map[string]*Provider
30 ProviderRequirements map[string][]VersionConstraint
32 Variables map[string]*Variable
33 Locals map[string]*Local
34 Outputs map[string]*Output
36 ModuleCalls map[string]*ModuleCall
38 ManagedResources map[string]*Resource
39 DataResources map[string]*Resource
42 // File describes the contents of a single configuration file.
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.
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.
54 CoreVersionConstraints []VersionConstraint
57 ProviderConfigs []*Provider
58 ProviderRequirements []*ProviderRequirement
64 ModuleCalls []*ModuleCall
66 ManagedResources []*Resource
67 DataResources []*Resource
70 // NewModule takes a list of primary files and a list of override files and
71 // produces a *Module by combining the files together.
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
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{},
91 for _, file := range primaryFiles {
92 fileDiags := mod.appendFile(file)
93 diags = append(diags, fileDiags...)
96 for _, file := range overrideFiles {
97 fileDiags := mod.mergeFile(file)
98 diags = append(diags, fileDiags...)
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 {
109 case addrs.ManagedResourceMode:
110 return m.ManagedResources[key]
111 case addrs.DataResourceMode:
112 return m.DataResources[key]
118 func (m *Module) appendFile(file *File) hcl.Diagnostics {
119 var diags hcl.Diagnostics
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)
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,
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,
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,
160 m.ProviderConfigs[key] = pc
163 for _, reqd := range file.ProviderRequirements {
164 m.ProviderRequirements[reqd.Name] = append(m.ProviderRequirements[reqd.Name], reqd.Requirement)
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,
176 m.Variables[v.Name] = v
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,
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,
200 m.Outputs[o.Name] = o
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,
212 m.ModuleCalls[mc.Name] = mc
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,
226 m.ManagedResources[key] = r
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,
240 m.DataResources[key] = r
246 func (m *Module) mergeFile(file *File) hcl.Diagnostics {
247 var diags hcl.Diagnostics
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)
259 if len(file.Backends) != 0 {
260 switch len(file.Backends) {
262 m.Backend = file.Backends[0]
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,
275 for _, pc := range file.ProviderConfigs {
276 key := pc.moduleUniqueKey()
277 existing, exists := m.ProviderConfigs[key]
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.
284 mergeDiags := existing.merge(pc)
285 diags = append(diags, mergeDiags...)
287 m.ProviderConfigs[key] = pc
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.
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,
302 mergeDiags := existing.merge(pc)
303 diags = append(diags, mergeDiags...)
307 if len(file.ProviderRequirements) != 0 {
308 mergeProviderVersionConstraints(m.ProviderRequirements, file.ProviderRequirements)
311 for _, v := range file.Variables {
312 existing, exists := m.Variables[v.Name]
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,
322 mergeDiags := existing.merge(v)
323 diags = append(diags, mergeDiags...)
326 for _, l := range file.Locals {
327 existing, exists := m.Locals[l.Name]
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,
337 mergeDiags := existing.merge(l)
338 diags = append(diags, mergeDiags...)
341 for _, o := range file.Outputs {
342 existing, exists := m.Outputs[o.Name]
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,
352 mergeDiags := existing.merge(o)
353 diags = append(diags, mergeDiags...)
356 for _, mc := range file.ModuleCalls {
357 existing, exists := m.ModuleCalls[mc.Name]
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,
367 mergeDiags := existing.merge(mc)
368 diags = append(diags, mergeDiags...)
371 for _, r := range file.ManagedResources {
372 key := r.moduleUniqueKey()
373 existing, exists := m.ManagedResources[key]
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,
383 mergeDiags := existing.merge(r)
384 diags = append(diags, mergeDiags...)
387 for _, r := range file.DataResources {
388 key := r.moduleUniqueKey()
389 existing, exists := m.DataResources[key]
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,
399 mergeDiags := existing.merge(r)
400 diags = append(diags, mergeDiags...)