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.
pull/38232/head
Daniel Banck 3 months ago committed by Daniel Schmidt
parent 28b76c1105
commit 11d819048a

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

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

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

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

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

Loading…
Cancel
Save