mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
02b25e7057
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.
236 lines
6.7 KiB
Go
236 lines
6.7 KiB
Go
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
|
|
|
|
}
|
|
}
|