Skip to content

Commit

Permalink
Check for duplicate types in required_providers
Browse files Browse the repository at this point in the history
Adding multiple local names for the same provider type in
required_providers was not prevented, which can lead to ambiguous
behavior in Terraform. Providers are always indexed by the providers
fully qualified name, so duplicate local names cannot be differentiated.
  • Loading branch information
jbardin committed Jun 10, 2022
1 parent 2f1a8bb commit 2581bc9
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 0 deletions.
6 changes: 6 additions & 0 deletions internal/configs/config_test.go
Expand Up @@ -158,6 +158,12 @@ func TestConfigProviderRequirements(t *testing.T) {
}
}

func TestConfigProviderRequirementsDuplicate(t *testing.T) {
_, diags := testNestedModuleConfigFromDir(t, "testdata/duplicate-local-name")
assertDiagnosticCount(t, diags, 2)
assertDiagnosticSummary(t, diags, "Duplicate required provider")
}

func TestConfigProviderRequirementsShallow(t *testing.T) {
cfg, diags := testNestedModuleConfigFromDir(t, "testdata/provider-reqs")
// TODO: Version Constraint Deprecation.
Expand Down
47 changes: 47 additions & 0 deletions internal/configs/provider_validation.go
Expand Up @@ -82,7 +82,54 @@ func validateProviderConfigs(parentCall *ModuleCall, cfg *Config, noProviderConf
}

if mod.ProviderRequirements != nil {
// Track all known local types too to ensure we don't have duplicated
// with different local names.
localTypes := map[string]bool{}

// check for duplicate requirements of the same type
for _, req := range mod.ProviderRequirements.RequiredProviders {
if localTypes[req.Type.String()] {
// find the last declaration to give a better error
prevDecl := ""
for localName, typ := range localNames {
if typ.Equals(req.Type) {
prevDecl = localName
}
}

diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning,
Summary: "Duplicate required provider",
Detail: fmt.Sprintf(
"Provider %s with the local name %q was previously required as %q. A provider can only be required once within required_providers.",
req.Type.ForDisplay(), req.Name, prevDecl,
),
Subject: &req.DeclRange,
})
} else if req.Type.IsDefault() {
// Now check for possible implied duplicates, where a provider
// block uses a default namespaced provider, but that provider
// was required via a different name.
impliedLocalName := req.Type.Type
// We have to search through the configs for a match, since the keys contains any aliases.
for _, pc := range mod.ProviderConfigs {
if pc.Name == impliedLocalName && req.Name != impliedLocalName {
diags = append(diags, &hcl.Diagnostic{
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.",
req.Type.ForDisplay(), req.Name, req.Type.Type,
),
Subject: &req.DeclRange,
})
break
}
}
}

localTypes[req.Type.String()] = true

localNames[req.Name] = req.Type
for _, alias := range req.Aliases {
addr := addrs.AbsProviderConfig{
Expand Down
16 changes: 16 additions & 0 deletions internal/configs/testdata/duplicate-local-name/main.tf
@@ -0,0 +1,16 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
dupe = {
source = "hashicorp/test"
}
other = {
source = "hashicorp/default"
}
}
}

provider "default" {
}

0 comments on commit 2581bc9

Please sign in to comment.