blob: 82763271afbe42142706f5b16654700181174cd8 [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package stackaddrs
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Reference describes a reference expression found in the configuration,
// capturing what it referred to and where it was found in source code.
type Reference struct {
Target Referenceable
SourceRange tfdiags.SourceRange
}
// ParseReference raises a raw absolute traversal into a higher-level reference,
// or returns error diagnostics explaining why it cannot.
//
// The returned traversal is a relative traversal covering the remainder of
// the given traversal after the part captured into the returned reference,
// in case the caller wants to do further validation or analysis of the
// subsequent steps.
func ParseReference(traversal hcl.Traversal) (Reference, hcl.Traversal, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
var ret Reference
switch rootName := traversal.RootName(); rootName {
case "var":
name, rng, remain, diags := parseSingleAttrRef(traversal)
ret.Target = InputVariable{Name: name}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
return ret, remain, diags
case "local":
name, rng, remain, diags := parseSingleAttrRef(traversal)
ret.Target = LocalValue{Name: name}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
return ret, remain, diags
case "component":
name, rng, remain, diags := parseSingleAttrRef(traversal)
ret.Target = Component{Name: name}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
return ret, remain, diags
case "stack":
name, rng, remain, diags := parseSingleAttrRef(traversal)
ret.Target = StackCall{Name: name}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
return ret, remain, diags
case "provider":
target, rng, remain, diags := parseProviderRef(traversal)
ret.Target = target
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
return ret, remain, diags
case "each", "count":
attrName, rng, remain, diags := parseSingleAttrRef(traversal)
if diags.HasErrors() {
return ret, nil, diags
}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
switch rootName {
case "each":
switch attrName {
case "key":
ret.Target = EachKey
return ret, remain, diags
case "value":
ret.Target = EachValue
return ret, remain, diags
}
case "count":
switch attrName {
case "index":
ret.Target = CountIndex
return ret, remain, diags
}
}
// If we get here then rootName and attrName are not a valid combination.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unknown symbol",
Detail: fmt.Sprintf("The object %q has no attribute named %q.", rootName, attrName),
Subject: traversal[1].SourceRange().Ptr(),
})
return ret, nil, diags
case "self":
ret.Target = Self
ret.SourceRange = tfdiags.SourceRangeFromHCL(traversal[0].SourceRange())
return ret, traversal[1:], diags
case "terraform":
attrName, rng, remain, diags := parseSingleAttrRef(traversal)
if diags.HasErrors() {
return ret, nil, diags
}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
switch attrName {
case "applying":
ret.Target = TerraformApplying
return ret, remain, diags
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unknown symbol",
Detail: fmt.Sprintf("The object %q has no attribute named %q.", rootName, attrName),
Subject: traversal[1].SourceRange().Ptr(),
})
return ret, remain, diags
}
case "_test_only_global":
name, rng, remain, diags := parseSingleAttrRef(traversal)
ret.Target = TestOnlyGlobal{Name: name}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)
return ret, remain, diags
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unknown symbol",
Detail: fmt.Sprintf("There is no symbol %q defined in the current scope.", rootName),
Subject: traversal[0].SourceRange().Ptr(),
})
return ret, nil, diags
}
}
func parseSingleAttrRef(traversal hcl.Traversal) (string, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
root := traversal.RootName()
rootRange := traversal[0].SourceRange()
if len(traversal) < 2 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: fmt.Sprintf("The %q object cannot be accessed directly. Instead, access one of its attributes.", root),
Subject: &rootRange,
})
return "", hcl.Range{}, nil, diags
}
if attrTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
return attrTrav.Name, hcl.RangeBetween(rootRange, attrTrav.SrcRange), traversal[2:], diags
}
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: fmt.Sprintf("The %q object does not support this operation.", root),
Subject: traversal[1].SourceRange().Ptr(),
})
return "", hcl.Range{}, nil, diags
}
func parseProviderRef(traversal hcl.Traversal) (ProviderConfigRef, hcl.Range, hcl.Traversal, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
if len(traversal) < 3 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: "The \"provider\" symbol must be followed by two attribute access operations, selecting a provider type and a provider configuration name.",
Subject: traversal.SourceRange().Ptr(),
})
return ProviderConfigRef{}, hcl.Range{}, nil, diags
}
if typeTrav, ok := traversal[1].(hcl.TraverseAttr); ok {
if nameTrav, ok := traversal[2].(hcl.TraverseAttr); ok {
ret := ProviderConfigRef{
ProviderLocalName: typeTrav.Name,
Name: nameTrav.Name,
}
return ret, traversal.SourceRange(), traversal[3:], diags
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: "The \"provider\" object's attributes do not support this operation.",
Subject: traversal[1].SourceRange().Ptr(),
})
}
} else {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid reference",
Detail: "The \"provider\" object does not support this operation.",
Subject: traversal[1].SourceRange().Ptr(),
})
}
return ProviderConfigRef{}, hcl.Range{}, nil, diags
}
func (r Reference) Absolute(stack StackInstance) AbsReference {
return AbsReference{
Stack: stack,
Ref: r,
}
}
// AbsReference is an absolute form of [Reference] that is to be resolved
// in the global scope of a particular stack.
//
// It's not meaningful to use this type for references to objects that exist
// only in a more specific scope, such as each.key, each.value, etc, because
// those would require additional information about exactly which object
// they are being resolved in terms of.
type AbsReference struct {
Stack StackInstance
Ref Reference
}
func (r AbsReference) Target() AbsReferenceable {
return AbsReferenceable{
Stack: r.Stack,
Item: r.Ref.Target,
}
}
func (r AbsReference) SourceRange() tfdiags.SourceRange {
return r.Ref.SourceRange
}