diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ed54cda4f..6da4337d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ENHANCEMENTS: * `tofu show` now supports a `-config` option, to be used in conjunction with `-json` to produce a machine-readable summary of the configuration without first creating a plan. ([#2820](https://github.com/opentofu/opentofu/pull/2820)) +* `tofu validate` now supports running in a module that contains provider configuration_aliases. ([#2905](https://github.com/opentofu/opentofu/pull/2905)) ## Previous Releases diff --git a/internal/tofu/context_validate_test.go b/internal/tofu/context_validate_test.go index fdc2067647..0d14c52abc 100644 --- a/internal/tofu/context_validate_test.go +++ b/internal/tofu/context_validate_test.go @@ -2506,3 +2506,73 @@ func TestContext2Validate_rangeOverZeroPlanTimestamp(t *testing.T) { t.Fatal(diags.ErrWithWarnings()) } } + +func TestContext2Validate_providerAliasesInRoot(t *testing.T) { + // This tests the scenario where a user is running tofu validate in a module, instead of the root + // It should allow configuration_aliases to function, even in the root, similar to how input + // variables function in validate. + m := testModuleInline(t, map[string]string{ + "main.tf": ` +terraform { + required_providers { + test = { + source = "hashicorp/test" + configuration_aliases = [test.alias] + } + } +} + +resource "test_object" "t" { + provider = test.alias +} +`, + }) + + p := simpleMockProvider() + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + diags := ctx.Validate(context.Background(), m) + if diags.HasErrors() { + t.Fatal(diags.ErrWithWarnings()) + } +} + +func TestContext2Validate_providerAliasesInRootMisconfigured(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +terraform { + required_providers { + test = { + source = "hashicorp/test" + configuration_aliases = [test.alias] + } + } +} + +resource "test_object" "t" { + provider = test.typo +} +`, + }) + + p := simpleMockProvider() + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + diags := ctx.Validate(context.Background(), m) + + if !diags.HasErrors() { + t.Fatal("Expected error") + } + + if !strings.Contains(diags.Err().Error(), `Provider configuration not present: To work with test_object.t its original provider configuration at provider["registry.opentofu.org/hashicorp/test"].typo is required, but it has been removed`) { + t.Fatalf("expected error, got: %q\n", diags.Err().Error()) + } +} diff --git a/internal/tofu/graph_builder_apply.go b/internal/tofu/graph_builder_apply.go index cb13f9913b..1e9e44eea1 100644 --- a/internal/tofu/graph_builder_apply.go +++ b/internal/tofu/graph_builder_apply.go @@ -147,7 +147,7 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer { &AttachResourceConfigTransformer{Config: b.Config}, // add providers - transformProviders(concreteProvider, b.Config), + transformProviders(concreteProvider, b.Config, b.Operation), // Remove modules no longer present in the config &RemovedModuleTransformer{Config: b.Config, State: b.State}, diff --git a/internal/tofu/graph_builder_eval.go b/internal/tofu/graph_builder_eval.go index 4f6b417146..2692de92f9 100644 --- a/internal/tofu/graph_builder_eval.go +++ b/internal/tofu/graph_builder_eval.go @@ -87,7 +87,7 @@ func (b *EvalGraphBuilder) Steps() []GraphTransformer { // Attach the state &AttachStateTransformer{State: b.State}, - transformProviders(concreteProvider, b.Config), + transformProviders(concreteProvider, b.Config, walkEval), // Must attach schemas before ReferenceTransformer so that we can // analyze the configuration to find references. diff --git a/internal/tofu/graph_builder_plan.go b/internal/tofu/graph_builder_plan.go index 0b32f41314..e2ff1cf3f3 100644 --- a/internal/tofu/graph_builder_plan.go +++ b/internal/tofu/graph_builder_plan.go @@ -197,7 +197,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { &AttachResourceConfigTransformer{Config: b.Config}, // add providers - transformProviders(b.ConcreteProvider, b.Config), + transformProviders(b.ConcreteProvider, b.Config, b.Operation), // Remove modules no longer present in the config &RemovedModuleTransformer{Config: b.Config, State: b.State}, diff --git a/internal/tofu/transform_provider.go b/internal/tofu/transform_provider.go index bd40992757..2101160749 100644 --- a/internal/tofu/transform_provider.go +++ b/internal/tofu/transform_provider.go @@ -17,12 +17,13 @@ import ( "github.com/opentofu/opentofu/internal/tfdiags" ) -func transformProviders(concrete ConcreteProviderNodeFunc, config *configs.Config) GraphTransformer { +func transformProviders(concrete ConcreteProviderNodeFunc, config *configs.Config, walkOp walkOperation) GraphTransformer { return GraphTransformMulti( // Add providers from the config &ProviderConfigTransformer{ - Config: config, - Concrete: concrete, + Config: config, + Concrete: concrete, + Operation: walkOp, }, // Add any remaining missing providers &MissingProviderTransformer{ @@ -686,6 +687,9 @@ type ProviderConfigTransformer struct { // Config is the root node of the configuration tree to add providers from. Config *configs.Config + + // Operation is needed to add workarounds for validate + Operation walkOperation } func (t *ProviderConfigTransformer) Transform(_ context.Context, g *Graph) error { @@ -753,19 +757,35 @@ func (t *ProviderConfigTransformer) transformSingle(g *Graph, c *configs.Config) continue } - abstract := &NodeAbstractProvider{ - Addr: addr, - } + addNode := func(alias string) { + abstract := &NodeAbstractProvider{ + Addr: addrs.AbsProviderConfig{ + Provider: addr.Provider, + Module: addr.Module, + Alias: alias, + }, + } - var v dag.Vertex - if t.Concrete != nil { - v = t.Concrete(abstract) - } else { - v = abstract - } + var v dag.Vertex + if t.Concrete != nil { + v = t.Concrete(abstract) + } else { + v = abstract + } - g.Add(v) - t.providers[addr.String()] = v.(GraphNodeProvider) + g.Add(v) + t.providers[abstract.Addr.String()] = v.(GraphNodeProvider) + } + // Add unaliased instance for the provider in the root + addNode("") + + if t.Operation == walkValidate { + // Add a workaround for validating modules by running them as a root module in `tofu validate` + // See the discussion in https://github.com/opentofu/opentofu/issues/2862 for more details + for _, alias := range p.Aliases { + addNode(alias.Alias) + } + } } }