Validation for provider blocks in expanding modules (nested) (#25248)
* Refactor provider validation into separate func & recurse Refactors the validate provider functions into a separate function that can recursively search above a module to check and see if any parents of the module contain count/for_each configs to be considered
This commit is contained in:
parent
3506f159aa
commit
199157a51a
|
@ -103,50 +103,70 @@ func (l *Loader) moduleWalkerLoad(req *configs.ModuleRequest) (*configs.Module,
|
||||||
// The providers associated with expanding modules must be present in the proxy/passed providers
|
// The providers associated with expanding modules must be present in the proxy/passed providers
|
||||||
// block. Guarding here for accessing the module call just in case.
|
// block. Guarding here for accessing the module call just in case.
|
||||||
if mc, exists := req.Parent.Module.ModuleCalls[req.Name]; exists {
|
if mc, exists := req.Parent.Module.ModuleCalls[req.Name]; exists {
|
||||||
if mc.Count != nil || mc.ForEach != nil {
|
var validateDiags hcl.Diagnostics
|
||||||
for key, pc := range mod.ProviderConfigs {
|
validateDiags = validateProviderConfigs(mc, mod, req.Parent, validateDiags)
|
||||||
// Use these to track if a provider is configured (not allowed),
|
diags = append(diags, validateDiags...)
|
||||||
// or if we've found its matching proxy
|
}
|
||||||
var isConfigured bool
|
return mod, record.Version, diags
|
||||||
var foundMatchingProxy bool
|
}
|
||||||
|
|
||||||
// Validate the config against an empty schema to see if it's empty.
|
func validateProviderConfigs(mc *configs.ModuleCall, mod *configs.Module, parent *configs.Config, diags hcl.Diagnostics) hcl.Diagnostics {
|
||||||
_, pcConfigDiags := pc.Config.Content(&hcl.BodySchema{})
|
if mc.Count != nil || mc.ForEach != nil {
|
||||||
if pcConfigDiags.HasErrors() || pc.Version.Required != nil {
|
for key, pc := range mod.ProviderConfigs {
|
||||||
isConfigured = true
|
// Use these to track if a provider is configured (not allowed),
|
||||||
}
|
// or if we've found its matching proxy
|
||||||
|
var isConfigured bool
|
||||||
|
var foundMatchingProxy bool
|
||||||
|
|
||||||
// If it is empty or only has an alias,
|
// Validate the config against an empty schema to see if it's empty.
|
||||||
// does this provider exist in our proxy configs?
|
_, pcConfigDiags := pc.Config.Content(&hcl.BodySchema{})
|
||||||
for _, r := range mc.Providers {
|
if pcConfigDiags.HasErrors() || pc.Version.Required != nil {
|
||||||
// Must match on name and Alias
|
isConfigured = true
|
||||||
if pc.Name == r.InChild.Name && pc.Alias == r.InChild.Alias {
|
}
|
||||||
foundMatchingProxy = true
|
|
||||||
break
|
// If it is empty or only has an alias,
|
||||||
}
|
// does this provider exist in our proxy configs?
|
||||||
|
for _, r := range mc.Providers {
|
||||||
|
// Must match on name and Alias
|
||||||
|
if pc.Name == r.InChild.Name && pc.Alias == r.InChild.Alias {
|
||||||
|
foundMatchingProxy = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if isConfigured || !foundMatchingProxy {
|
}
|
||||||
if mc.Count != nil {
|
if isConfigured || !foundMatchingProxy {
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
if mc.Count != nil {
|
||||||
Severity: hcl.DiagError,
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Summary: "Module does not support count",
|
Severity: hcl.DiagError,
|
||||||
Detail: fmt.Sprintf(moduleProviderError, mc.Name, "count", key, pc.NameRange),
|
Summary: "Module does not support count",
|
||||||
Subject: mc.Count.Range().Ptr(),
|
Detail: fmt.Sprintf(moduleProviderError, mc.Name, "count", key, pc.NameRange),
|
||||||
})
|
Subject: mc.Count.Range().Ptr(),
|
||||||
}
|
})
|
||||||
if mc.ForEach != nil {
|
}
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
if mc.ForEach != nil {
|
||||||
Severity: hcl.DiagError,
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Summary: "Module does not support for_each",
|
Severity: hcl.DiagError,
|
||||||
Detail: fmt.Sprintf(moduleProviderError, mc.Name, "for_each", key, pc.NameRange),
|
Summary: "Module does not support for_each",
|
||||||
Subject: mc.ForEach.Range().Ptr(),
|
Detail: fmt.Sprintf(moduleProviderError, mc.Name, "for_each", key, pc.NameRange),
|
||||||
})
|
Subject: mc.ForEach.Range().Ptr(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mod, record.Version, diags
|
// If this module has further parents, go through them recursively
|
||||||
|
if !parent.Path.IsRoot() {
|
||||||
|
// Use the path to get the name so we can look it up in the parent module calls
|
||||||
|
path := parent.Path
|
||||||
|
name := path[len(path)-1]
|
||||||
|
// This parent's module call, so we can check for count/for_each here,
|
||||||
|
// guarding with exists just in case. We pass the diags through to the recursive
|
||||||
|
// call so they will accumulate if needed.
|
||||||
|
if mc, exists := parent.Parent.Module.ModuleCalls[name]; exists {
|
||||||
|
return validateProviderConfigs(mc, mod, parent.Parent, diags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
var moduleProviderError = `Module "%s" cannot be used with %s because it contains a nested provider configuration for "%s", at %s.
|
var moduleProviderError = `Module "%s" cannot be used with %s because it contains a nested provider configuration for "%s", at %s.
|
||||||
|
|
|
@ -86,24 +86,24 @@ func TestLoaderLoadConfig_moduleExpand(t *testing.T) {
|
||||||
// We do not allow providers to be configured in expanding modules
|
// We do not allow providers to be configured in expanding modules
|
||||||
// In addition, if a provider is present but an empty block, it is allowed,
|
// In addition, if a provider is present but an empty block, it is allowed,
|
||||||
// but IFF a provider is passed through the module call
|
// but IFF a provider is passed through the module call
|
||||||
paths := []string{"provider-configured", "no-provider-passed"}
|
paths := []string{"provider-configured", "no-provider-passed", "nested-provider", "more-nested-provider"}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
fixtureDir := filepath.Clean(fmt.Sprintf("testdata/expand-modules/%s", p))
|
fixtureDir := filepath.Clean(fmt.Sprintf("testdata/expand-modules/%s", p))
|
||||||
loader, err := NewLoader(&Config{
|
loader, err := NewLoader(&Config{
|
||||||
ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
|
ModulesDir: filepath.Join(fixtureDir, ".terraform/modules"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error from NewLoader: %s", err)
|
t.Fatalf("unexpected error from NewLoader at path %s: %s", p, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, diags := loader.LoadConfig(fixtureDir)
|
_, diags := loader.LoadConfig(fixtureDir)
|
||||||
if !diags.HasErrors() {
|
if !diags.HasErrors() {
|
||||||
t.Fatalf("success; want error")
|
t.Fatalf("success; want error at path %s", p)
|
||||||
}
|
}
|
||||||
got := diags.Error()
|
got := diags.Error()
|
||||||
want := "Module does not support count"
|
want := "Module does not support count"
|
||||||
if !strings.Contains(got, want) {
|
if !strings.Contains(got, want) {
|
||||||
t.Fatalf("wrong error\ngot:\n%s\n\nwant: containing %q", got, want)
|
t.Fatalf("wrong error at path %s \ngot:\n%s\n\nwant: containing %q", p, got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
configs/configload/testdata/expand-modules/more-nested-provider/.terraform/modules/modules.json
vendored
Normal file
34
configs/configload/testdata/expand-modules/more-nested-provider/.terraform/modules/modules.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Key": "",
|
||||||
|
"Source": "",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child",
|
||||||
|
"Source": "./child",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child2",
|
||||||
|
"Source": "./child2",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child3",
|
||||||
|
"Source": "./child3",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child.child2",
|
||||||
|
"Source": "../child2",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child.child2.child3",
|
||||||
|
"Source": "../child3",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
4
configs/configload/testdata/expand-modules/more-nested-provider/child/main.tf
vendored
Normal file
4
configs/configload/testdata/expand-modules/more-nested-provider/child/main.tf
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module "child2" {
|
||||||
|
source = "../child2"
|
||||||
|
|
||||||
|
}
|
4
configs/configload/testdata/expand-modules/more-nested-provider/child2/main.tf
vendored
Normal file
4
configs/configload/testdata/expand-modules/more-nested-provider/child2/main.tf
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module "child3" {
|
||||||
|
source = "../child3"
|
||||||
|
|
||||||
|
}
|
7
configs/configload/testdata/expand-modules/more-nested-provider/child3/main.tf
vendored
Normal file
7
configs/configload/testdata/expand-modules/more-nested-provider/child3/main.tf
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
provider "aws" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
output "my_output" {
|
||||||
|
value = "my output"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
module "child" {
|
||||||
|
count = 1
|
||||||
|
source = "./child"
|
||||||
|
}
|
24
configs/configload/testdata/expand-modules/nested-provider/.terraform/modules/modules.json
vendored
Normal file
24
configs/configload/testdata/expand-modules/nested-provider/.terraform/modules/modules.json
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Key": "",
|
||||||
|
"Source": "",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child",
|
||||||
|
"Source": "./child",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child2",
|
||||||
|
"Source": "./child2",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "child.child2",
|
||||||
|
"Source": "../child2",
|
||||||
|
"Dir": "testdata/expand-modules/nested-provider/child2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
module "child2" {
|
||||||
|
source = "../child2"
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
provider "aws" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
output "my_output" {
|
||||||
|
value = "my output"
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
module "child" {
|
||||||
|
count = 1
|
||||||
|
source = "./child"
|
||||||
|
}
|
Loading…
Reference in New Issue