terraform/addrs/parse_target.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
}
}