github.com/opentofu/opentofu@v1.7.1/internal/configs/provider_validation.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package configs 7 8 import ( 9 "fmt" 10 "sort" 11 "strings" 12 13 "github.com/hashicorp/hcl/v2" 14 15 "github.com/opentofu/opentofu/internal/addrs" 16 ) 17 18 // validateProviderConfigsForTests performs the same role as 19 // validateProviderConfigs except it validates the providers configured within 20 // test files. 21 // 22 // To do this is calls out to validateProviderConfigs for each run block that 23 // has ConfigUnderTest set. 24 // 25 // In addition, for each run block that executes against the main config it 26 // validates the providers the run block wants to use match the providers 27 // specified in the main configuration. It does this without reaching out to 28 // validateProviderConfigs because the main configuration has already been 29 // validated, and we don't want to redo all the work that happens in that 30 // function. So, we only validate the providers our test files define match 31 // the providers required by the main configuration. 32 // 33 // This function does some fairly controversial conversions into structures 34 // expected by validateProviderConfigs but since we're just using it for 35 // validation we'll still get the correct error messages, and we can make the 36 // declaration ranges line up sensibly so we'll even get good diagnostics. 37 func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) { 38 39 for name, test := range cfg.Module.Tests { 40 for _, run := range test.Runs { 41 42 if run.ConfigUnderTest == nil { 43 // Then we're calling out to the main configuration under test. 44 // 45 // We just need to make sure that the providers we are setting 46 // actually match the providers in the configuration. The main 47 // configuration has already been validated, so we don't need to 48 // do the whole thing again. 49 50 if len(run.Providers) > 0 { 51 // This is the easy case, we can just validate that the 52 // provider types match. 53 for _, provider := range run.Providers { 54 55 parentType, childType := provider.InParent.providerType, provider.InChild.providerType 56 if parentType.IsZero() { 57 parentType = addrs.NewDefaultProvider(provider.InParent.Name) 58 } 59 if childType.IsZero() { 60 childType = addrs.NewDefaultProvider(provider.InChild.Name) 61 } 62 63 if !childType.Equals(parentType) { 64 diags = append(diags, &hcl.Diagnostic{ 65 Severity: hcl.DiagError, 66 Summary: "Provider type mismatch", 67 Detail: fmt.Sprintf( 68 "The local name %q in %s represents provider %q, but %q in the root module represents %q.\n\nThis means the provider definition for %q within %s, or other provider definitions with the same name, have been referenced by multiple run blocks and assigned to different provider types.", 69 provider.InParent.Name, name, parentType, provider.InChild.Name, childType, provider.InParent.Name, name), 70 Subject: provider.InParent.NameRange.Ptr(), 71 }) 72 } 73 } 74 75 // Skip to the next file, we only need to verify the types 76 // specified here. 77 continue 78 } 79 80 // Otherwise, we need to verify that the providers required by 81 // the configuration match the types defined by our test file. 82 83 for _, requirement := range cfg.Module.ProviderRequirements.RequiredProviders { 84 if provider, exists := test.Providers[requirement.Name]; exists { 85 86 providerType := provider.providerType 87 if providerType.IsZero() { 88 providerType = addrs.NewDefaultProvider(provider.Name) 89 } 90 91 if !providerType.Equals(requirement.Type) { 92 diags = append(diags, &hcl.Diagnostic{ 93 Severity: hcl.DiagError, 94 Summary: "Provider type mismatch", 95 Detail: fmt.Sprintf( 96 "The provider %q in %s represents provider %q, but %q in the root module represents %q.\n\nThis means the provider definition for %q within %s, or other provider definitions with the same name, have been referenced by multiple run blocks and assigned to different provider types.", 97 provider.moduleUniqueKey(), name, providerType, requirement.Name, requirement.Type, provider.moduleUniqueKey(), name), 98 Subject: provider.DeclRange.Ptr(), 99 }) 100 } 101 } 102 103 for _, alias := range requirement.Aliases { 104 if provider, exists := test.Providers[alias.StringCompact()]; exists { 105 106 providerType := provider.providerType 107 if providerType.IsZero() { 108 providerType = addrs.NewDefaultProvider(provider.Name) 109 } 110 111 if !providerType.Equals(requirement.Type) { 112 diags = append(diags, &hcl.Diagnostic{ 113 Severity: hcl.DiagError, 114 Summary: "Provider type mismatch", 115 Detail: fmt.Sprintf( 116 "The provider %q in %s represents provider %q, but %q in the root module represents %q.\n\nThis means the provider definition for %q within %s, or other provider definitions with the same name, have been referenced by multiple run blocks and assigned to different provider types.", 117 provider.moduleUniqueKey(), name, providerType, alias.StringCompact(), requirement.Type, provider.moduleUniqueKey(), name), 118 Subject: provider.DeclRange.Ptr(), 119 }) 120 } 121 } 122 } 123 } 124 125 for _, provider := range cfg.Module.ProviderConfigs { 126 127 providerType := provider.providerType 128 if providerType.IsZero() { 129 providerType = addrs.NewDefaultProvider(provider.Name) 130 } 131 132 if testProvider, exists := test.Providers[provider.moduleUniqueKey()]; exists { 133 134 testProviderType := testProvider.providerType 135 if testProviderType.IsZero() { 136 testProviderType = addrs.NewDefaultProvider(testProvider.Name) 137 } 138 139 if !providerType.Equals(testProviderType) { 140 diags = append(diags, &hcl.Diagnostic{ 141 Severity: hcl.DiagError, 142 Summary: "Provider type mismatch", 143 Detail: fmt.Sprintf( 144 "The provider %q in %s represents provider %q, but %q in the root module represents %q.\n\nThis means the provider definition for %q within %s has been referenced by multiple run blocks and assigned to different provider types.", 145 testProvider.moduleUniqueKey(), name, testProviderType, provider.moduleUniqueKey(), providerType, testProvider.moduleUniqueKey(), name), 146 Subject: testProvider.DeclRange.Ptr(), 147 }) 148 } 149 } 150 } 151 152 } else { 153 // Then we're executing another module. We'll just call out to 154 // validateProviderConfigs and let it do the whole thing. 155 156 providers := run.Providers 157 if len(providers) == 0 { 158 // If the test run didn't provide us a subset of providers 159 // to use, we'll build our own. This is so that we can fit 160 // into the schema expected by validateProviderConfigs. 161 162 matchedProviders := make(map[string]PassedProviderConfig) 163 164 // We'll go over all the requirements in the module first 165 // and see if we have defined any providers for that 166 // requirement. If we have, then we'll take not of that. 167 168 for _, requirement := range cfg.Module.ProviderRequirements.RequiredProviders { 169 170 if provider, exists := test.Providers[requirement.Name]; exists { 171 matchedProviders[requirement.Name] = PassedProviderConfig{ 172 InChild: &ProviderConfigRef{ 173 Name: requirement.Name, 174 NameRange: requirement.DeclRange, 175 providerType: requirement.Type, 176 }, 177 InParent: &ProviderConfigRef{ 178 Name: provider.Name, 179 NameRange: provider.NameRange, 180 Alias: provider.Alias, 181 AliasRange: provider.AliasRange, 182 providerType: provider.providerType, 183 }, 184 } 185 } 186 187 // Also, remember to check for any aliases the module 188 // expects. 189 190 for _, alias := range requirement.Aliases { 191 key := alias.StringCompact() 192 193 if provider, exists := test.Providers[key]; exists { 194 matchedProviders[key] = PassedProviderConfig{ 195 InChild: &ProviderConfigRef{ 196 Name: requirement.Name, 197 NameRange: requirement.DeclRange, 198 Alias: alias.Alias, 199 AliasRange: requirement.DeclRange.Ptr(), 200 providerType: requirement.Type, 201 }, 202 InParent: &ProviderConfigRef{ 203 Name: provider.Name, 204 NameRange: provider.NameRange, 205 Alias: provider.Alias, 206 AliasRange: provider.AliasRange, 207 providerType: provider.providerType, 208 }, 209 } 210 } 211 212 } 213 214 } 215 216 // Next, we'll look at any providers the module has defined 217 // directly. If we have an equivalent provider in the test 218 // file then we'll add that in to override it. If the module 219 // has both built a required providers block and a provider 220 // block for the same provider, we'll overwrite the one we 221 // made for the requirement provider. We get more precise 222 // DeclRange objects from provider blocks so it makes for 223 // better error messages to use these. 224 225 for _, provider := range cfg.Module.ProviderConfigs { 226 key := provider.moduleUniqueKey() 227 228 if testProvider, exists := test.Providers[key]; exists { 229 matchedProviders[key] = PassedProviderConfig{ 230 InChild: &ProviderConfigRef{ 231 Name: provider.Name, 232 NameRange: provider.DeclRange, 233 Alias: provider.Alias, 234 AliasRange: provider.DeclRange.Ptr(), 235 providerType: provider.providerType, 236 }, 237 InParent: &ProviderConfigRef{ 238 Name: testProvider.Name, 239 NameRange: testProvider.NameRange, 240 Alias: testProvider.Alias, 241 AliasRange: testProvider.AliasRange, 242 providerType: testProvider.providerType, 243 }, 244 } 245 } 246 } 247 248 // Last thing to do here is add them into the actual 249 // providers list that is going into the module call below. 250 for _, provider := range matchedProviders { 251 providers = append(providers, provider) 252 } 253 254 } 255 256 // Let's make a little fake module call that we can use to call 257 // into validateProviderConfigs. 258 mc := &ModuleCall{ 259 Name: run.Name, 260 SourceAddr: run.Module.Source, 261 SourceAddrRange: run.Module.SourceDeclRange, 262 SourceSet: true, 263 Version: run.Module.Version, 264 Providers: providers, 265 DeclRange: run.Module.DeclRange, 266 } 267 268 diags = append(diags, validateProviderConfigs(mc, run.ConfigUnderTest, nil)...) 269 } 270 } 271 } 272 273 return diags 274 } 275 276 // validateProviderConfigs walks the full configuration tree from the root 277 // module outward, static validation rules to the various combinations of 278 // provider configuration, required_providers values, and module call providers 279 // mappings. 280 // 281 // To retain compatibility with previous terraform versions, empty "proxy 282 // provider blocks" are still allowed within modules, though they will 283 // generate warnings when the configuration is loaded. The new validation 284 // however will generate an error if a suitable provider configuration is not 285 // passed in through the module call. 286 // 287 // The call argument is the ModuleCall for the provided Config cfg. The 288 // noProviderConfigRange argument is passed down the call stack, indicating 289 // that the module call, or a parent module call, has used a feature (at the 290 // specified source location) that precludes providers from being configured at 291 // all within the module. 292 func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConfigRange *hcl.Range) (diags hcl.Diagnostics) { 293 mod := cfg.Module 294 295 for name, child := range cfg.Children { 296 mc := mod.ModuleCalls[name] 297 childNoProviderConfigRange := noProviderConfigRange 298 // if the module call has any of count, for_each or depends_on, 299 // providers are prohibited from being configured in this module, or 300 // any module beneath this module. 301 switch { 302 case mc.Count != nil: 303 childNoProviderConfigRange = mc.Count.Range().Ptr() 304 case mc.ForEach != nil: 305 childNoProviderConfigRange = mc.ForEach.Range().Ptr() 306 case mc.DependsOn != nil: 307 if len(mc.DependsOn) > 0 { 308 childNoProviderConfigRange = mc.DependsOn[0].SourceRange().Ptr() 309 } else { 310 // Weird! We'll just use the call itself, then. 311 childNoProviderConfigRange = mc.DeclRange.Ptr() 312 } 313 } 314 diags = append(diags, validateProviderConfigs(mc, child, childNoProviderConfigRange)...) 315 } 316 317 // the set of provider configuration names passed into the module, with the 318 // source range of the provider assignment in the module call. 319 passedIn := map[string]PassedProviderConfig{} 320 321 // the set of empty configurations that could be proxy configurations, with 322 // the source range of the empty configuration block. 323 emptyConfigs := map[string]hcl.Range{} 324 325 // the set of provider with a defined configuration, with the source range 326 // of the configuration block declaration. 327 configured := map[string]hcl.Range{} 328 329 // the set of configuration_aliases defined in the required_providers 330 // block, with the fully qualified provider type. 331 configAliases := map[string]addrs.AbsProviderConfig{} 332 333 // the set of provider names defined in the required_providers block, and 334 // their provider types. 335 localNames := map[string]addrs.Provider{} 336 337 for _, pc := range mod.ProviderConfigs { 338 name := providerName(pc.Name, pc.Alias) 339 // Validate the config against an empty schema to see if it's empty. 340 _, pcConfigDiags := pc.Config.Content(&hcl.BodySchema{}) 341 if pcConfigDiags.HasErrors() || pc.Version.Required != nil { 342 configured[name] = pc.DeclRange 343 } else { 344 emptyConfigs[name] = pc.DeclRange 345 } 346 } 347 348 if mod.ProviderRequirements != nil { 349 // Track all known local types too to ensure we don't have duplicated 350 // with different local names. 351 localTypes := map[string]bool{} 352 353 // check for duplicate requirements of the same type 354 for _, req := range mod.ProviderRequirements.RequiredProviders { 355 if localTypes[req.Type.String()] { 356 // find the last declaration to give a better error 357 prevDecl := "" 358 for localName, typ := range localNames { 359 if typ.Equals(req.Type) { 360 prevDecl = localName 361 } 362 } 363 364 diags = append(diags, &hcl.Diagnostic{ 365 Severity: hcl.DiagWarning, 366 Summary: "Duplicate required provider", 367 Detail: fmt.Sprintf( 368 "Provider %s with the local name %q was previously required as %q. A provider can only be required once within required_providers.", 369 req.Type.ForDisplay(), req.Name, prevDecl, 370 ), 371 Subject: &req.DeclRange, 372 }) 373 } else if addrs.IsDefaultProvider(req.Type) { 374 // Now check for possible implied duplicates, where a provider 375 // block uses a default namespaced provider, but that provider 376 // was required via a different name. 377 impliedLocalName := req.Type.Type 378 // We have to search through the configs for a match, since the keys contains any aliases. 379 for _, pc := range mod.ProviderConfigs { 380 if pc.Name == impliedLocalName && req.Name != impliedLocalName { 381 diags = append(diags, &hcl.Diagnostic{ 382 Severity: hcl.DiagWarning, 383 Summary: "Duplicate required provider", 384 Detail: fmt.Sprintf( 385 "Provider %s with the local name %q was implicitly required via a configuration block as %q. The provider configuration block name must match the name used in required_providers.", 386 req.Type.ForDisplay(), req.Name, req.Type.Type, 387 ), 388 Subject: &req.DeclRange, 389 }) 390 break 391 } 392 } 393 } 394 395 localTypes[req.Type.String()] = true 396 397 localNames[req.Name] = req.Type 398 for _, alias := range req.Aliases { 399 addr := addrs.AbsProviderConfig{ 400 Module: cfg.Path, 401 Provider: req.Type, 402 Alias: alias.Alias, 403 } 404 configAliases[providerName(alias.LocalName, alias.Alias)] = addr 405 } 406 } 407 } 408 409 checkImpliedProviderNames := func(resourceConfigs map[string]*Resource) { 410 // Now that we have all the provider configs and requirements validated, 411 // check for any resources which use an implied localname which doesn't 412 // match that of required_providers 413 for _, r := range resourceConfigs { 414 // We're looking for resources with no specific provider reference 415 if r.ProviderConfigRef != nil { 416 continue 417 } 418 419 localName := r.Addr().ImpliedProvider() 420 421 _, err := addrs.ParseProviderPart(localName) 422 if err != nil { 423 diags = append(diags, &hcl.Diagnostic{ 424 Severity: hcl.DiagError, 425 Summary: "Invalid provider local name", 426 Detail: fmt.Sprintf("%q is an invalid implied provider local name: %s", localName, err), 427 Subject: r.DeclRange.Ptr(), 428 }) 429 continue 430 } 431 432 if _, ok := localNames[localName]; ok { 433 // OK, this was listed directly in the required_providers 434 continue 435 } 436 437 defAddr := addrs.ImpliedProviderForUnqualifiedType(localName) 438 439 // Now make sure we don't have the same provider required under a 440 // different name. 441 for prevLocalName, addr := range localNames { 442 if addr.Equals(defAddr) { 443 diags = append(diags, &hcl.Diagnostic{ 444 Severity: hcl.DiagWarning, 445 Summary: "Duplicate required provider", 446 Detail: fmt.Sprintf( 447 "Provider %q was implicitly required via resource %q, but listed in required_providers as %q. Either the local name in required_providers must match the resource name, or the %q provider must be assigned within the resource block.", 448 defAddr, r.Addr(), prevLocalName, prevLocalName, 449 ), 450 Subject: &r.DeclRange, 451 }) 452 } 453 } 454 } 455 } 456 checkImpliedProviderNames(mod.ManagedResources) 457 checkImpliedProviderNames(mod.DataResources) 458 459 // collect providers passed from the parent 460 if parentCall != nil { 461 for _, passed := range parentCall.Providers { 462 name := providerName(passed.InChild.Name, passed.InChild.Alias) 463 passedIn[name] = passed 464 } 465 } 466 467 parentModuleText := "the root module" 468 moduleText := "the root module" 469 if !cfg.Path.IsRoot() { 470 moduleText = cfg.Path.String() 471 if parent := cfg.Path.Parent(); !parent.IsRoot() { 472 // module address are prefixed with `module.` 473 parentModuleText = parent.String() 474 } 475 } 476 477 // Verify that any module calls only refer to named providers, and that 478 // those providers will have a configuration at runtime. This way we can 479 // direct users where to add the missing configuration, because the runtime 480 // error is only "missing provider X". 481 for _, modCall := range mod.ModuleCalls { 482 for _, passed := range modCall.Providers { 483 // aliased providers are handled more strictly, and are never 484 // inherited, so they are validated within modules further down. 485 // Skip these checks to prevent redundant diagnostics. 486 if passed.InParent.Alias != "" { 487 continue 488 } 489 490 name := passed.InParent.String() 491 _, confOK := configured[name] 492 _, localOK := localNames[name] 493 _, passedOK := passedIn[name] 494 495 // This name was not declared somewhere within in the 496 // configuration. We ignore empty configs, because they will 497 // already produce a warning. 498 if !(confOK || localOK) { 499 defAddr := addrs.NewDefaultProvider(name) 500 diags = append(diags, &hcl.Diagnostic{ 501 Severity: hcl.DiagWarning, 502 Summary: "Reference to undefined provider", 503 Detail: fmt.Sprintf( 504 "There is no explicit declaration for local provider name %q in %s, so OpenTofu is assuming you mean to pass a configuration for provider %q.\n\nTo clarify your intent and silence this warning, add to %s a required_providers entry named %q with source = %q, or a different source address if appropriate.", 505 name, moduleText, defAddr.ForDisplay(), 506 parentModuleText, name, defAddr.ForDisplay(), 507 ), 508 Subject: &passed.InParent.NameRange, 509 }) 510 continue 511 } 512 513 // Now we may have named this provider within the module, but 514 // there won't be a configuration available at runtime if the 515 // parent module did not pass one in. 516 if !cfg.Path.IsRoot() && !(confOK || passedOK) { 517 defAddr := addrs.NewDefaultProvider(name) 518 diags = append(diags, &hcl.Diagnostic{ 519 Severity: hcl.DiagWarning, 520 Summary: "Missing required provider configuration", 521 Detail: fmt.Sprintf( 522 "The configuration for %s expects to inherit a configuration for provider %s with local name %q, but %s doesn't pass a configuration under that name.\n\nTo satisfy this requirement, add an entry for %q to the \"providers\" argument in the module %q block.", 523 moduleText, defAddr.ForDisplay(), name, parentModuleText, 524 name, parentCall.Name, 525 ), 526 Subject: parentCall.DeclRange.Ptr(), 527 }) 528 } 529 } 530 } 531 532 if cfg.Path.IsRoot() { 533 // nothing else to do in the root module 534 return diags 535 } 536 537 // there cannot be any configurations if no provider config is allowed 538 if len(configured) > 0 && noProviderConfigRange != nil { 539 // We report this from the perspective of the use of count, for_each, 540 // or depends_on rather than from inside the module, because the 541 // recipient of this message is more likely to be the author of the 542 // calling module (trying to use an older module that hasn't been 543 // updated yet) than of the called module. 544 diags = append(diags, &hcl.Diagnostic{ 545 Severity: hcl.DiagError, 546 Summary: "Module is incompatible with count, for_each, and depends_on", 547 Detail: fmt.Sprintf( 548 "The module at %s is a legacy module which contains its own local provider configurations, and so calls to it may not use the count, for_each, or depends_on arguments.\n\nIf you also control the module %q, consider updating this module to instead expect provider configurations to be passed by its caller.", 549 cfg.Path, cfg.SourceAddr, 550 ), 551 Subject: noProviderConfigRange, 552 }) 553 } 554 555 // now check that the user is not attempting to override a config 556 for name := range configured { 557 if passed, ok := passedIn[name]; ok { 558 diags = append(diags, &hcl.Diagnostic{ 559 Severity: hcl.DiagError, 560 Summary: "Cannot override provider configuration", 561 Detail: fmt.Sprintf( 562 "The configuration of %s has its own local configuration for %s, and so it cannot accept an overridden configuration provided by %s.", 563 moduleText, name, parentModuleText, 564 ), 565 Subject: &passed.InChild.NameRange, 566 }) 567 } 568 } 569 570 // A declared alias requires either a matching configuration within the 571 // module, or one must be passed in. 572 for name, providerAddr := range configAliases { 573 _, confOk := configured[name] 574 _, passedOk := passedIn[name] 575 576 if confOk || passedOk { 577 continue 578 } 579 580 diags = append(diags, &hcl.Diagnostic{ 581 Severity: hcl.DiagError, 582 Summary: "Missing required provider configuration", 583 Detail: fmt.Sprintf( 584 "The child module requires an additional configuration for provider %s, with the local name %q.\n\nRefer to the module's documentation to understand the intended purpose of this additional provider configuration, and then add an entry for %s in the \"providers\" meta-argument in the module block to choose which provider configuration the module should use for that purpose.", 585 providerAddr.Provider.ForDisplay(), name, 586 name, 587 ), 588 Subject: &parentCall.DeclRange, 589 }) 590 } 591 592 // You cannot pass in a provider that cannot be used 593 for name, passed := range passedIn { 594 childTy := passed.InChild.providerType 595 // get a default type if there was none set 596 if childTy.IsZero() { 597 // This means the child module is only using an inferred 598 // provider type. We allow this but will generate a warning to 599 // declare provider_requirements below. 600 childTy = addrs.NewDefaultProvider(passed.InChild.Name) 601 } 602 603 providerAddr := addrs.AbsProviderConfig{ 604 Module: cfg.Path, 605 Provider: childTy, 606 Alias: passed.InChild.Alias, 607 } 608 609 localAddr, localName := localNames[name] 610 if localName { 611 providerAddr.Provider = localAddr 612 } 613 614 aliasAddr, configAlias := configAliases[name] 615 if configAlias { 616 providerAddr = aliasAddr 617 } 618 619 _, emptyConfig := emptyConfigs[name] 620 621 if !(localName || configAlias || emptyConfig) { 622 623 // we still allow default configs, so switch to a warning if the incoming provider is a default 624 if addrs.IsDefaultProvider(providerAddr.Provider) { 625 diags = append(diags, &hcl.Diagnostic{ 626 Severity: hcl.DiagWarning, 627 Summary: "Reference to undefined provider", 628 Detail: fmt.Sprintf( 629 "There is no explicit declaration for local provider name %q in %s, so OpenTofu is assuming you mean to pass a configuration for %q.\n\nIf you also control the child module, add a required_providers entry named %q with the source address %q.", 630 name, moduleText, providerAddr.Provider.ForDisplay(), 631 name, providerAddr.Provider.ForDisplay(), 632 ), 633 Subject: &passed.InChild.NameRange, 634 }) 635 } else { 636 diags = append(diags, &hcl.Diagnostic{ 637 Severity: hcl.DiagError, 638 Summary: "Reference to undefined provider", 639 Detail: fmt.Sprintf( 640 "The child module does not declare any provider requirement with the local name %q.\n\nIf you also control the child module, you can add a required_providers entry named %q with the source address %q to accept this provider configuration.", 641 name, name, providerAddr.Provider.ForDisplay(), 642 ), 643 Subject: &passed.InChild.NameRange, 644 }) 645 } 646 } 647 648 // The provider being passed in must also be of the correct type. 649 pTy := passed.InParent.providerType 650 if pTy.IsZero() { 651 // While we would like to ensure required_providers exists here, 652 // implied default configuration is still allowed. 653 pTy = addrs.NewDefaultProvider(passed.InParent.Name) 654 } 655 656 // use the full address for a nice diagnostic output 657 parentAddr := addrs.AbsProviderConfig{ 658 Module: cfg.Parent.Path, 659 Provider: pTy, 660 Alias: passed.InParent.Alias, 661 } 662 663 if cfg.Parent.Module.ProviderRequirements != nil { 664 req, defined := cfg.Parent.Module.ProviderRequirements.RequiredProviders[name] 665 if defined { 666 parentAddr.Provider = req.Type 667 } 668 } 669 670 if !providerAddr.Provider.Equals(parentAddr.Provider) { 671 // If this module declares the same source address for a different 672 // local name then we'll prefer to suggest changing to match 673 // the child module's chosen name, assuming that it was the local 674 // name that was wrong rather than the source address. 675 var otherLocalName string 676 for localName, sourceAddr := range localNames { 677 if sourceAddr.Equals(parentAddr.Provider) { 678 otherLocalName = localName 679 break 680 } 681 } 682 683 const errSummary = "Provider type mismatch" 684 if otherLocalName != "" { 685 diags = append(diags, &hcl.Diagnostic{ 686 Severity: hcl.DiagError, 687 Summary: errSummary, 688 Detail: fmt.Sprintf( 689 "The assigned configuration is for provider %q, but local name %q in %s represents %q.\n\nTo pass this configuration to the child module, use the local name %q instead.", 690 parentAddr.Provider.ForDisplay(), passed.InChild.Name, 691 parentModuleText, providerAddr.Provider.ForDisplay(), 692 otherLocalName, 693 ), 694 Subject: &passed.InChild.NameRange, 695 }) 696 } else { 697 // If there is no declared requirement for the provider the 698 // caller is trying to pass under any name then we'll instead 699 // report it as an unsuitable configuration to pass into the 700 // child module's provider configuration slot. 701 diags = append(diags, &hcl.Diagnostic{ 702 Severity: hcl.DiagError, 703 Summary: errSummary, 704 Detail: fmt.Sprintf( 705 "The local name %q in %s represents provider %q, but %q in %s represents %q.\n\nEach provider has its own distinct configuration schema and provider types, so this module's %q can be assigned only a configuration for %s, which is not required by %s.", 706 passed.InParent, parentModuleText, parentAddr.Provider.ForDisplay(), 707 passed.InChild, moduleText, providerAddr.Provider.ForDisplay(), 708 passed.InChild, providerAddr.Provider.ForDisplay(), 709 moduleText, 710 ), 711 Subject: passed.InParent.NameRange.Ptr(), 712 }) 713 } 714 } 715 } 716 717 // Empty configurations are no longer needed. Since the replacement for 718 // this calls for one entry per provider rather than one entry per 719 // provider _configuration_, we'll first gather them up by provider 720 // and then report a single warning for each, whereby we can show a direct 721 // example of what the replacement should look like. 722 type ProviderReqSuggestion struct { 723 SourceAddr addrs.Provider 724 SourceRanges []hcl.Range 725 RequiredConfigs []string 726 AliasCount int 727 } 728 providerReqSuggestions := make(map[string]*ProviderReqSuggestion) 729 for name, src := range emptyConfigs { 730 providerLocalName := name 731 if idx := strings.IndexByte(providerLocalName, '.'); idx >= 0 { 732 providerLocalName = providerLocalName[:idx] 733 } 734 735 sourceAddr, ok := localNames[name] 736 if !ok { 737 sourceAddr = addrs.NewDefaultProvider(providerLocalName) 738 } 739 740 suggestion := providerReqSuggestions[providerLocalName] 741 if suggestion == nil { 742 providerReqSuggestions[providerLocalName] = &ProviderReqSuggestion{ 743 SourceAddr: sourceAddr, 744 } 745 suggestion = providerReqSuggestions[providerLocalName] 746 } 747 748 if providerLocalName != name { 749 // It's an aliased provider config, then. 750 suggestion.AliasCount++ 751 } 752 753 suggestion.RequiredConfigs = append(suggestion.RequiredConfigs, name) 754 suggestion.SourceRanges = append(suggestion.SourceRanges, src) 755 } 756 for name, suggestion := range providerReqSuggestions { 757 var buf strings.Builder 758 759 fmt.Fprintf( 760 &buf, 761 "Earlier versions of OpenTofu used empty provider blocks (\"proxy provider configurations\") for child modules to declare their need to be passed a provider configuration by their callers. That approach was ambiguous and is now deprecated.\n\nIf you control this module, you can migrate to the new declaration syntax by removing all of the empty provider %q blocks and then adding or updating an entry like the following to the required_providers block of %s:\n", 762 name, moduleText, 763 ) 764 fmt.Fprintf(&buf, " %s = {\n", name) 765 fmt.Fprintf(&buf, " source = %q\n", suggestion.SourceAddr.ForDisplay()) 766 if suggestion.AliasCount > 0 { 767 // A lexical sort is fine because all of these strings are 768 // guaranteed to start with the same provider local name, and 769 // so we're only really sorting by the alias part. 770 sort.Strings(suggestion.RequiredConfigs) 771 fmt.Fprintln(&buf, " configuration_aliases = [") 772 for _, addrStr := range suggestion.RequiredConfigs { 773 fmt.Fprintf(&buf, " %s,\n", addrStr) 774 } 775 fmt.Fprintln(&buf, " ]") 776 777 } 778 fmt.Fprint(&buf, " }") 779 780 // We're arbitrarily going to just take the one source range that 781 // sorts earliest here. Multiple should be rare, so this is only to 782 // ensure that we produce a deterministic result in the edge case. 783 sort.Slice(suggestion.SourceRanges, func(i, j int) bool { 784 return suggestion.SourceRanges[i].String() < suggestion.SourceRanges[j].String() 785 }) 786 diags = append(diags, &hcl.Diagnostic{ 787 Severity: hcl.DiagWarning, 788 Summary: "Redundant empty provider block", 789 Detail: buf.String(), 790 Subject: suggestion.SourceRanges[0].Ptr(), 791 }) 792 } 793 794 return diags 795 } 796 797 func providerName(name, alias string) string { 798 if alias != "" { 799 name = name + "." + alias 800 } 801 return name 802 }