mirror of https://github.com/hashicorp/terraform
This "kitchen sink" commit is mainly focused on supporting "targets" as a new sub-category of addresses, for use-case like the -target CLI option, but also includes some other functionality to get closer to replacing terraform.ResourceAddress and fill out some missing parts for representing various other address types that are currently represented as strings in the "terraform" package.pull/19086/head
parent
b6fdd0446e
commit
02b25e7057
@ -0,0 +1,75 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// OutputValue is the address of an output value, in the context of the module
|
||||
// that is defining it.
|
||||
//
|
||||
// This is related to but separate from ModuleCallOutput, which represents
|
||||
// a module output from the perspective of its parent module. Since output
|
||||
// values cannot be represented from the module where they are defined,
|
||||
// OutputValue is not Referenceable, while ModuleCallOutput is.
|
||||
type OutputValue struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (v OutputValue) String() string {
|
||||
return "output." + v.Name
|
||||
}
|
||||
|
||||
// Absolute converts the receiver into an absolute address within the given
|
||||
// module instance.
|
||||
func (v OutputValue) Absolute(m ModuleInstance) AbsOutputValue {
|
||||
return AbsOutputValue{
|
||||
Module: m,
|
||||
OutputValue: v,
|
||||
}
|
||||
}
|
||||
|
||||
// AbsOutputValue is the absolute address of an output value within a module instance.
|
||||
//
|
||||
// This represents an output globally within the namespace of a particular
|
||||
// configuration. It is related to but separate from ModuleCallOutput, which
|
||||
// represents a module output from the perspective of its parent module.
|
||||
type AbsOutputValue struct {
|
||||
Module ModuleInstance
|
||||
OutputValue OutputValue
|
||||
}
|
||||
|
||||
// OutputValue returns the absolute address of an output value of the given
|
||||
// name within the receiving module instance.
|
||||
func (m ModuleInstance) OutputValue(name string) AbsOutputValue {
|
||||
return AbsOutputValue{
|
||||
Module: m,
|
||||
OutputValue: OutputValue{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v AbsOutputValue) String() string {
|
||||
if v.Module.IsRoot() {
|
||||
return v.OutputValue.String()
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", v.Module.String(), v.OutputValue.String())
|
||||
}
|
||||
|
||||
// ModuleCallOutput converts an AbsModuleOutput into a ModuleCallOutput,
|
||||
// returning also the module instance that the ModuleCallOutput is relative
|
||||
// to.
|
||||
//
|
||||
// The root module does not have a call, and so this method cannot be used
|
||||
// with outputs in the root module, and will panic in that case.
|
||||
func (v AbsOutputValue) ModuleCallOutput() (ModuleInstance, ModuleCallOutput) {
|
||||
if v.Module.IsRoot() {
|
||||
panic("ReferenceFromCall used with root module output")
|
||||
}
|
||||
|
||||
caller, call := v.Module.CallInstance()
|
||||
return caller, ModuleCallOutput{
|
||||
Call: call,
|
||||
Name: v.OutputValue.Name,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,235 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// Target describes a targeted address with source location information.
|
||||
type Target struct {
|
||||
Subject Targetable
|
||||
SourceRange tfdiags.SourceRange
|
||||
}
|
||||
|
||||
// ParseTarget attempts to interpret the given traversal as a targetable
|
||||
// address. The given traversal must be absolute, or this function will
|
||||
// panic.
|
||||
//
|
||||
// If no error diagnostics are returned, the returned target includes the
|
||||
// address that was extracted and the source range it was extracted from.
|
||||
//
|
||||
// If error diagnostics are returned then the Target value is invalid and
|
||||
// must not be used.
|
||||
func ParseTarget(traversal hcl.Traversal) (*Target, tfdiags.Diagnostics) {
|
||||
path, remain, diags := parseModuleInstancePrefix(traversal)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
rng := tfdiags.SourceRangeFromHCL(traversal.SourceRange())
|
||||
|
||||
if len(remain) == 0 {
|
||||
return &Target{
|
||||
Subject: path,
|
||||
SourceRange: rng,
|
||||
}, diags
|
||||
}
|
||||
|
||||
mode := ManagedResourceMode
|
||||
if remain.RootName() == "data" {
|
||||
mode = DataResourceMode
|
||||
remain = remain[1:]
|
||||
}
|
||||
|
||||
if len(remain) < 2 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "Resource specification must include a resource type and name.",
|
||||
Subject: remain.SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
var typeName, name string
|
||||
switch tt := remain[0].(type) {
|
||||
case hcl.TraverseRoot:
|
||||
typeName = tt.Name
|
||||
case hcl.TraverseAttr:
|
||||
typeName = tt.Name
|
||||
default:
|
||||
switch mode {
|
||||
case ManagedResourceMode:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A resource type name is required.",
|
||||
Subject: remain[0].SourceRange().Ptr(),
|
||||
})
|
||||
case DataResourceMode:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A data source name is required.",
|
||||
Subject: remain[0].SourceRange().Ptr(),
|
||||
})
|
||||
default:
|
||||
panic("unknown mode")
|
||||
}
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
switch tt := remain[1].(type) {
|
||||
case hcl.TraverseAttr:
|
||||
name = tt.Name
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A resource name is required.",
|
||||
Subject: remain[1].SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
var subject Targetable
|
||||
remain = remain[2:]
|
||||
switch len(remain) {
|
||||
case 0:
|
||||
subject = path.Resource(mode, typeName, name)
|
||||
case 1:
|
||||
if tt, ok := remain[0].(hcl.TraverseIndex); ok {
|
||||
key, err := ParseInstanceKey(tt.Key)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: fmt.Sprintf("Invalid resource instance key: %s.", err),
|
||||
Subject: remain[0].SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
subject = path.ResourceInstance(mode, typeName, name, key)
|
||||
} else {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "Resource instance key must be given in square brackets.",
|
||||
Subject: remain[0].SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "Unexpected extra operators after address.",
|
||||
Subject: remain[1].SourceRange().Ptr(),
|
||||
})
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return &Target{
|
||||
Subject: subject,
|
||||
SourceRange: rng,
|
||||
}, diags
|
||||
}
|
||||
|
||||
// ParseAbsResource attempts to interpret the given traversal as an absolute
|
||||
// resource address, using the same syntax as expected by ParseTarget.
|
||||
//
|
||||
// If no error diagnostics are returned, the returned target includes the
|
||||
// address that was extracted and the source range it was extracted from.
|
||||
//
|
||||
// If error diagnostics are returned then the AbsResource value is invalid and
|
||||
// must not be used.
|
||||
func ParseAbsResource(traversal hcl.Traversal) (AbsResource, tfdiags.Diagnostics) {
|
||||
addr, diags := ParseTarget(traversal)
|
||||
if diags.HasErrors() {
|
||||
return AbsResource{}, diags
|
||||
}
|
||||
|
||||
switch tt := addr.Subject.(type) {
|
||||
|
||||
case AbsResource:
|
||||
return tt, diags
|
||||
|
||||
case AbsResourceInstance: // Catch likely user error with specialized message
|
||||
// Assume that the last element of the traversal must be the index,
|
||||
// since that's required for a valid resource instance address.
|
||||
indexStep := traversal[len(traversal)-1]
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A resource address is required. This instance key identifies a specific resource instance, which is not expected here.",
|
||||
Subject: indexStep.SourceRange().Ptr(),
|
||||
})
|
||||
return AbsResource{}, diags
|
||||
|
||||
case ModuleInstance: // Catch likely user error with specialized message
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A resource address is required here. The module path must be followed by a resource specification.",
|
||||
Subject: traversal.SourceRange().Ptr(),
|
||||
})
|
||||
return AbsResource{}, diags
|
||||
|
||||
default: // Generic message for other address types
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A resource address is required here.",
|
||||
Subject: traversal.SourceRange().Ptr(),
|
||||
})
|
||||
return AbsResource{}, diags
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ParseAbsResourceInstance attempts to interpret the given traversal as an
|
||||
// absolute resource instance address, using the same syntax as expected by
|
||||
// ParseTarget.
|
||||
//
|
||||
// If no error diagnostics are returned, the returned target includes the
|
||||
// address that was extracted and the source range it was extracted from.
|
||||
//
|
||||
// If error diagnostics are returned then the AbsResource value is invalid and
|
||||
// must not be used.
|
||||
func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
|
||||
addr, diags := ParseTarget(traversal)
|
||||
if diags.HasErrors() {
|
||||
return AbsResourceInstance{}, diags
|
||||
}
|
||||
|
||||
switch tt := addr.Subject.(type) {
|
||||
|
||||
case AbsResource:
|
||||
return tt.Instance(NoKey), diags
|
||||
|
||||
case AbsResourceInstance:
|
||||
return tt, diags
|
||||
|
||||
case ModuleInstance: // Catch likely user error with specialized message
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A resource instance address is required here. The module path must be followed by a resource instance specification.",
|
||||
Subject: traversal.SourceRange().Ptr(),
|
||||
})
|
||||
return AbsResourceInstance{}, diags
|
||||
|
||||
default: // Generic message for other address types
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid address",
|
||||
Detail: "A resource address is required here.",
|
||||
Subject: traversal.SourceRange().Ptr(),
|
||||
})
|
||||
return AbsResourceInstance{}, diags
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,343 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
func TestParseTarget(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input string
|
||||
Want *Target
|
||||
WantErr string
|
||||
}{
|
||||
{
|
||||
`module.foo`,
|
||||
&Target{
|
||||
Subject: ModuleInstance{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo[2]`,
|
||||
&Target{
|
||||
Subject: ModuleInstance{
|
||||
{
|
||||
Name: "foo",
|
||||
InstanceKey: IntKey(2),
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 14, Byte: 13},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo[2].module.bar`,
|
||||
&Target{
|
||||
Subject: ModuleInstance{
|
||||
{
|
||||
Name: "foo",
|
||||
InstanceKey: IntKey(2),
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`aws_instance.foo`,
|
||||
&Target{
|
||||
Subject: AbsResource{
|
||||
Resource: Resource{
|
||||
Mode: ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
},
|
||||
Module: RootModuleInstance,
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 17, Byte: 16},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`aws_instance.foo[1]`,
|
||||
&Target{
|
||||
Subject: AbsResourceInstance{
|
||||
Resource: ResourceInstance{
|
||||
Resource: Resource{
|
||||
Mode: ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
},
|
||||
Key: IntKey(1),
|
||||
},
|
||||
Module: RootModuleInstance,
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`data.aws_instance.foo`,
|
||||
&Target{
|
||||
Subject: AbsResource{
|
||||
Resource: Resource{
|
||||
Mode: DataResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
},
|
||||
Module: RootModuleInstance,
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`data.aws_instance.foo[1]`,
|
||||
&Target{
|
||||
Subject: AbsResourceInstance{
|
||||
Resource: ResourceInstance{
|
||||
Resource: Resource{
|
||||
Mode: DataResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
},
|
||||
Key: IntKey(1),
|
||||
},
|
||||
Module: RootModuleInstance,
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo.aws_instance.bar`,
|
||||
&Target{
|
||||
Subject: AbsResource{
|
||||
Resource: Resource{
|
||||
Mode: ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "bar",
|
||||
},
|
||||
Module: ModuleInstance{
|
||||
{Name: "foo"},
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 28, Byte: 27},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo.module.bar.aws_instance.baz`,
|
||||
&Target{
|
||||
Subject: AbsResource{
|
||||
Resource: Resource{
|
||||
Mode: ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "baz",
|
||||
},
|
||||
Module: ModuleInstance{
|
||||
{Name: "foo"},
|
||||
{Name: "bar"},
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 39, Byte: 38},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo.module.bar.aws_instance.baz["hello"]`,
|
||||
&Target{
|
||||
Subject: AbsResourceInstance{
|
||||
Resource: ResourceInstance{
|
||||
Resource: Resource{
|
||||
Mode: ManagedResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "baz",
|
||||
},
|
||||
Key: StringKey("hello"),
|
||||
},
|
||||
Module: ModuleInstance{
|
||||
{Name: "foo"},
|
||||
{Name: "bar"},
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 48, Byte: 47},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo.data.aws_instance.bar`,
|
||||
&Target{
|
||||
Subject: AbsResource{
|
||||
Resource: Resource{
|
||||
Mode: DataResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "bar",
|
||||
},
|
||||
Module: ModuleInstance{
|
||||
{Name: "foo"},
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 33, Byte: 32},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo.module.bar.data.aws_instance.baz`,
|
||||
&Target{
|
||||
Subject: AbsResource{
|
||||
Resource: Resource{
|
||||
Mode: DataResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "baz",
|
||||
},
|
||||
Module: ModuleInstance{
|
||||
{Name: "foo"},
|
||||
{Name: "bar"},
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 44, Byte: 43},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
{
|
||||
`module.foo.module.bar.data.aws_instance.baz["hello"]`,
|
||||
&Target{
|
||||
Subject: AbsResourceInstance{
|
||||
Resource: ResourceInstance{
|
||||
Resource: Resource{
|
||||
Mode: DataResourceMode,
|
||||
Type: "aws_instance",
|
||||
Name: "baz",
|
||||
},
|
||||
Key: StringKey("hello"),
|
||||
},
|
||||
Module: ModuleInstance{
|
||||
{Name: "foo"},
|
||||
{Name: "bar"},
|
||||
},
|
||||
},
|
||||
SourceRange: tfdiags.SourceRange{
|
||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||
End: tfdiags.SourcePos{Line: 1, Column: 53, Byte: 52},
|
||||
},
|
||||
},
|
||||
``,
|
||||
},
|
||||
|
||||
{
|
||||
`aws_instance`,
|
||||
nil,
|
||||
`Resource specification must include a resource type and name.`,
|
||||
},
|
||||
{
|
||||
`module`,
|
||||
nil,
|
||||
`Prefix "module." must be followed by a module name.`,
|
||||
},
|
||||
{
|
||||
`module["baz"]`,
|
||||
nil,
|
||||
`Prefix "module." must be followed by a module name.`,
|
||||
},
|
||||
{
|
||||
`module.baz.bar`,
|
||||
nil,
|
||||
`Resource specification must include a resource type and name.`,
|
||||
},
|
||||
{
|
||||
`aws_instance.foo.bar`,
|
||||
nil,
|
||||
`Resource instance key must be given in square brackets.`,
|
||||
},
|
||||
{
|
||||
`aws_instance.foo[1].baz`,
|
||||
nil,
|
||||
`Unexpected extra operators after address.`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Input, func(t *testing.T) {
|
||||
traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1})
|
||||
if travDiags.HasErrors() {
|
||||
t.Fatal(travDiags.Error())
|
||||
}
|
||||
|
||||
got, diags := ParseTarget(traversal)
|
||||
|
||||
switch len(diags) {
|
||||
case 0:
|
||||
if test.WantErr != "" {
|
||||
t.Fatalf("succeeded; want error: %s", test.WantErr)
|
||||
}
|
||||
case 1:
|
||||
if test.WantErr == "" {
|
||||
t.Fatalf("unexpected diagnostics: %s", diags.Err())
|
||||
}
|
||||
if got, want := diags[0].Description().Detail, test.WantErr; got != want {
|
||||
t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("too many diagnostics: %s", diags.Err())
|
||||
}
|
||||
|
||||
if diags.HasErrors() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, problem := range deep.Equal(got, test.Want) {
|
||||
t.Errorf(problem)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package addrs
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ResourceInstancePhase is a special kind of reference used only internally
|
||||
// during graph building to represent resource instances that are in a
|
||||
// non-primary state.
|
||||
//
|
||||
// Graph nodes can declare themselves referenceable via an instance phase
|
||||
// or can declare that they reference an instance phase in order to accomodate
|
||||
// secondary graph nodes dealing with, for example, destroy actions.
|
||||
//
|
||||
// This special reference type cannot be accessed directly by end-users, and
|
||||
// should never be shown in the UI.
|
||||
type ResourceInstancePhase struct {
|
||||
referenceable
|
||||
ResourceInstance ResourceInstance
|
||||
Phase ResourceInstancePhaseType
|
||||
}
|
||||
|
||||
var _ Referenceable = ResourceInstancePhase{}
|
||||
|
||||
// Phase returns a special "phase address" for the receving instance. See the
|
||||
// documentation of ResourceInstancePhase for the limited situations where this
|
||||
// is intended to be used.
|
||||
func (r ResourceInstance) Phase(rpt ResourceInstancePhaseType) ResourceInstancePhase {
|
||||
return ResourceInstancePhase{
|
||||
ResourceInstance: r,
|
||||
Phase: rpt,
|
||||
}
|
||||
}
|
||||
|
||||
func (rp ResourceInstancePhase) String() string {
|
||||
// We use a different separator here than usual to ensure that we'll
|
||||
// never conflict with any non-phased resource instance string. This
|
||||
// is intentionally something that would fail parsing with ParseRef,
|
||||
// because this special address type should never be exposed in the UI.
|
||||
return fmt.Sprintf("%s#%s", rp.ResourceInstance, rp.Phase)
|
||||
}
|
||||
|
||||
// ResourceInstancePhaseType is an enumeration used with ResourceInstancePhase.
|
||||
type ResourceInstancePhaseType string
|
||||
|
||||
const (
|
||||
// ResourceInstancePhaseDestroy represents the "destroy" phase of a
|
||||
// resource instance.
|
||||
ResourceInstancePhaseDestroy ResourceInstancePhaseType = "destroy"
|
||||
|
||||
// ResourceInstancePhaseDestroyCBD is similar to ResourceInstancePhaseDestroy
|
||||
// but is used for resources that have "create_before_destroy" set, thus
|
||||
// requiring a different dependency ordering.
|
||||
ResourceInstancePhaseDestroyCBD ResourceInstancePhaseType = "destroy-cbd"
|
||||
)
|
||||
|
||||
func (rpt ResourceInstancePhaseType) String() string {
|
||||
return string(rpt)
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package addrs
|
||||
|
||||
// Targetable is an interface implemented by all address types that can be
|
||||
// used as "targets" for selecting sub-graphs of a graph.
|
||||
type Targetable interface {
|
||||
targetableSigil()
|
||||
|
||||
// TargetContains returns true if the receiver is considered to contain
|
||||
// the given other address. Containment, for the purpose of targeting,
|
||||
// means that if a container address is targeted then all of the
|
||||
// addresses within it are also implicitly targeted.
|
||||
//
|
||||
// A targetable address always contains at least itself.
|
||||
TargetContains(other Targetable) bool
|
||||
|
||||
// String produces a string representation of the address that could be
|
||||
// parsed as a HCL traversal and passed to ParseTarget to produce an
|
||||
// identical result.
|
||||
String() string
|
||||
}
|
||||
|
||||
type targetable struct {
|
||||
}
|
||||
|
||||
func (r targetable) targetableSigil() {
|
||||
}
|
||||
Loading…
Reference in new issue