addrs: ImpliedProviderForUnqualifiedType function

This encapsulates the logic for selecting an implied FQN for an
unqualified type name, which could either come from a local name used in
a module without specifying an explicit source for it or from the prefix
of a resource type on a resource that doesn't explicitly set "provider".

This replaces the previous behavior of just directly calling
NewDefaultProvider everywhere so that we can use a different implication
for the local name "terraform", to refer to the built-in terraform
provider rather than the stale one that's on registry.terraform.io for
compatibility with other Terraform versions.
This commit is contained in:
Martin Atkins 2020-04-01 16:11:15 -07:00
parent 27a794062e
commit 7caf0b9246
10 changed files with 59 additions and 12 deletions

View File

@ -78,6 +78,30 @@ func NewProvider(hostname svchost.Hostname, namespace, typeName string) Provider
}
}
// ImpliedProviderForUnqualifiedType represents the rules for inferring what
// provider FQN a user intended when only a naked type name is available.
//
// For all except the type name "terraform" this returns a so-called "default"
// provider, which is under the registry.terraform.io/hashicorp/ namespace.
//
// As a special case, the string "terraform" maps to
// "terraform.io/builtin/terraform" because that is the more likely user
// intent than the now-unmaintained "registry.terraform.io/hashicorp/terraform"
// which remains only for compatibility with older Terraform versions.
func ImpliedProviderForUnqualifiedType(typeName string) Provider {
switch typeName {
case "terraform":
// Note for future maintainers: any additional strings we add here
// as implied to be builtin must never also be use as provider names
// in the registry.terraform.io/hashicorp/... namespace, because
// otherwise older versions of Terraform could implicitly select
// the registry name instead of the internal one.
return NewBuiltInProvider(typeName)
default:
return NewDefaultProvider(typeName)
}
}
// NewDefaultProvider returns the default address of a HashiCorp-maintained,
// Registry-hosted provider.
func NewDefaultProvider(name string) Provider {

View File

@ -310,7 +310,7 @@ func (c *Config) ResolveAbsProviderAddr(addr addrs.ProviderConfig, inModule addr
if providerReq, exists := c.Module.ProviderRequirements[addr.LocalName]; exists {
provider = providerReq.Type
} else {
provider = addrs.NewDefaultProvider(addr.LocalName)
provider = addrs.ImpliedProviderForUnqualifiedType(addr.LocalName)
}
return addrs.AbsProviderConfig{

View File

@ -124,6 +124,7 @@ func TestConfigProviderRequirements(t *testing.T) {
nullProvider := addrs.NewDefaultProvider("null")
randomProvider := addrs.NewDefaultProvider("random")
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
got, diags := cfg.ProviderRequirements()
assertNoDiagnostics(t, diags)
@ -134,6 +135,7 @@ func TestConfigProviderRequirements(t *testing.T) {
tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"),
impliedProvider: nil,
happycloudProvider: nil,
terraformProvider: nil,
}
if diff := cmp.Diff(want, got); diff != "" {

View File

@ -195,7 +195,7 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
}
diags = append(diags, hclDiags...)
} else {
fqn = addrs.NewDefaultProvider(reqd.Name)
fqn = addrs.ImpliedProviderForUnqualifiedType(reqd.Name)
}
if existing, exists := m.ProviderRequirements[reqd.Name]; exists {
if existing.Type != fqn {
@ -286,11 +286,11 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
if existing, exists := m.ProviderRequirements[r.ProviderConfigAddr().LocalName]; exists {
r.Provider = existing.Type
} else {
r.Provider = addrs.NewDefaultProvider(r.ProviderConfigAddr().LocalName)
r.Provider = addrs.ImpliedProviderForUnqualifiedType(r.ProviderConfigAddr().LocalName)
}
continue
}
r.Provider = addrs.NewDefaultProvider(r.Addr().ImpliedProvider())
r.Provider = addrs.ImpliedProviderForUnqualifiedType(r.Addr().ImpliedProvider())
}
for _, r := range file.DataResources {
@ -310,13 +310,12 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics {
if r.ProviderConfigRef != nil {
if existing, exists := m.ProviderRequirements[r.ProviderConfigAddr().LocalName]; exists {
r.Provider = existing.Type
} else {
r.Provider = addrs.NewDefaultProvider(r.ProviderConfigAddr().LocalName)
r.Provider = addrs.ImpliedProviderForUnqualifiedType(r.ProviderConfigAddr().LocalName)
}
continue
}
r.Provider = addrs.NewDefaultProvider(r.Addr().ImpliedProvider())
r.Provider = addrs.ImpliedProviderForUnqualifiedType(r.Addr().ImpliedProvider())
}
return diags
@ -511,5 +510,5 @@ func (m *Module) ProviderForLocalConfig(pc addrs.LocalProviderConfig) addrs.Prov
if provider, exists := m.ProviderRequirements[pc.LocalName]; exists {
return provider.Type
}
return addrs.NewDefaultProvider(pc.LocalName)
return addrs.ImpliedProviderForUnqualifiedType(pc.LocalName)
}

View File

@ -48,7 +48,7 @@ func mergeProviderVersionConstraints(recv map[string]ProviderRequirements, ovrd
// any errors parsing the source string will have already been captured.
fqn, _ = addrs.ParseProviderSourceString(reqd.Source.SourceStr)
} else {
fqn = addrs.NewDefaultProvider(reqd.Name)
fqn = addrs.ImpliedProviderForUnqualifiedType(reqd.Name)
}
recv[reqd.Name] = ProviderRequirements{Type: fqn, VersionConstraints: []VersionConstraint{reqd.Requirement}}
}
@ -218,7 +218,7 @@ func (r *Resource) merge(or *Resource, prs map[string]ProviderRequirements) hcl.
if existing, exists := prs[or.ProviderConfigRef.Name]; exists {
r.Provider = existing.Type
} else {
r.Provider = addrs.NewDefaultProvider(r.ProviderConfigRef.Name)
r.Provider = addrs.ImpliedProviderForUnqualifiedType(r.ProviderConfigRef.Name)
}
}

View File

@ -33,6 +33,18 @@ func TestNewModule_provider_local_name(t *testing.T) {
if localName != "nonexist" {
t.Error("wrong local name returned for a non-local provider")
}
// can also look up the "terraform" provider and see that it sources is
// allowed to be overridden, even though there is a builtin provider
// called "terraform".
p = addrs.NewProvider(addrs.DefaultRegistryHost, "not-builtin", "not-terraform")
if name, exists := mod.ProviderLocalNames[p]; !exists {
t.Fatal("provider FQN not-builtin/not-terraform not found")
} else {
if name != "terraform" {
t.Fatalf("provider localname mismatch: got %s, want terraform", name)
}
}
}
// This test validates the provider FQNs set in each Resource

View File

@ -19,3 +19,10 @@ resource "implied_foo" "bar" {
module "child" {
source = "./child"
}
# There is no provider in required_providers called "terraform", but for
# this name in particular we imply terraform.io/builtin/terraform instead,
# to avoid selecting the now-unmaintained
# registry.terraform.io/hashicorp/terraform.
data "terraform_remote_state" "bar" {
}

View File

@ -4,5 +4,8 @@ terraform {
foo-test = {
source = "foo/test"
}
terraform = {
source = "not-builtin/not-terraform"
}
}
}

View File

@ -105,7 +105,7 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements) tfdiags
fqn = addr
}
if fqn.IsZero() {
fqn = addrs.NewDefaultProvider(localName)
fqn = addrs.ImpliedProviderForUnqualifiedType(localName)
}
if _, ok := reqs[fqn]; !ok {
// We'll at least have an unconstrained dependency then, but might

View File

@ -305,7 +305,7 @@ func (n *NodeAbstractResource) Provider() addrs.Provider {
if n.Config != nil {
return n.Config.Provider
}
return addrs.NewDefaultProvider(n.Addr.Resource.ImpliedProvider())
return addrs.ImpliedProviderForUnqualifiedType(n.Addr.Resource.ImpliedProvider())
}
// GraphNodeProviderConsumer