resolve provider types when building the config

All the information is available to resolve provider types when building
the configuration, but some provider references still had no FQN. This
caused validation to assume a default type, and incorrectly reject valid
module calls with non-default namespaced providers.

Resolve as much provider type information as possible when loading the
config. Only use this internally for now, but this should be useful
outside of the package to avoid re-resolving the providers later on. We
can come back and find where this might be useful elsewhere, but for now
keep the change as small as possible to avoid any changes in behavior.
This commit is contained in:
James Bardin 2021-04-16 12:37:50 -04:00
parent 2cd1619c40
commit d0cc7f1d5e
7 changed files with 92 additions and 6 deletions

View File

@ -329,6 +329,53 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse
return diags return diags
} }
// resolveProviderTypes walks through the providers in the module and ensures
// the true types are assigned based on the provider requirements for the
// module.
func (c *Config) resolveProviderTypes() {
for _, child := range c.Children {
child.resolveProviderTypes()
}
// collect the required_providers, and then add any missing default providers
providers := map[string]addrs.Provider{}
for name, p := range c.Module.ProviderRequirements.RequiredProviders {
providers[name] = p.Type
}
// ensure all provider configs know their correct type
for _, p := range c.Module.ProviderConfigs {
addr, required := providers[p.Name]
if required {
p.providerType = addr
} else {
addr := addrs.NewDefaultProvider(p.Name)
p.providerType = addr
providers[p.Name] = addr
}
}
// connect module call providers to the correct type
for _, mod := range c.Module.ModuleCalls {
for _, p := range mod.Providers {
if addr, known := providers[p.InParent.Name]; known {
p.InParent.providerType = addr
}
}
}
// fill in parent module calls too
if c.Parent != nil {
for _, mod := range c.Parent.Module.ModuleCalls {
for _, p := range mod.Providers {
if addr, known := providers[p.InChild.Name]; known {
p.InChild.providerType = addr
}
}
}
}
}
// ProviderTypes returns the FQNs of each distinct provider type referenced // ProviderTypes returns the FQNs of each distinct provider type referenced
// in the receiving configuration. // in the receiving configuration.
// //

View File

@ -23,6 +23,10 @@ func BuildConfig(root *Module, walker ModuleWalker) (*Config, hcl.Diagnostics) {
cfg.Root = cfg // Root module is self-referential. cfg.Root = cfg // Root module is self-referential.
cfg.Children, diags = buildChildModules(cfg, walker) cfg.Children, diags = buildChildModules(cfg, walker)
// Now that the config is built, we can connect the provider names to all
// the known types for validation.
cfg.resolveProviderTypes()
diags = append(diags, validateProviderConfigs(nil, cfg, false)...) diags = append(diags, validateProviderConfigs(nil, cfg, false)...)
return cfg, diags return cfg, diags

View File

@ -25,6 +25,13 @@ type Provider struct {
Config hcl.Body Config hcl.Body
DeclRange hcl.Range DeclRange hcl.Range
// TODO: this may not be set in some cases, so it is not yet suitable for
// use outside of this package. We currently only use it for internal
// validation, but once we verify that this can be set in all cases, we can
// export this so providers don't need to be re-resolved.
// This same field is also added to the ProviderConfigRef struct.
providerType addrs.Provider
} }
func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) { func decodeProviderBlock(block *hcl.Block) (*Provider, hcl.Diagnostics) {

View File

@ -136,9 +136,18 @@ func validateProviderConfigs(call *ModuleCall, cfg *Config, noProviderConfig boo
// You cannot pass in a provider that cannot be used // You cannot pass in a provider that cannot be used
for name, passed := range passedIn { for name, passed := range passedIn {
childTy := passed.InChild.providerType
// get a default type if there was none set
if childTy.IsZero() {
// This means the child module is only using an inferred
// provider type. We allow this but will generate a warning to
// declare provider_requirements below.
childTy = addrs.NewDefaultProvider(passed.InChild.Name)
}
providerAddr := addrs.AbsProviderConfig{ providerAddr := addrs.AbsProviderConfig{
Module: cfg.Path, Module: cfg.Path,
Provider: addrs.NewDefaultProvider(passed.InChild.Name), Provider: childTy,
Alias: passed.InChild.Alias, Alias: passed.InChild.Alias,
} }
@ -172,9 +181,12 @@ func validateProviderConfigs(call *ModuleCall, cfg *Config, noProviderConfig boo
} }
// The provider being passed in must also be of the correct type. // The provider being passed in must also be of the correct type.
pTy := passed.InParent.providerType
if pTy.IsZero() {
// While we would like to ensure required_providers exists here, // While we would like to ensure required_providers exists here,
// implied default configuration is still allowed. // implied default configuration is still allowed.
pTy := addrs.NewDefaultProvider(passed.InParent.Name) pTy = addrs.NewDefaultProvider(passed.InParent.Name)
}
// use the full address for a nice diagnostic output // use the full address for a nice diagnostic output
parentAddr := addrs.AbsProviderConfig{ parentAddr := addrs.AbsProviderConfig{

View File

@ -374,6 +374,13 @@ type ProviderConfigRef struct {
NameRange hcl.Range NameRange hcl.Range
Alias string Alias string
AliasRange *hcl.Range // nil if alias not set AliasRange *hcl.Range // nil if alias not set
// TODO: this may not be set in some cases, so it is not yet suitable for
// use outside of this package. We currently only use it for internal
// validation, but once we verify that this can be set in all cases, we can
// export this so providers don't need to be re-resolved.
// This same field is also added to the Provider struct.
providerType addrs.Provider
} }
func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) { func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {

View File

@ -3,10 +3,12 @@ terraform {
bar-test = { bar-test = {
source = "bar/test" source = "bar/test"
} }
foo-test = {
source = "foo/test"
configuration_aliases = [foo-test.other]
}
} }
} }
provider "bar-test" {}
resource "test_instance" "explicit" { resource "test_instance" "explicit" {
// explicitly setting provider bar-test // explicitly setting provider bar-test
@ -17,3 +19,7 @@ resource "test_instance" "implicit" {
// since the provider type name "test" does not match an entry in // since the provider type name "test" does not match an entry in
// required_providers, the default provider "test" should be used // required_providers, the default provider "test" should be used
} }
resource "test_instance" "other" {
provider = foo-test.other
}

View File

@ -10,6 +10,9 @@ provider "foo-test" {}
module "child" { module "child" {
source = "./child" source = "./child"
providers = {
foo-test.other = foo-test
}
} }
resource "test_instance" "explicit" { resource "test_instance" "explicit" {