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