configs: parse the "providers" map for module calls

This was accidentally missed on the first pass of module call decoding.
As before, this is a map from child provider config address to parent
provider config address, allowing the set of providers to be projected in
arbitrary ways into a child module.
This commit is contained in:
Martin Atkins 2018-04-24 10:06:51 -07:00
parent c6598a3f86
commit b6fdd0446e
4 changed files with 195 additions and 7 deletions

View File

@ -1,6 +1,8 @@
package configs
import (
"fmt"
"github.com/hashicorp/hcl2/gohcl"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
@ -21,6 +23,8 @@ type ModuleCall struct {
Count hcl.Expression
ForEach hcl.Expression
Providers []PassedProviderConfig
DependsOn []hcl.Traversal
DeclRange hcl.Range
@ -76,9 +80,49 @@ func decodeModuleBlock(block *hcl.Block, override bool) (*ModuleCall, hcl.Diagno
mc.DependsOn = append(mc.DependsOn, deps...)
}
if attr, exists := content.Attributes["providers"]; exists {
seen := make(map[string]hcl.Range)
pairs, pDiags := hcl.ExprMap(attr.Expr)
diags = append(diags, pDiags...)
for _, pair := range pairs {
key, keyDiags := decodeProviderConfigRef(pair.Key, "providers")
diags = append(diags, keyDiags...)
value, valueDiags := decodeProviderConfigRef(pair.Value, "providers")
diags = append(diags, valueDiags...)
if keyDiags.HasErrors() || valueDiags.HasErrors() {
continue
}
matchKey := key.String()
if prev, exists := seen[matchKey]; exists {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate provider address",
Detail: fmt.Sprintf("A provider configuration was already passed to %s at %s. Each child provider configuration can be assigned only once.", matchKey, prev),
Subject: pair.Value.Range().Ptr(),
})
continue
}
rng := hcl.RangeBetween(pair.Key.Range(), pair.Value.Range())
seen[matchKey] = rng
mc.Providers = append(mc.Providers, PassedProviderConfig{
InChild: key,
InParent: value,
})
}
}
return mc, diags
}
// PassedProviderConfig represents a provider config explicitly passed down to
// a child module, possibly giving it a new local address in the process.
type PassedProviderConfig struct {
InChild *ProviderConfigRef
InParent *ProviderConfigRef
}
var moduleBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
@ -97,5 +141,8 @@ var moduleBlockSchema = &hcl.BodySchema{
{
Name: "depends_on",
},
{
Name: "providers",
},
},
}

137
configs/module_call_test.go Normal file
View File

@ -0,0 +1,137 @@
package configs
import (
"io/ioutil"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/hcl2/hcl"
)
func TestLoadModuleCall(t *testing.T) {
src, err := ioutil.ReadFile("test-fixtures/valid-files/module-calls.tf")
if err != nil {
t.Fatal(err)
}
parser := testParser(map[string]string{
"module-calls.tf": string(src),
})
file, diags := parser.LoadConfigFile("module-calls.tf")
if len(diags) != 0 {
t.Errorf("Wrong number of diagnostics %d; want 0", len(diags))
for _, diag := range diags {
t.Logf("- %s", diag)
}
return
}
gotModules := file.ModuleCalls
wantModules := []*ModuleCall{
{
Name: "foo",
SourceAddr: "./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},
},
DeclRange: hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 2, Column: 1, Byte: 1},
End: hcl.Pos{Line: 2, Column: 13, Byte: 13},
},
},
{
Name: "bar",
SourceAddr: "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},
},
DeclRange: hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 7, Column: 1, Byte: 87},
End: hcl.Pos{Line: 7, Column: 13, Byte: 99},
},
},
{
Name: "baz",
SourceAddr: "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},
},
DependsOn: []hcl.Traversal{
{
hcl.TraverseRoot{
Name: "module",
SrcRange: hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 23, Column: 5, Byte: 295},
End: hcl.Pos{Line: 23, Column: 11, Byte: 301},
},
},
hcl.TraverseAttr{
Name: "bar",
SrcRange: hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 23, Column: 11, Byte: 301},
End: hcl.Pos{Line: 23, Column: 15, Byte: 305},
},
},
},
},
Providers: []PassedProviderConfig{
{
InChild: &ProviderConfigRef{
Name: "aws",
NameRange: hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 27, Column: 5, Byte: 332},
End: hcl.Pos{Line: 27, Column: 8, Byte: 335},
},
},
InParent: &ProviderConfigRef{
Name: "aws",
NameRange: hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 27, Column: 11, Byte: 338},
End: hcl.Pos{Line: 27, Column: 14, Byte: 341},
},
Alias: "foo",
AliasRange: &hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 27, Column: 14, Byte: 341},
End: hcl.Pos{Line: 27, Column: 18, Byte: 345},
},
},
},
},
DeclRange: hcl.Range{
Filename: "module-calls.tf",
Start: hcl.Pos{Line: 14, Column: 1, Byte: 167},
End: hcl.Pos{Line: 14, Column: 13, Byte: 179},
},
},
}
// We'll hide all of the bodies/exprs since we're treating them as opaque
// here anyway... the point of this test is to ensure we handle everything
// else properly.
for _, m := range gotModules {
m.Config = nil
m.Count = nil
m.ForEach = nil
}
for _, problem := range deep.Equal(gotModules, wantModules) {
t.Error(problem)
}
}

View File

@ -114,7 +114,7 @@ func decodeResourceBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
if attr, exists := content.Attributes["provider"]; exists {
var providerDiags hcl.Diagnostics
r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
diags = append(diags, providerDiags...)
}
@ -290,7 +290,7 @@ func decodeDataBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
if attr, exists := content.Attributes["provider"]; exists {
var providerDiags hcl.Diagnostics
r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr)
r.ProviderConfigRef, providerDiags = decodeProviderConfigRef(attr.Expr, "provider")
diags = append(diags, providerDiags...)
}
@ -324,10 +324,11 @@ type ProviderConfigRef struct {
AliasRange *hcl.Range // nil if alias not set
}
func decodeProviderConfigRef(attr *hcl.Attribute) (*ProviderConfigRef, hcl.Diagnostics) {
func decodeProviderConfigRef(expr hcl.Expression, argName string) (*ProviderConfigRef, hcl.Diagnostics) {
var diags hcl.Diagnostics
expr, shimDiags := shimTraversalInString(attr.Expr, false)
var shimDiags hcl.Diagnostics
expr, shimDiags = shimTraversalInString(expr, false)
diags = append(diags, shimDiags...)
traversal, travDiags := hcl.AbsTraversalForExpr(expr)
@ -345,7 +346,7 @@ func decodeProviderConfigRef(attr *hcl.Attribute) (*ProviderConfigRef, hcl.Diagn
// showing that usage, so we'll sniff for that situation here and
// produce a specialized error message for it to help users find
// the new correct form.
if exprIsNativeQuotedString(attr.Expr) {
if exprIsNativeQuotedString(expr) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration reference",
@ -358,7 +359,7 @@ func decodeProviderConfigRef(attr *hcl.Attribute) (*ProviderConfigRef, hcl.Diagn
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid provider configuration reference",
Detail: fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", attr.Name),
Detail: fmt.Sprintf("The %s argument requires a provider type name, optionally followed by a period and then a configuration alias.", argName),
Subject: expr.Range().Ptr(),
})
return nil, diags

View File

@ -22,5 +22,8 @@ module "baz" {
depends_on = [
module.bar,
]
}
providers = {
aws = aws.foo
}
}