From 11d819048af8290cced683027d383bf410143dbd Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Thu, 26 Feb 2026 16:31:32 +0100 Subject: [PATCH] Store module source and version expressions Instead of evaluating and parsing a module source and version on configuration loading, we now simply store the expression. Decoding is now done during the graph-based configuration loading in the module install node. --- internal/configs/module_call.go | 78 ++----------------------- internal/configs/module_call_test.go | 49 +++++----------- internal/configs/module_merge.go | 11 ++-- internal/configs/module_merge_test.go | 23 ++------ internal/configs/provider_validation.go | 15 ++--- 5 files changed, 36 insertions(+), 140 deletions(-) diff --git a/internal/configs/module_call.go b/internal/configs/module_call.go index f1064b359c..e01b584cd0 100644 --- a/internal/configs/module_call.go +++ b/internal/configs/module_call.go @@ -7,26 +7,19 @@ import ( "fmt" "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/zclconf/go-cty/cty" - - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/getmodules/moduleaddrs" ) // ModuleCall represents a "module" block in a module or file. type ModuleCall struct { Name string - SourceAddr addrs.ModuleSource - SourceAddrRaw string - SourceAddrRange hcl.Range - SourceSet bool + SourceExpr hcl.Expression Config hcl.Body - Version VersionConstraint + VersionExpr hcl.Expression Count hcl.Expression ForEach hcl.Expression @@ -66,75 +59,12 @@ func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagno }) } - haveVersionArg := false if attr, exists := content.Attributes["version"]; exists { - var versionDiags hcl.Diagnostics - mc.Version, versionDiags = decodeVersionConstraint(attr) - diags = append(diags, versionDiags...) - haveVersionArg = true + mc.VersionExpr = attr.Expr } if attr, exists := content.Attributes["source"]; exists { - mc.SourceSet = true - mc.SourceAddrRange = attr.Expr.Range() - valDiags := gohcl.DecodeExpression(attr.Expr, nil, &mc.SourceAddrRaw) - diags = append(diags, valDiags...) - if !valDiags.HasErrors() { - var addr addrs.ModuleSource - var err error - if haveVersionArg { - addr, err = moduleaddrs.ParseModuleSourceRegistry(mc.SourceAddrRaw) - } else { - addr, err = moduleaddrs.ParseModuleSource(mc.SourceAddrRaw) - } - mc.SourceAddr = addr - if err != nil { - // NOTE: We leave mc.SourceAddr as nil for any situation where the - // source attribute is invalid, so any code which tries to carefully - // use the partial result of a failed config decode must be - // resilient to that. - mc.SourceAddr = nil - - // NOTE: In practice it's actually very unlikely to end up here, - // because our source address parser can turn just about any string - // into some sort of remote package address, and so for most errors - // we'll detect them only during module installation. There are - // still a _few_ purely-syntax errors we can catch at parsing time, - // though, mostly related to remote package sub-paths and local - // paths. - switch err := err.(type) { - case *moduleaddrs.MaybeRelativePathErr: - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid module source address", - Detail: fmt.Sprintf( - "Terraform failed to determine your intended installation method for remote module package %q.\n\nIf you intended this as a path relative to the current module, use \"./%s\" instead. The \"./\" prefix indicates that the address is a relative filesystem path.", - err.Addr, err.Addr, - ), - Subject: mc.SourceAddrRange.Ptr(), - }) - default: - if haveVersionArg { - // In this case we'll include some extra context that - // we assumed a registry source address due to the - // version argument. - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid registry module source address", - Detail: fmt.Sprintf("Failed to parse module registry address: %s.\n\nTerraform assumed that you intended a module registry source address because you also set the argument \"version\", which applies only to registry modules.", err), - Subject: mc.SourceAddrRange.Ptr(), - }) - } else { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid module source address", - Detail: fmt.Sprintf("Failed to parse module source address: %s.", err), - Subject: mc.SourceAddrRange.Ptr(), - }) - } - } - } - } + mc.SourceExpr = attr.Expr } if attr, exists := content.Attributes["count"]; exists { diff --git a/internal/configs/module_call_test.go b/internal/configs/module_call_test.go index 4e94d00c44..4c865c3622 100644 --- a/internal/configs/module_call_test.go +++ b/internal/configs/module_call_test.go @@ -10,7 +10,7 @@ import ( "github.com/go-test/deep" "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/hcl/v2/hclsyntax" ) func TestLoadModuleCall(t *testing.T) { @@ -31,15 +31,11 @@ func TestLoadModuleCall(t *testing.T) { gotModules := file.ModuleCalls wantModules := []*ModuleCall{ { - Name: "foo", - SourceAddr: addrs.ModuleSourceLocal("./foo"), - SourceAddrRaw: "./foo", - SourceSet: true, - SourceAddrRange: hcl.Range{ - Filename: "module-calls.tf", - Start: hcl.Pos{Line: 3, Column: 12, Byte: 27}, - End: hcl.Pos{Line: 3, Column: 19, Byte: 34}, - }, + Name: "foo", + SourceExpr: mustExpr(hclsyntax.ParseExpression( + []byte("\"./foo\""), "module-calls.tf", + hcl.Pos{Line: 3, Column: 12, Byte: 27}, + )), DeclRange: hcl.Range{ Filename: "module-calls.tf", Start: hcl.Pos{Line: 2, Column: 1, Byte: 1}, @@ -48,21 +44,10 @@ func TestLoadModuleCall(t *testing.T) { }, { Name: "bar", - SourceAddr: addrs.ModuleSourceRegistry{ - Package: addrs.ModuleRegistryPackage{ - Host: addrs.DefaultModuleRegistryHost, - Namespace: "hashicorp", - Name: "bar", - TargetSystem: "aws", - }, - }, - SourceAddrRaw: "hashicorp/bar/aws", - SourceSet: true, - SourceAddrRange: hcl.Range{ - Filename: "module-calls.tf", - Start: hcl.Pos{Line: 8, Column: 12, Byte: 113}, - End: hcl.Pos{Line: 8, Column: 31, Byte: 132}, - }, + SourceExpr: mustExpr(hclsyntax.ParseExpression( + []byte("\"hashicorp/bar/aws\""), "module-calls.tf", + hcl.Pos{Line: 8, Column: 12, Byte: 113}, + )), DeclRange: hcl.Range{ Filename: "module-calls.tf", Start: hcl.Pos{Line: 7, Column: 1, Byte: 87}, @@ -71,16 +56,10 @@ func TestLoadModuleCall(t *testing.T) { }, { Name: "baz", - SourceAddr: addrs.ModuleSourceRemote{ - Package: addrs.ModulePackage("git::https://example.com/"), - }, - SourceAddrRaw: "git::https://example.com/", - SourceSet: true, - SourceAddrRange: hcl.Range{ - Filename: "module-calls.tf", - Start: hcl.Pos{Line: 15, Column: 12, Byte: 193}, - End: hcl.Pos{Line: 15, Column: 39, Byte: 220}, - }, + SourceExpr: mustExpr(hclsyntax.ParseExpression( + []byte("\"git::https://example.com/\""), "module-calls.tf", + hcl.Pos{Line: 15, Column: 12, Byte: 193}, + )), DependsOn: []hcl.Traversal{ { hcl.TraverseRoot{ diff --git a/internal/configs/module_merge.go b/internal/configs/module_merge.go index ff27d502e3..d9bc2abcc7 100644 --- a/internal/configs/module_merge.go +++ b/internal/configs/module_merge.go @@ -178,11 +178,8 @@ func (o *Output) merge(oo *Output) hcl.Diagnostics { func (mc *ModuleCall) merge(omc *ModuleCall) hcl.Diagnostics { var diags hcl.Diagnostics - if omc.SourceSet { - mc.SourceAddr = omc.SourceAddr - mc.SourceAddrRaw = omc.SourceAddrRaw - mc.SourceAddrRange = omc.SourceAddrRange - mc.SourceSet = omc.SourceSet + if omc.SourceExpr != nil { + mc.SourceExpr = omc.SourceExpr } if omc.Count != nil { @@ -193,8 +190,8 @@ func (mc *ModuleCall) merge(omc *ModuleCall) hcl.Diagnostics { mc.ForEach = omc.ForEach } - if len(omc.Version.Required) != 0 { - mc.Version = omc.Version + if omc.VersionExpr != nil { + mc.VersionExpr = omc.VersionExpr } mc.Config = MergeBodies(mc.Config, omc.Config) diff --git a/internal/configs/module_merge_test.go b/internal/configs/module_merge_test.go index 78b2825e21..dabed28a8f 100644 --- a/internal/configs/module_merge_test.go +++ b/internal/configs/module_merge_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" @@ -91,23 +92,11 @@ func TestModuleOverrideModule(t *testing.T) { got := mod.ModuleCalls["example"] want := &ModuleCall{ - Name: "example", - SourceAddr: addrs.ModuleSourceLocal("./example2-a_override"), - SourceAddrRaw: "./example2-a_override", - SourceAddrRange: hcl.Range{ - Filename: "testdata/valid-modules/override-module/a_override.tf", - Start: hcl.Pos{ - Line: 3, - Column: 12, - Byte: 31, - }, - End: hcl.Pos{ - Line: 3, - Column: 35, - Byte: 54, - }, - }, - SourceSet: true, + Name: "example", + SourceExpr: mustExpr(hclsyntax.ParseExpression( + []byte("\"./example2-a_override\""), "testdata/valid-modules/override-module/a_override.tf", + hcl.Pos{Line: 3, Column: 12, Byte: 31}, + )), DeclRange: hcl.Range{ Filename: "testdata/valid-modules/override-module/primary.tf", Start: hcl.Pos{ diff --git a/internal/configs/provider_validation.go b/internal/configs/provider_validation.go index 78adb8cc9a..009f33ed4c 100644 --- a/internal/configs/provider_validation.go +++ b/internal/configs/provider_validation.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" ) @@ -253,14 +254,14 @@ func validateProviderConfigsForTests(cfg *Config) (diags hcl.Diagnostics) { // Let's make a little fake module call that we can use to call // into validateProviderConfigs. + sourceExpr := hcl.StaticExpr(cty.StringVal(run.Module.Source.String()), run.Module.SourceDeclRange) + versionExpr := hcl.StaticExpr(cty.StringVal(run.Module.Version.Required.String()), run.Module.Version.DeclRange) mc := &ModuleCall{ - Name: run.Name, - SourceAddr: run.Module.Source, - SourceAddrRange: run.Module.SourceDeclRange, - SourceSet: true, - Version: run.Module.Version, - Providers: providers, - DeclRange: run.Module.DeclRange, + Name: run.Name, + SourceExpr: sourceExpr, + VersionExpr: versionExpr, + Providers: providers, + DeclRange: run.Module.DeclRange, } diags = append(diags, validateProviderConfigs(mc, run.ConfigUnderTest, nil)...)