diff --git a/internal/configs/config_test.go b/internal/configs/config_test.go index 1fcdfc877045..b5360278df7b 100644 --- a/internal/configs/config_test.go +++ b/internal/configs/config_test.go @@ -160,7 +160,7 @@ func TestConfigProviderRequirements(t *testing.T) { func TestConfigProviderRequirementsDuplicate(t *testing.T) { _, diags := testNestedModuleConfigFromDir(t, "testdata/duplicate-local-name") - assertDiagnosticCount(t, diags, 2) + assertDiagnosticCount(t, diags, 3) assertDiagnosticSummary(t, diags, "Duplicate required provider") } diff --git a/internal/configs/provider_validation.go b/internal/configs/provider_validation.go index 87ed6a71807b..a6feb4279eaf 100644 --- a/internal/configs/provider_validation.go +++ b/internal/configs/provider_validation.go @@ -118,7 +118,7 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf Severity: hcl.DiagWarning, Summary: "Duplicate required provider", Detail: fmt.Sprintf( - "Provider %s with the local name %q was implicitly required via a configuration block as %q. Make sure the provider configuration block name matches the name used in required_providers.", + "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.", req.Type.ForDisplay(), req.Name, req.Type.Type, ), Subject: &req.DeclRange, @@ -142,6 +142,44 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf } } + checkImpliedProviderNames := func(resourceConfigs map[string]*Resource) { + // Now that we have all the provider configs and requirements validated, + // check for any resources which use an implied localname which doesn't + // match that of required_providers + for _, r := range resourceConfigs { + // We're looking for resources with no specific provider reference + if r.ProviderConfigRef != nil { + continue + } + + localName := r.Addr().ImpliedProvider() + if _, ok := localNames[localName]; ok { + // OK, this was listed directly in the required_providers + continue + } + + defAddr := addrs.ImpliedProviderForUnqualifiedType(localName) + + // Now make sure we don't have the same provider required under a + // different name. + for prevLocalName, addr := range localNames { + if addr.Equals(defAddr) { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "Duplicate required provider", + Detail: fmt.Sprintf( + "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.", + defAddr, r.Addr(), prevLocalName, prevLocalName, + ), + Subject: &r.DeclRange, + }) + } + } + } + } + checkImpliedProviderNames(mod.ManagedResources) + checkImpliedProviderNames(mod.DataResources) + // collect providers passed from the parent if parentCall != nil { for _, passed := range parentCall.Providers { diff --git a/internal/configs/testdata/duplicate-local-name/main.tf b/internal/configs/testdata/duplicate-local-name/main.tf index 1036599b940d..9bac5451cdf8 100644 --- a/internal/configs/testdata/duplicate-local-name/main.tf +++ b/internal/configs/testdata/duplicate-local-name/main.tf @@ -9,8 +9,15 @@ terraform { other = { source = "hashicorp/default" } + + wrong-name = { + source = "hashicorp/foo" + } } } provider "default" { } + +resource "foo_resource" { +}