addrs: ParseAbsProviderConfig function
This is for parsing the type of provider configuration address we write into state in order to remember which provider configuration is responsible for each resource.
This commit is contained in:
parent
8ca174b133
commit
c6598a3f86
|
@ -1,6 +1,16 @@
|
|||
package addrs
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// ModuleInstance is an address for a particular module instance within the
|
||||
// dynamic module tree. This is an extension of the static traversals
|
||||
|
@ -12,6 +22,141 @@ import "bytes"
|
|||
// creation.
|
||||
type ModuleInstance []ModuleInstanceStep
|
||||
|
||||
func ParseModuleInstance(traversal hcl.Traversal) (ModuleInstance, tfdiags.Diagnostics) {
|
||||
mi, remain, diags := parseModuleInstancePrefix(traversal)
|
||||
if len(remain) != 0 {
|
||||
if len(remain) == len(traversal) {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid module instance address",
|
||||
Detail: "A module instance address must begin with \"module.\".",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid module instance address",
|
||||
Detail: "The module instance address is followed by additional invalid content.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return mi, diags
|
||||
}
|
||||
|
||||
func parseModuleInstancePrefix(traversal hcl.Traversal) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) {
|
||||
remain := traversal
|
||||
var mi ModuleInstance
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
for len(remain) > 0 {
|
||||
var next string
|
||||
switch tt := remain[0].(type) {
|
||||
case hcl.TraverseRoot:
|
||||
next = tt.Name
|
||||
case hcl.TraverseAttr:
|
||||
next = tt.Name
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address operator",
|
||||
Detail: "Module address prefix must be followed by dot and then a name.",
|
||||
Subject: remain[0].SourceRange().Ptr(),
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
if next != "module" {
|
||||
break
|
||||
}
|
||||
|
||||
kwRange := remain[0].SourceRange()
|
||||
remain = remain[1:]
|
||||
// If we have the prefix "module" then we should be followed by an
|
||||
// module call name, as an attribute, and then optionally an index step
|
||||
// giving the instance key.
|
||||
if len(remain) == 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address operator",
|
||||
Detail: "Prefix \"module.\" must be followed by a module name.",
|
||||
Subject: &kwRange,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
var moduleName string
|
||||
switch tt := remain[0].(type) {
|
||||
case hcl.TraverseAttr:
|
||||
moduleName = tt.Name
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address operator",
|
||||
Detail: "Prefix \"module.\" must be followed by a module name.",
|
||||
Subject: remain[0].SourceRange().Ptr(),
|
||||
})
|
||||
break
|
||||
}
|
||||
remain = remain[1:]
|
||||
step := ModuleInstanceStep{
|
||||
Name: moduleName,
|
||||
}
|
||||
|
||||
if len(remain) > 0 {
|
||||
if idx, ok := remain[0].(hcl.TraverseIndex); ok {
|
||||
remain = remain[1:]
|
||||
|
||||
switch idx.Key.Type() {
|
||||
case cty.String:
|
||||
step.InstanceKey = StringKey(idx.Key.AsString())
|
||||
case cty.Number:
|
||||
var idxInt int
|
||||
err := gocty.FromCtyValue(idx.Key, &idxInt)
|
||||
if err == nil {
|
||||
step.InstanceKey = IntKey(idxInt)
|
||||
} else {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address operator",
|
||||
Detail: fmt.Sprintf("Invalid module index: %s.", err),
|
||||
Subject: idx.SourceRange().Ptr(),
|
||||
})
|
||||
}
|
||||
default:
|
||||
// Should never happen, because no other types are allowed in traversal indices.
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address operator",
|
||||
Detail: "Invalid module key: must be either a string or an integer.",
|
||||
Subject: idx.SourceRange().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mi = append(mi, step)
|
||||
}
|
||||
|
||||
var retRemain hcl.Traversal
|
||||
if len(remain) > 0 {
|
||||
retRemain = make(hcl.Traversal, len(remain))
|
||||
copy(retRemain, remain)
|
||||
// The first element here might be either a TraverseRoot or a
|
||||
// TraverseAttr, depending on whether we had a module address on the
|
||||
// front. To make life easier for callers, we'll normalize to always
|
||||
// start with a TraverseRoot.
|
||||
if tt, ok := retRemain[0].(hcl.TraverseAttr); ok {
|
||||
retRemain[0] = hcl.TraverseRoot{
|
||||
Name: tt.Name,
|
||||
SrcRange: tt.SrcRange,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mi, retRemain, diags
|
||||
}
|
||||
|
||||
// ModuleInstanceStep is a single traversal step through the dynamic module
|
||||
// tree. It is used only as part of ModuleInstance.
|
||||
type ModuleInstanceStep struct {
|
||||
|
|
|
@ -2,6 +2,10 @@ package addrs
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
// ProviderConfig is the address of a provider configuration.
|
||||
|
@ -37,6 +41,76 @@ type AbsProviderConfig struct {
|
|||
ProviderConfig ProviderConfig
|
||||
}
|
||||
|
||||
// ParseAbsProviderConfig parses the given traversal as an absolute provider
|
||||
// address. The following are examples of traversals that can be successfully
|
||||
// parsed as absolute provider configuration addresses:
|
||||
//
|
||||
// provider.aws
|
||||
// provider.aws.foo
|
||||
// module.bar.provider.aws
|
||||
// module.bar.module.baz.provider.aws.foo
|
||||
// module.foo[1].provider.aws.foo
|
||||
//
|
||||
// This type of address is used, for example, to record the relationships
|
||||
// between resources and provider configurations in the state structure.
|
||||
// This type of address is not generally used in the UI, except in error
|
||||
// messages that refer to provider configurations.
|
||||
func ParseAbsProviderConfig(traversal hcl.Traversal) (AbsProviderConfig, tfdiags.Diagnostics) {
|
||||
modInst, remain, diags := parseModuleInstancePrefix(traversal)
|
||||
ret := AbsProviderConfig{
|
||||
Module: modInst,
|
||||
}
|
||||
if len(remain) < 2 || remain.RootName() != "provider" {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider configuration address",
|
||||
Detail: "Provider address must begin with \"provider.\", followed by a provider type name.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
if len(remain) > 3 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider configuration address",
|
||||
Detail: "Extraneous operators after provider configuration alias.",
|
||||
Subject: hcl.Traversal(remain[3:]).SourceRange().Ptr(),
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
if tt, ok := remain[1].(hcl.TraverseAttr); ok {
|
||||
ret.ProviderConfig.Type = tt.Name
|
||||
} else {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider configuration address",
|
||||
Detail: "The prefix \"provider.\" must be followed by a provider type name.",
|
||||
Subject: remain[1].SourceRange().Ptr(),
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
if len(remain) == 3 {
|
||||
if tt, ok := remain[2].(hcl.TraverseAttr); ok {
|
||||
ret.ProviderConfig.Alias = tt.Name
|
||||
} else {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider configuration address",
|
||||
Detail: "Provider type name must be followed by a configuration alias name.",
|
||||
Subject: remain[2].SourceRange().Ptr(),
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
func (pc AbsProviderConfig) String() string {
|
||||
if len(pc.Module) == 0 {
|
||||
return pc.ProviderConfig.String()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", pc.Module.String(), pc.ProviderConfig.String())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
package addrs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
)
|
||||
|
||||
func TestParseAbsProviderConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input string
|
||||
Want AbsProviderConfig
|
||||
WantDiag string
|
||||
}{
|
||||
{
|
||||
`provider.aws`,
|
||||
AbsProviderConfig{
|
||||
Module: RootModuleInstance,
|
||||
ProviderConfig: ProviderConfig{
|
||||
Type: "aws",
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`provider.aws.foo`,
|
||||
AbsProviderConfig{
|
||||
Module: RootModuleInstance,
|
||||
ProviderConfig: ProviderConfig{
|
||||
Type: "aws",
|
||||
Alias: "foo",
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.baz.provider.aws`,
|
||||
AbsProviderConfig{
|
||||
Module: ModuleInstance{
|
||||
{
|
||||
Name: "baz",
|
||||
},
|
||||
},
|
||||
ProviderConfig: ProviderConfig{
|
||||
Type: "aws",
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.baz.provider.aws.foo`,
|
||||
AbsProviderConfig{
|
||||
Module: ModuleInstance{
|
||||
{
|
||||
Name: "baz",
|
||||
},
|
||||
},
|
||||
ProviderConfig: ProviderConfig{
|
||||
Type: "aws",
|
||||
Alias: "foo",
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.baz["foo"].provider.aws`,
|
||||
AbsProviderConfig{
|
||||
Module: ModuleInstance{
|
||||
{
|
||||
Name: "baz",
|
||||
InstanceKey: StringKey("foo"),
|
||||
},
|
||||
},
|
||||
ProviderConfig: ProviderConfig{
|
||||
Type: "aws",
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.baz[1].provider.aws`,
|
||||
AbsProviderConfig{
|
||||
Module: ModuleInstance{
|
||||
{
|
||||
Name: "baz",
|
||||
InstanceKey: IntKey(1),
|
||||
},
|
||||
},
|
||||
ProviderConfig: ProviderConfig{
|
||||
Type: "aws",
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.baz[1].module.bar.provider.aws`,
|
||||
AbsProviderConfig{
|
||||
Module: ModuleInstance{
|
||||
{
|
||||
Name: "baz",
|
||||
InstanceKey: IntKey(1),
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
ProviderConfig: ProviderConfig{
|
||||
Type: "aws",
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`aws`,
|
||||
AbsProviderConfig{},
|
||||
`Provider address must begin with "provider.", followed by a provider type name.`,
|
||||
},
|
||||
{
|
||||
`aws.foo`,
|
||||
AbsProviderConfig{},
|
||||
`Provider address must begin with "provider.", followed by a provider type name.`,
|
||||
},
|
||||
{
|
||||
`provider`,
|
||||
AbsProviderConfig{},
|
||||
`Provider address must begin with "provider.", followed by a provider type name.`,
|
||||
},
|
||||
{
|
||||
`provider.aws.foo.bar`,
|
||||
AbsProviderConfig{},
|
||||
`Extraneous operators after provider configuration alias.`,
|
||||
},
|
||||
{
|
||||
`provider["aws"]`,
|
||||
AbsProviderConfig{},
|
||||
`The prefix "provider." must be followed by a provider type name.`,
|
||||
},
|
||||
{
|
||||
`provider.aws["foo"]`,
|
||||
AbsProviderConfig{},
|
||||
`Provider type name must be followed by a configuration alias name.`,
|
||||
},
|
||||
{
|
||||
`module.foo`,
|
||||
AbsProviderConfig{},
|
||||
`Provider address must begin with "provider.", followed by a provider type name.`,
|
||||
},
|
||||
{
|
||||
`module.foo["provider"]`,
|
||||
AbsProviderConfig{},
|
||||
`Provider address must begin with "provider.", followed by a provider type name.`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Input, func(t *testing.T) {
|
||||
traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{})
|
||||
if len(parseDiags) != 0 {
|
||||
t.Errorf("unexpected diagnostics during parse")
|
||||
for _, diag := range parseDiags {
|
||||
t.Logf("- %s", diag)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
got, diags := ParseAbsProviderConfig(traversal)
|
||||
|
||||
if test.WantDiag != "" {
|
||||
if len(diags) != 1 {
|
||||
t.Fatalf("got %d diagnostics; want 1", len(diags))
|
||||
}
|
||||
gotDetail := diags[0].Description().Detail
|
||||
if gotDetail != test.WantDiag {
|
||||
t.Fatalf("wrong diagnostic detail\ngot: %s\nwant: %s", gotDetail, test.WantDiag)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
if len(diags) != 0 {
|
||||
t.Fatalf("got %d diagnostics; want 0", len(diags))
|
||||
}
|
||||
}
|
||||
|
||||
for _, problem := range deep.Equal(got, test.Want) {
|
||||
t.Error(problem)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue