Skip to content

Commit

Permalink
Fix partial address representation in plan for deferred actions (#34966)
Browse files Browse the repository at this point in the history
* Fix partial address representation in plan

* reflect new type in stacks
  • Loading branch information
liamcervante committed Jun 20, 2024
1 parent dbd1714 commit 0f0414d
Show file tree
Hide file tree
Showing 33 changed files with 345 additions and 238 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/hcl/v2 v2.20.0
github.com/hashicorp/hcl/v2 v2.21.0
github.com/hashicorp/jsonapi v1.3.1
github.com/hashicorp/terraform-registry-address v0.2.3
github.com/hashicorp/terraform-svchost v0.1.1
Expand Down Expand Up @@ -63,7 +63,7 @@ require (
github.com/xanzy/ssh-agent v0.3.3
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
github.com/zclconf/go-cty v1.14.4
github.com/zclconf/go-cty-debug v0.0.0-20240417160409-8c45e122ae1a
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940
github.com/zclconf/go-cty-yaml v1.0.3
go.opentelemetry.io/contrib/exporters/autoexport v0.45.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0
Expand Down
12 changes: 4 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,6 @@ github.com/antchfx/xpath v1.1.10/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByN
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-shquot v0.0.1 h1:MGV8lwxF4zw75lN7e0MGs7o6AFYn7L6AZaExUpLh0Mo=
github.com/apparentlymart/go-shquot v0.0.1/go.mod h1:lw58XsE5IgUXZ9h0cxnypdx31p9mPFIVEQ9P3c7MlrU=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
Expand Down Expand Up @@ -701,8 +699,8 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.20.0 h1:l++cRs/5jQOiKVvqXZm/P1ZEfVXJmvLS9WSVxkaeTb4=
github.com/hashicorp/hcl/v2 v2.20.0/go.mod h1:WmcD/Ym72MDOOx5F62Ly+leloeu6H7m0pG7VBiU6pQk=
github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14=
github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/jsonapi v1.3.1 h1:GtPvnmcWgYwCuDGvYT5VZBHcUyFdq9lSyCzDjn1DdPo=
github.com/hashicorp/jsonapi v1.3.1/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
Expand Down Expand Up @@ -957,8 +955,6 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
Expand Down Expand Up @@ -1036,8 +1032,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240417160409-8c45e122ae1a h1:/o/Emn22dZIQ7AhyA0aLOKo528WG/WRAM5tqzIoQIOs=
github.com/zclconf/go-cty-debug v0.0.0-20240417160409-8c45e122ae1a/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc=
github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
Expand Down
4 changes: 2 additions & 2 deletions internal/addrs/checkable.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagn
return nil, diags
}

path, remain, diags := parseModuleInstancePrefix(traversal)
path, remain, diags := parseModuleInstancePrefix(traversal, false)
if diags.HasErrors() {
return nil, diags
}
Expand Down Expand Up @@ -157,7 +157,7 @@ func ParseCheckableStr(kind CheckableKind, src string) (Checkable, tfdiags.Diagn
// might be a resource whose type is literally "output".
switch kind {
case CheckableResource:
riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
riAddr, moreDiags := parseResourceInstanceUnderModule(path, false, remain)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
return nil, diags
Expand Down
18 changes: 17 additions & 1 deletion internal/addrs/instance_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,23 @@ var NoKey InstanceKey
// WildcardKey represents the "unknown" value of an InstanceKey. This is used
// within the deferral logic to express absolute module and resource addresses
// that are not known at the time of planning.
var WildcardKey InstanceKey = StringKey("*")
var WildcardKey InstanceKey = &wildcardKey{}

// wildcardKey is a special kind of InstanceKey that represents the "unknown"
// value of an InstanceKey. This is used within the deferral logic to express
// absolute module and resource addresses that are not known at the time of
// planning.
type wildcardKey struct{}

func (w *wildcardKey) instanceKeySigil() {}

func (w *wildcardKey) String() string {
return "[*]"
}

func (w *wildcardKey) Value() cty.Value {
return cty.DynamicVal
}

// IntKey is the InstanceKey representation representing integer indices, as
// used when the "count" argument is specified or if for_each is used with
Expand Down
13 changes: 10 additions & 3 deletions internal/addrs/module_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var (
)

func ParseModuleInstance(traversal hcl.Traversal) (ModuleInstance, tfdiags.Diagnostics) {
mi, remain, diags := parseModuleInstancePrefix(traversal)
mi, remain, diags := parseModuleInstancePrefix(traversal, false)
if len(remain) != 0 {
if len(remain) == len(traversal) {
diags = diags.Append(&hcl.Diagnostic{
Expand Down Expand Up @@ -80,7 +80,7 @@ func ParseModuleInstanceStr(str string) (ModuleInstance, tfdiags.Diagnostics) {
return addr, diags
}

func parseModuleInstancePrefix(traversal hcl.Traversal) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) {
func parseModuleInstancePrefix(traversal hcl.Traversal, allowPartial bool) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) {
remain := traversal
var mi ModuleInstance
var diags tfdiags.Diagnostics
Expand Down Expand Up @@ -141,7 +141,8 @@ LOOP:
}

if len(remain) > 0 {
if idx, ok := remain[0].(hcl.TraverseIndex); ok {
switch idx := remain[0].(type) {
case hcl.TraverseIndex:
remain = remain[1:]

switch idx.Key.Type() {
Expand Down Expand Up @@ -169,6 +170,12 @@ LOOP:
Subject: idx.SourceRange().Ptr(),
})
}

case hcl.TraverseSplat:
if allowPartial {
remain = remain[1:]
step.InstanceKey = WildcardKey
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions internal/addrs/move_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"

"github.com/hashicorp/hcl/v2"

"github.com/hashicorp/terraform/internal/tfdiags"
)

Expand Down Expand Up @@ -126,7 +127,7 @@ func (e *MoveEndpoint) ConfigMoveable(baseModule Module) ConfigMoveable {
// it with the address of the module where it was declared in order to get
// an absolute address relative to the root module.
func ParseMoveEndpoint(traversal hcl.Traversal) (*MoveEndpoint, tfdiags.Diagnostics) {
path, remain, diags := parseModuleInstancePrefix(traversal)
path, remain, diags := parseModuleInstancePrefix(traversal, false)
if diags.HasErrors() {
return nil, diags
}
Expand All @@ -140,7 +141,7 @@ func ParseMoveEndpoint(traversal hcl.Traversal) (*MoveEndpoint, tfdiags.Diagnost
}, diags
}

riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
riAddr, moreDiags := parseResourceInstanceUnderModule(path, false, remain)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
return nil, diags
Expand Down
2 changes: 1 addition & 1 deletion internal/addrs/output_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ type absOutputValueUniqueKey string
func (k absOutputValueUniqueKey) uniqueKeySigil() {}

func ParseAbsOutputValue(traversal hcl.Traversal) (AbsOutputValue, tfdiags.Diagnostics) {
path, remain, diags := parseModuleInstancePrefix(traversal)
path, remain, diags := parseModuleInstancePrefix(traversal, false)
if diags.HasErrors() {
return AbsOutputValue{}, diags
}
Expand Down
99 changes: 89 additions & 10 deletions internal/addrs/parse_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ package addrs
import (
"fmt"

"github.com/hashicorp/hcl/v2/hclsyntax"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"

"github.com/hashicorp/terraform/internal/tfdiags"
)
Expand All @@ -29,7 +28,19 @@ type Target struct {
// 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)
return parseTarget(traversal, false)
}

// ParsePartialTarget is like ParseTarget, but it allows the given traversal
// to support the [*] wildcard syntax for resource instances. These indicate
// a "partial" resource address that refers to all potential instances of a
// resource or module.
func ParsePartialTarget(traversal hcl.Traversal) (*Target, tfdiags.Diagnostics) {
return parseTarget(traversal, true)
}

func parseTarget(traversal hcl.Traversal, allowPartial bool) (*Target, tfdiags.Diagnostics) {
path, remain, diags := parseModuleInstancePrefix(traversal, allowPartial)
if diags.HasErrors() {
return nil, diags
}
Expand All @@ -43,7 +54,7 @@ func ParseTarget(traversal hcl.Traversal) (*Target, tfdiags.Diagnostics) {
}, diags
}

riAddr, moreDiags := parseResourceInstanceUnderModule(path, remain)
riAddr, moreDiags := parseResourceInstanceUnderModule(path, allowPartial, remain)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
return nil, diags
Expand Down Expand Up @@ -151,7 +162,7 @@ func parseConfigResourceUnderModule(moduleAddr Module, remain hcl.Traversal) (Co
}, diags
}

func parseResourceInstanceUnderModule(moduleAddr ModuleInstance, remain hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
func parseResourceInstanceUnderModule(moduleAddr ModuleInstance, allowPartial bool, remain hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
// Note that this helper is used as part of both ParseTarget and
// ParseMoveEndpoint, so its error messages should be generic
// enough to suit both situations.
Expand Down Expand Up @@ -224,7 +235,8 @@ func parseResourceInstanceUnderModule(moduleAddr ModuleInstance, remain hcl.Trav
case 0:
return moduleAddr.ResourceInstance(mode, typeName, name, NoKey), diags
case 1:
if tt, ok := remain[0].(hcl.TraverseIndex); ok {
switch tt := remain[0].(type) {
case hcl.TraverseIndex:
key, err := ParseInstanceKey(tt.Key)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Expand All @@ -237,7 +249,20 @@ func parseResourceInstanceUnderModule(moduleAddr ModuleInstance, remain hcl.Trav
}

return moduleAddr.ResourceInstance(mode, typeName, name, key), diags
} else {
case hcl.TraverseSplat:
if allowPartial {
return moduleAddr.ResourceInstance(mode, typeName, name, WildcardKey), diags
}

// Otherwise, return an error.
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 AbsResourceInstance{}, diags
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid address",
Expand Down Expand Up @@ -374,11 +399,39 @@ func ParseAbsResourceStr(str string) (AbsResource, tfdiags.Diagnostics) {
// 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)
target, diags := ParseTarget(traversal)
if diags.HasErrors() {
return AbsResourceInstance{}, diags
}

addr, validateDiags := validateResourceFromTarget(target, traversal.SourceRange().Ptr())
diags = diags.Append(validateDiags)
return addr, diags
}

// ParsePartialResourceInstance attempts to interpret the given traversal as a
// partial absolute resource instance address, using the same syntax as expected
// by ParsePartialTarget.
//
// 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 ParsePartialResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfdiags.Diagnostics) {
target, diags := ParsePartialTarget(traversal)
if diags.HasErrors() {
return AbsResourceInstance{}, diags
}

addr, validateDiags := validateResourceFromTarget(target, traversal.SourceRange().Ptr())
diags = diags.Append(validateDiags)
return addr, diags
}

func validateResourceFromTarget(addr *Target, src *hcl.Range) (AbsResourceInstance, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics

switch tt := addr.Subject.(type) {

case AbsResource:
Expand All @@ -392,7 +445,7 @@ func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfd
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(),
Subject: src,
})
return AbsResourceInstance{}, diags

Expand All @@ -401,7 +454,7 @@ func ParseAbsResourceInstance(traversal hcl.Traversal) (AbsResourceInstance, tfd
Severity: hcl.DiagError,
Summary: "Invalid address",
Detail: "A resource address is required here.",
Subject: traversal.SourceRange().Ptr(),
Subject: src,
})
return AbsResourceInstance{}, diags

Expand Down Expand Up @@ -434,6 +487,32 @@ func ParseAbsResourceInstanceStr(str string) (AbsResourceInstance, tfdiags.Diagn
return addr, diags
}

// ParsePartialResourceInstanceStr is a helper wrapper around
// ParsePartialResourceInstance that takes a string and parses it with the HCL
// native syntax traversal parser before interpreting it.
//
// Error diagnostics are returned if either the parsing fails or the analysis
// of the traversal fails. There is no way for the caller to distinguish the
// two kinds of diagnostics programmatically. If error diagnostics are returned
// the returned address may be incomplete.
//
// Since this function has no context about the source of the given string,
// any returned diagnostics will not have meaningful source location
// information.
func ParsePartialResourceInstanceStr(str string) (AbsResourceInstance, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics

traversal, parseDiags := hclsyntax.ParseTraversalPartial([]byte(str), "", hcl.Pos{Line: 1, Column: 1})
diags = diags.Append(parseDiags)
if parseDiags.HasErrors() {
return AbsResourceInstance{}, diags
}

addr, addrDiags := ParsePartialResourceInstance(traversal)
diags = diags.Append(addrDiags)
return addr, diags
}

// ModuleAddr returns the module address portion of the subject of
// the recieving target.
//
Expand Down
Loading

0 comments on commit 0f0414d

Please sign in to comment.