diff --git a/terraform/context_plan2_test.go b/terraform/context_plan2_test.go index 9ea5d66c8a4c..1e93e35fad52 100644 --- a/terraform/context_plan2_test.go +++ b/terraform/context_plan2_test.go @@ -1,6 +1,7 @@ package terraform import ( + "errors" "testing" "github.com/hashicorp/terraform/addrs" @@ -191,3 +192,60 @@ output "out" { t.Fatalf("expected %#v, got %#v\n", expected, change.After) } } + +func TestContext2Plan_basicConfigurationAliases(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +provider "test" { + alias = "z" + test_string = "config" +} + +module "mod" { + source = "./mod" + providers = { + test.x = test.z + } +} +`, + + "mod/main.tf": ` +terraform { + required_providers { + test = { + source = "registry.terraform.io/hashicorp/test" + configuration_aliases = [ test.x ] + } + } +} + +resource "test_object" "a" { + provider = test.x +} + +`, + }) + + p := simpleMockProvider() + + // The resource within the module should be using the provider configured + // from the root module. We should never see an empty configuration. + p.ConfigureFn = func(req providers.ConfigureRequest) (resp providers.ConfigureResponse) { + if req.Config.GetAttr("test_string").IsNull() { + resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing test_string value")) + } + return resp + } + + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + _, diags := ctx.Plan() + if diags.HasErrors() { + t.Fatal(diags.Err()) + } +} diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index fec371cfa46a..f8f390b0ab04 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -605,6 +605,43 @@ func (t *ProviderConfigTransformer) transformSingle(g *Graph, c *configs.Config) t.proxiable[key] = !diags.HasErrors() } + if mod.ProviderRequirements != nil { + // Add implied provider configs from the required_providers + // Since we're still treating empty configs as proxies, we can just add + // these as empty configs too. We'll ensure that these are given a + // configuration during validation to prevent them from becoming + // fully-fledged config instances. + for _, p := range mod.ProviderRequirements.RequiredProviders { + for _, aliasAddr := range p.Aliases { + addr := addrs.AbsProviderConfig{ + Provider: mod.ProviderForLocalConfig(aliasAddr), + Module: path, + Alias: aliasAddr.Alias, + } + + key := addr.String() + if _, ok := t.providers[key]; ok { + continue + } + + abstract := &NodeAbstractProvider{ + Addr: addr, + } + var v dag.Vertex + if t.Concrete != nil { + v = t.Concrete(abstract) + } else { + v = abstract + } + + // Add it to the graph + g.Add(v) + t.providers[key] = v.(GraphNodeProvider) + t.proxiable[key] = true + } + } + } + // Now replace the provider nodes with proxy nodes if a provider was being // passed in, and create implicit proxies if there was no config. Any extra // proxies will be removed in the prune step.