configs: Fix nested provider requirements bug

In a recent PR, we changed the provider requirements code to permit
per-module requirements gathering, to enhance the provider command
output. This had an incorrect implementation of recursive requirements
gathering for the normal case, which resulted in only depth-1 modules
being inspected.

This commit fixes the broken recursion and adds a grandchild module to
the unit tests as test coverage. This also demanded fixing the
testNestedModuleConfigFromDir helper function to cope with nested
modules in test configs.
This commit is contained in:
Alisdair McDiarmid 2020-06-22 12:13:35 -04:00
parent 69b94ec149
commit 45f7da9678
5 changed files with 45 additions and 14 deletions

View File

@ -185,12 +185,7 @@ func (c *Config) DescendentForInstance(path addrs.ModuleInstance) *Config {
// may be incomplete.
func (c *Config) ProviderRequirements() (getproviders.Requirements, hcl.Diagnostics) {
reqs := make(getproviders.Requirements)
diags := c.addProviderRequirements(reqs)
for _, childConfig := range c.Children {
moreDiags := childConfig.addProviderRequirements(reqs)
diags = append(diags, moreDiags...)
}
diags := c.addProviderRequirements(reqs, true)
return reqs, diags
}
@ -203,7 +198,7 @@ func (c *Config) ProviderRequirements() (getproviders.Requirements, hcl.Diagnost
// may be incomplete.
func (c *Config) ProviderRequirementsByModule() (*ModuleRequirements, hcl.Diagnostics) {
reqs := make(getproviders.Requirements)
diags := c.addProviderRequirements(reqs)
diags := c.addProviderRequirements(reqs, false)
children := make(map[string]*ModuleRequirements)
for name, child := range c.Children {
@ -225,9 +220,9 @@ func (c *Config) ProviderRequirementsByModule() (*ModuleRequirements, hcl.Diagno
// addProviderRequirements is the main part of the ProviderRequirements
// implementation, gradually mutating a shared requirements object to
// eventually return. This function only adds requirements for the top-level
// module.
func (c *Config) addProviderRequirements(reqs getproviders.Requirements) hcl.Diagnostics {
// eventually return. If the recurse argument is true, the requirements will
// include all descendant modules; otherwise, only the specified module.
func (c *Config) addProviderRequirements(reqs getproviders.Requirements, recurse bool) hcl.Diagnostics {
var diags hcl.Diagnostics
// First we'll deal with the requirements directly in _our_ module...
@ -309,6 +304,13 @@ func (c *Config) addProviderRequirements(reqs getproviders.Requirements) hcl.Dia
}
}
if recurse {
for _, childConfig := range c.Children {
moreDiags := childConfig.addProviderRequirements(reqs, true)
diags = append(diags, moreDiags...)
}
}
return diags
}

View File

@ -130,6 +130,7 @@ func TestConfigProviderRequirements(t *testing.T) {
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
grandchildProvider := addrs.NewDefaultProvider("grandchild")
got, diags := cfg.ProviderRequirements()
assertNoDiagnostics(t, diags)
@ -142,6 +143,7 @@ func TestConfigProviderRequirements(t *testing.T) {
impliedProvider: nil,
happycloudProvider: nil,
terraformProvider: nil,
grandchildProvider: nil,
}
if diff := cmp.Diff(want, got); diff != "" {
@ -166,6 +168,7 @@ func TestConfigProviderRequirementsByModule(t *testing.T) {
impliedProvider := addrs.NewDefaultProvider("implied")
terraformProvider := addrs.NewBuiltInProvider("terraform")
configuredProvider := addrs.NewDefaultProvider("configured")
grandchildProvider := addrs.NewDefaultProvider("grandchild")
got, diags := cfg.ProviderRequirementsByModule()
assertNoDiagnostics(t, diags)
@ -191,7 +194,17 @@ func TestConfigProviderRequirementsByModule(t *testing.T) {
nullProvider: getproviders.MustParseVersionConstraints("= 2.0.1"),
happycloudProvider: nil,
},
Children: map[string]*ModuleRequirements{},
Children: map[string]*ModuleRequirements{
"nested": {
Name: "nested",
SourceAddr: "./grandchild",
SourceDir: "testdata/provider-reqs/child/grandchild",
Requirements: getproviders.Requirements{
grandchildProvider: nil,
},
Children: map[string]*ModuleRequirements{},
},
},
},
},
}
@ -227,6 +240,6 @@ func TestConfigAddProviderRequirements(t *testing.T) {
reqs := getproviders.Requirements{
addrs.NewDefaultProvider("null"): nil,
}
diags = cfg.addProviderRequirements(reqs)
diags = cfg.addProviderRequirements(reqs, true)
assertNoDiagnostics(t, diags)
}

View File

@ -92,10 +92,18 @@ func testNestedModuleConfigFromDir(t *testing.T, path string) (*Config, hcl.Diag
cfg, diags := BuildConfig(mod, ModuleWalkerFunc(
func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) {
// For the sake of this test we're going to just treat our
// SourceAddr as a path relative to our fixture directory.
// SourceAddr as a path relative to the calling module.
// A "real" implementation of ModuleWalker should accept the
// various different source address syntaxes Terraform supports.
sourcePath := filepath.Join(path, req.SourceAddr)
// Build a full path by walking up the module tree, prepending each
// source address path until we hit the root
paths := []string{req.SourceAddr}
for config := req.Parent; config != nil && config.Parent != nil; config = config.Parent {
paths = append([]string{config.SourceAddr}, paths...)
}
paths = append([]string{path}, paths...)
sourcePath := filepath.Join(paths...)
mod, diags := parser.LoadConfigDir(sourcePath)
version, _ := version.NewVersion(fmt.Sprintf("1.0.%d", versionI))

View File

@ -0,0 +1,4 @@
# There is no provider in required_providers called "grandchild", so this
# implicitly declares a dependency on "hashicorp/grandchild".
resource "grandchild_foo" "bar" {
}

View File

@ -9,3 +9,7 @@ terraform {
}
}
}
module "nested" {
source = "./grandchild"
}