Allow configuration aliases in root module to impact validation only (#2905)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh
2025-06-11 12:55:48 -04:00
committed by GitHub
parent 52700e677e
commit 78a325732d
6 changed files with 108 additions and 17 deletions

View File

@@ -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

View File

@@ -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())
}
}

View File

@@ -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},

View File

@@ -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.

View File

@@ -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},

View File

@@ -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)
}
}
}
}