Check for duplicate types in required_providers

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.
This commit is contained in:
James Bardin 2022-06-09 14:00:10 -04:00
parent 2f1a8bbbd8
commit 2581bc93cb
3 changed files with 69 additions and 0 deletions

View File

@ -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.

View File

@ -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{

View File

@ -0,0 +1,16 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
dupe = {
source = "hashicorp/test"
}
other = {
source = "hashicorp/default"
}
}
}
provider "default" {
}