github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/configs/module.go (about) 1 package configs 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/hcl/v2" 7 8 "github.com/hashicorp/terraform/internal/addrs" 9 "github.com/hashicorp/terraform/internal/experiments" 10 ) 11 12 // Module is a container for a set of configuration constructs that are 13 // evaluated within a common namespace. 14 type Module struct { 15 // SourceDir is the filesystem directory that the module was loaded from. 16 // 17 // This is populated automatically only for configurations loaded with 18 // LoadConfigDir. If the parser is using a virtual filesystem then the 19 // path here will be in terms of that virtual filesystem. 20 21 // Any other caller that constructs a module directly with NewModule may 22 // assign a suitable value to this attribute before using it for other 23 // purposes. It should be treated as immutable by all consumers of Module 24 // values. 25 SourceDir string 26 27 CoreVersionConstraints []VersionConstraint 28 29 ActiveExperiments experiments.Set 30 31 Backend *Backend 32 ProviderConfigs map[string]*Provider 33 ProviderRequirements *RequiredProviders 34 ProviderLocalNames map[addrs.Provider]string 35 ProviderMetas map[addrs.Provider]*ProviderMeta 36 37 Variables map[string]*Variable 38 Locals map[string]*Local 39 Outputs map[string]*Output 40 41 ModuleCalls map[string]*ModuleCall 42 43 ManagedResources map[string]*Resource 44 DataResources map[string]*Resource 45 46 Moved []*Moved 47 } 48 49 // File describes the contents of a single configuration file. 50 // 51 // Individual files are not usually used alone, but rather combined together 52 // with other files (conventionally, those in the same directory) to produce 53 // a *Module, using NewModule. 54 // 55 // At the level of an individual file we represent directly the structural 56 // elements present in the file, without any attempt to detect conflicting 57 // declarations. A File object can therefore be used for some basic static 58 // analysis of individual elements, but must be built into a Module to detect 59 // duplicate declarations. 60 type File struct { 61 CoreVersionConstraints []VersionConstraint 62 63 ActiveExperiments experiments.Set 64 65 Backends []*Backend 66 ProviderConfigs []*Provider 67 ProviderMetas []*ProviderMeta 68 RequiredProviders []*RequiredProviders 69 70 Variables []*Variable 71 Locals []*Local 72 Outputs []*Output 73 74 ModuleCalls []*ModuleCall 75 76 ManagedResources []*Resource 77 DataResources []*Resource 78 79 Moved []*Moved 80 } 81 82 // NewModule takes a list of primary files and a list of override files and 83 // produces a *Module by combining the files together. 84 // 85 // If there are any conflicting declarations in the given files -- for example, 86 // if the same variable name is defined twice -- then the resulting module 87 // will be incomplete and error diagnostics will be returned. Careful static 88 // analysis of the returned Module is still possible in this case, but the 89 // module will probably not be semantically valid. 90 func NewModule(primaryFiles, overrideFiles []*File) (*Module, hcl.Diagnostics) { 91 var diags hcl.Diagnostics 92 mod := &Module{ 93 ProviderConfigs: map[string]*Provider{}, 94 ProviderLocalNames: map[addrs.Provider]string{}, 95 Variables: map[string]*Variable{}, 96 Locals: map[string]*Local{}, 97 Outputs: map[string]*Output{}, 98 ModuleCalls: map[string]*ModuleCall{}, 99 ManagedResources: map[string]*Resource{}, 100 DataResources: map[string]*Resource{}, 101 ProviderMetas: map[addrs.Provider]*ProviderMeta{}, 102 } 103 104 // Process the required_providers blocks first, to ensure that all 105 // resources have access to the correct provider FQNs 106 for _, file := range primaryFiles { 107 for _, r := range file.RequiredProviders { 108 if mod.ProviderRequirements != nil { 109 diags = append(diags, &hcl.Diagnostic{ 110 Severity: hcl.DiagError, 111 Summary: "Duplicate required providers configuration", 112 Detail: fmt.Sprintf("A module may have only one required providers configuration. The required providers were previously configured at %s.", mod.ProviderRequirements.DeclRange), 113 Subject: &r.DeclRange, 114 }) 115 continue 116 } 117 mod.ProviderRequirements = r 118 } 119 } 120 121 // If no required_providers block is configured, create a useful empty 122 // state to reduce nil checks elsewhere 123 if mod.ProviderRequirements == nil { 124 mod.ProviderRequirements = &RequiredProviders{ 125 RequiredProviders: make(map[string]*RequiredProvider), 126 } 127 } 128 129 // Any required_providers blocks in override files replace the entire 130 // block for each provider 131 for _, file := range overrideFiles { 132 for _, override := range file.RequiredProviders { 133 for name, rp := range override.RequiredProviders { 134 mod.ProviderRequirements.RequiredProviders[name] = rp 135 } 136 } 137 } 138 139 for _, file := range primaryFiles { 140 fileDiags := mod.appendFile(file) 141 diags = append(diags, fileDiags...) 142 } 143 144 for _, file := range overrideFiles { 145 fileDiags := mod.mergeFile(file) 146 diags = append(diags, fileDiags...) 147 } 148 149 diags = append(diags, checkModuleExperiments(mod)...) 150 151 // Generate the FQN -> LocalProviderName map 152 mod.gatherProviderLocalNames() 153 154 return mod, diags 155 } 156 157 // ResourceByAddr returns the configuration for the resource with the given 158 // address, or nil if there is no such resource. 159 func (m *Module) ResourceByAddr(addr addrs.Resource) *Resource { 160 key := addr.String() 161 switch addr.Mode { 162 case addrs.ManagedResourceMode: 163 return m.ManagedResources[key] 164 case addrs.DataResourceMode: 165 return m.DataResources[key] 166 default: 167 return nil 168 } 169 } 170 171 func (m *Module) appendFile(file *File) hcl.Diagnostics { 172 var diags hcl.Diagnostics 173 174 // If there are any conflicting requirements then we'll catch them 175 // when we actually check these constraints. 176 m.CoreVersionConstraints = append(m.CoreVersionConstraints, file.CoreVersionConstraints...) 177 178 m.ActiveExperiments = experiments.SetUnion(m.ActiveExperiments, file.ActiveExperiments) 179 180 for _, b := range file.Backends { 181 if m.Backend != nil { 182 diags = append(diags, &hcl.Diagnostic{ 183 Severity: hcl.DiagError, 184 Summary: "Duplicate backend configuration", 185 Detail: fmt.Sprintf("A module may have only one backend configuration. The backend was previously configured at %s.", m.Backend.DeclRange), 186 Subject: &b.DeclRange, 187 }) 188 continue 189 } 190 m.Backend = b 191 } 192 193 for _, pc := range file.ProviderConfigs { 194 key := pc.moduleUniqueKey() 195 if existing, exists := m.ProviderConfigs[key]; exists { 196 if existing.Alias == "" { 197 diags = append(diags, &hcl.Diagnostic{ 198 Severity: hcl.DiagError, 199 Summary: "Duplicate provider configuration", 200 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), 201 Subject: &pc.DeclRange, 202 }) 203 } else { 204 diags = append(diags, &hcl.Diagnostic{ 205 Severity: hcl.DiagError, 206 Summary: "Duplicate provider configuration", 207 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), 208 Subject: &pc.DeclRange, 209 }) 210 } 211 continue 212 } 213 m.ProviderConfigs[key] = pc 214 } 215 216 for _, pm := range file.ProviderMetas { 217 provider := m.ProviderForLocalConfig(addrs.LocalProviderConfig{LocalName: pm.Provider}) 218 if existing, exists := m.ProviderMetas[provider]; exists { 219 diags = append(diags, &hcl.Diagnostic{ 220 Severity: hcl.DiagError, 221 Summary: "Duplicate provider_meta block", 222 Detail: fmt.Sprintf("A provider_meta block for provider %q was already declared at %s. Providers may only have one provider_meta block per module.", existing.Provider, existing.DeclRange), 223 Subject: &pm.DeclRange, 224 }) 225 } 226 m.ProviderMetas[provider] = pm 227 } 228 229 for _, v := range file.Variables { 230 if existing, exists := m.Variables[v.Name]; exists { 231 diags = append(diags, &hcl.Diagnostic{ 232 Severity: hcl.DiagError, 233 Summary: "Duplicate variable declaration", 234 Detail: fmt.Sprintf("A variable named %q was already declared at %s. Variable names must be unique within a module.", existing.Name, existing.DeclRange), 235 Subject: &v.DeclRange, 236 }) 237 } 238 m.Variables[v.Name] = v 239 } 240 241 for _, l := range file.Locals { 242 if existing, exists := m.Locals[l.Name]; exists { 243 diags = append(diags, &hcl.Diagnostic{ 244 Severity: hcl.DiagError, 245 Summary: "Duplicate local value definition", 246 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), 247 Subject: &l.DeclRange, 248 }) 249 } 250 m.Locals[l.Name] = l 251 } 252 253 for _, o := range file.Outputs { 254 if existing, exists := m.Outputs[o.Name]; exists { 255 diags = append(diags, &hcl.Diagnostic{ 256 Severity: hcl.DiagError, 257 Summary: "Duplicate output definition", 258 Detail: fmt.Sprintf("An output named %q was already defined at %s. Output names must be unique within a module.", existing.Name, existing.DeclRange), 259 Subject: &o.DeclRange, 260 }) 261 } 262 m.Outputs[o.Name] = o 263 } 264 265 for _, mc := range file.ModuleCalls { 266 if existing, exists := m.ModuleCalls[mc.Name]; exists { 267 diags = append(diags, &hcl.Diagnostic{ 268 Severity: hcl.DiagError, 269 Summary: "Duplicate module call", 270 Detail: fmt.Sprintf("A module call named %q was already defined at %s. Module calls must have unique names within a module.", existing.Name, existing.DeclRange), 271 Subject: &mc.DeclRange, 272 }) 273 } 274 m.ModuleCalls[mc.Name] = mc 275 } 276 277 for _, r := range file.ManagedResources { 278 key := r.moduleUniqueKey() 279 if existing, exists := m.ManagedResources[key]; exists { 280 diags = append(diags, &hcl.Diagnostic{ 281 Severity: hcl.DiagError, 282 Summary: fmt.Sprintf("Duplicate resource %q configuration", existing.Type), 283 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), 284 Subject: &r.DeclRange, 285 }) 286 continue 287 } 288 m.ManagedResources[key] = r 289 290 // set the provider FQN for the resource 291 if r.ProviderConfigRef != nil { 292 r.Provider = m.ProviderForLocalConfig(r.ProviderConfigAddr()) 293 } else { 294 // an invalid resource name (for e.g. "null resource" instead of 295 // "null_resource") can cause a panic down the line in addrs: 296 // https://github.com/hashicorp/terraform/issues/25560 297 implied, err := addrs.ParseProviderPart(r.Addr().ImpliedProvider()) 298 if err == nil { 299 r.Provider = m.ImpliedProviderForUnqualifiedType(implied) 300 } 301 // We don't return a diagnostic because the invalid resource name 302 // will already have been caught. 303 } 304 } 305 306 for _, r := range file.DataResources { 307 key := r.moduleUniqueKey() 308 if existing, exists := m.DataResources[key]; exists { 309 diags = append(diags, &hcl.Diagnostic{ 310 Severity: hcl.DiagError, 311 Summary: fmt.Sprintf("Duplicate data %q configuration", existing.Type), 312 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), 313 Subject: &r.DeclRange, 314 }) 315 continue 316 } 317 m.DataResources[key] = r 318 319 // set the provider FQN for the resource 320 if r.ProviderConfigRef != nil { 321 r.Provider = m.ProviderForLocalConfig(r.ProviderConfigAddr()) 322 } else { 323 // an invalid data source name (for e.g. "null resource" instead of 324 // "null_resource") can cause a panic down the line in addrs: 325 // https://github.com/hashicorp/terraform/issues/25560 326 implied, err := addrs.ParseProviderPart(r.Addr().ImpliedProvider()) 327 if err == nil { 328 r.Provider = m.ImpliedProviderForUnqualifiedType(implied) 329 } 330 // We don't return a diagnostic because the invalid resource name 331 // will already have been caught. 332 } 333 } 334 335 // "Moved" blocks just append, because they are all independent 336 // of one another at this level. (We handle any references between 337 // them at runtime.) 338 m.Moved = append(m.Moved, file.Moved...) 339 340 return diags 341 } 342 343 func (m *Module) mergeFile(file *File) hcl.Diagnostics { 344 var diags hcl.Diagnostics 345 346 if len(file.CoreVersionConstraints) != 0 { 347 // This is a bit of a strange case for overriding since we normally 348 // would union together across multiple files anyway, but we'll 349 // allow it and have each override file clobber any existing list. 350 m.CoreVersionConstraints = nil 351 m.CoreVersionConstraints = append(m.CoreVersionConstraints, file.CoreVersionConstraints...) 352 } 353 354 if len(file.Backends) != 0 { 355 switch len(file.Backends) { 356 case 1: 357 m.Backend = file.Backends[0] 358 default: 359 // An override file with multiple backends is still invalid, even 360 // though it can override backends from _other_ files. 361 diags = append(diags, &hcl.Diagnostic{ 362 Severity: hcl.DiagError, 363 Summary: "Duplicate backend configuration", 364 Detail: fmt.Sprintf("Each override file may have only one backend configuration. A backend was previously configured at %s.", file.Backends[0].DeclRange), 365 Subject: &file.Backends[1].DeclRange, 366 }) 367 } 368 } 369 370 for _, pc := range file.ProviderConfigs { 371 key := pc.moduleUniqueKey() 372 existing, exists := m.ProviderConfigs[key] 373 if pc.Alias == "" { 374 // We allow overriding a non-existing _default_ provider configuration 375 // because the user model is that an absent provider configuration 376 // implies an empty provider configuration, which is what the user 377 // is therefore overriding here. 378 if exists { 379 mergeDiags := existing.merge(pc) 380 diags = append(diags, mergeDiags...) 381 } else { 382 m.ProviderConfigs[key] = pc 383 } 384 } else { 385 // For aliased providers, there must be a base configuration to 386 // override. This allows us to detect and report alias typos 387 // that might otherwise cause the override to not apply. 388 if !exists { 389 diags = append(diags, &hcl.Diagnostic{ 390 Severity: hcl.DiagError, 391 Summary: "Missing base provider configuration for override", 392 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), 393 Subject: &pc.DeclRange, 394 }) 395 continue 396 } 397 mergeDiags := existing.merge(pc) 398 diags = append(diags, mergeDiags...) 399 } 400 } 401 402 for _, v := range file.Variables { 403 existing, exists := m.Variables[v.Name] 404 if !exists { 405 diags = append(diags, &hcl.Diagnostic{ 406 Severity: hcl.DiagError, 407 Summary: "Missing base variable declaration to override", 408 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), 409 Subject: &v.DeclRange, 410 }) 411 continue 412 } 413 mergeDiags := existing.merge(v) 414 diags = append(diags, mergeDiags...) 415 } 416 417 for _, l := range file.Locals { 418 existing, exists := m.Locals[l.Name] 419 if !exists { 420 diags = append(diags, &hcl.Diagnostic{ 421 Severity: hcl.DiagError, 422 Summary: "Missing base local value definition to override", 423 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), 424 Subject: &l.DeclRange, 425 }) 426 continue 427 } 428 mergeDiags := existing.merge(l) 429 diags = append(diags, mergeDiags...) 430 } 431 432 for _, o := range file.Outputs { 433 existing, exists := m.Outputs[o.Name] 434 if !exists { 435 diags = append(diags, &hcl.Diagnostic{ 436 Severity: hcl.DiagError, 437 Summary: "Missing base output definition to override", 438 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), 439 Subject: &o.DeclRange, 440 }) 441 continue 442 } 443 mergeDiags := existing.merge(o) 444 diags = append(diags, mergeDiags...) 445 } 446 447 for _, mc := range file.ModuleCalls { 448 existing, exists := m.ModuleCalls[mc.Name] 449 if !exists { 450 diags = append(diags, &hcl.Diagnostic{ 451 Severity: hcl.DiagError, 452 Summary: "Missing module call to override", 453 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), 454 Subject: &mc.DeclRange, 455 }) 456 continue 457 } 458 mergeDiags := existing.merge(mc) 459 diags = append(diags, mergeDiags...) 460 } 461 462 for _, r := range file.ManagedResources { 463 key := r.moduleUniqueKey() 464 existing, exists := m.ManagedResources[key] 465 if !exists { 466 diags = append(diags, &hcl.Diagnostic{ 467 Severity: hcl.DiagError, 468 Summary: "Missing resource to override", 469 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), 470 Subject: &r.DeclRange, 471 }) 472 continue 473 } 474 mergeDiags := existing.merge(r, m.ProviderRequirements.RequiredProviders) 475 diags = append(diags, mergeDiags...) 476 } 477 478 for _, r := range file.DataResources { 479 key := r.moduleUniqueKey() 480 existing, exists := m.DataResources[key] 481 if !exists { 482 diags = append(diags, &hcl.Diagnostic{ 483 Severity: hcl.DiagError, 484 Summary: "Missing data resource to override", 485 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), 486 Subject: &r.DeclRange, 487 }) 488 continue 489 } 490 mergeDiags := existing.merge(r, m.ProviderRequirements.RequiredProviders) 491 diags = append(diags, mergeDiags...) 492 } 493 494 for _, m := range file.Moved { 495 diags = append(diags, &hcl.Diagnostic{ 496 Severity: hcl.DiagError, 497 Summary: "Cannot override 'moved' blocks", 498 Detail: "Records of moved objects can appear only in normal files, not in override files.", 499 Subject: m.DeclRange.Ptr(), 500 }) 501 } 502 503 return diags 504 } 505 506 // gatherProviderLocalNames is a helper function that populatesA a map of 507 // provider FQNs -> provider local names. This information is useful for 508 // user-facing output, which should include both the FQN and LocalName. It must 509 // only be populated after the module has been parsed. 510 func (m *Module) gatherProviderLocalNames() { 511 providers := make(map[addrs.Provider]string) 512 for k, v := range m.ProviderRequirements.RequiredProviders { 513 providers[v.Type] = k 514 } 515 m.ProviderLocalNames = providers 516 } 517 518 // LocalNameForProvider returns the module-specific user-supplied local name for 519 // a given provider FQN, or the default local name if none was supplied. 520 func (m *Module) LocalNameForProvider(p addrs.Provider) string { 521 if existing, exists := m.ProviderLocalNames[p]; exists { 522 return existing 523 } else { 524 // If there isn't a map entry, fall back to the default: 525 // Type = LocalName 526 return p.Type 527 } 528 } 529 530 // ProviderForLocalConfig returns the provider FQN for a given 531 // LocalProviderConfig, based on its local name. 532 func (m *Module) ProviderForLocalConfig(pc addrs.LocalProviderConfig) addrs.Provider { 533 return m.ImpliedProviderForUnqualifiedType(pc.LocalName) 534 } 535 536 // ImpliedProviderForUnqualifiedType returns the provider FQN for a given type, 537 // first by looking up the type in the provider requirements map, and falling 538 // back to an implied default provider. 539 // 540 // The intended behaviour is that configuring a provider with local name "foo" 541 // in a required_providers block will result in resources with type "foo" using 542 // that provider. 543 func (m *Module) ImpliedProviderForUnqualifiedType(pType string) addrs.Provider { 544 if provider, exists := m.ProviderRequirements.RequiredProviders[pType]; exists { 545 return provider.Type 546 } 547 return addrs.ImpliedProviderForUnqualifiedType(pType) 548 }