blob: d605c6d7f6e6e1d5b3c7fdaf55fdafb2b3f381cb [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/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Component is the address of a "component" block within a stack config.
type Component struct {
Name string
}
func (Component) referenceableSigil() {}
func (Component) inStackConfigSigil() {}
func (Component) inStackInstanceSigil() {}
func (c Component) String() string {
return "component." + c.Name
}
func (c Component) UniqueKey() collections.UniqueKey[Component] {
return c
}
// A Component is its own [collections.UniqueKey].
func (Component) IsUniqueKey(Component) {}
// ConfigComponent places a [Component] in the context of a particular [Stack].
type ConfigComponent = InStackConfig[Component]
// AbsComponent places a [Component] in the context of a particular [StackInstance].
type AbsComponent = InStackInstance[Component]
func AbsComponentToInstance(ist AbsComponent, ik addrs.InstanceKey) AbsComponentInstance {
return AbsComponentInstance{
Stack: ist.Stack,
Item: ComponentInstance{
Component: ist.Item,
Key: ik,
},
}
}
// ComponentInstance is the address of a dynamic instance of a component.
type ComponentInstance struct {
Component Component
Key addrs.InstanceKey
}
func (ComponentInstance) inStackConfigSigil() {}
func (ComponentInstance) inStackInstanceSigil() {}
func (c ComponentInstance) String() string {
if c.Key == nil {
return c.Component.String()
}
return c.Component.String() + c.Key.String()
}
func (c ComponentInstance) UniqueKey() collections.UniqueKey[ComponentInstance] {
return c
}
// A ComponentInstance is its own [collections.UniqueKey].
func (ComponentInstance) IsUniqueKey(ComponentInstance) {}
// ConfigComponentInstance places a [ComponentInstance] in the context of a
// particular [Stack].
type ConfigComponentInstance = InStackConfig[ComponentInstance]
// AbsComponentInstance places a [ComponentInstance] in the context of a
// particular [StackInstance].
type AbsComponentInstance = InStackInstance[ComponentInstance]
func ConfigComponentForAbsInstance(instAddr AbsComponentInstance) ConfigComponent {
configInst := ConfigForAbs(instAddr) // a ConfigComponentInstance
return ConfigComponent{
Stack: configInst.Stack,
Item: Component{
Name: configInst.Item.Component.Name,
},
}
}
func ParseAbsComponentInstance(traversal hcl.Traversal) (AbsComponentInstance, tfdiags.Diagnostics) {
inst, remain, diags := ParseAbsComponentInstanceOnly(traversal)
if diags.HasErrors() {
return AbsComponentInstance{}, diags
}
if len(remain) > 0 {
// Then we have some remaining traversal steps that weren't consumed
// by the component instance address itself, which is an error when the
// caller is using this function.
rng := remain.SourceRange()
// if "remain" is empty then the source range would be zero length,
// and so we'll use the original traversal instead.
if len(remain) == 0 {
rng = traversal.SourceRange()
}
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid component instance address",
Detail: "The component instance address must include the keyword \"component\" followed by a component name.",
Subject: &rng,
})
return AbsComponentInstance{}, diags
}
return inst, diags
}
func ParseAbsComponentInstanceStr(s string) (AbsComponentInstance, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(s), "", hcl.InitialPos)
diags = diags.Append(hclDiags)
if diags.HasErrors() {
return AbsComponentInstance{}, diags
}
ret, moreDiags := ParseAbsComponentInstance(traversal)
diags = diags.Append(moreDiags)
return ret, diags
}
func ParsePartialComponentInstanceStr(s string) (AbsComponentInstance, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, hclDiags := hclsyntax.ParseTraversalPartial([]byte(s), "", hcl.InitialPos)
diags = diags.Append(hclDiags)
if diags.HasErrors() {
return AbsComponentInstance{}, diags
}
ret, moreDiags := ParseAbsComponentInstance(traversal)
diags = diags.Append(moreDiags)
return ret, diags
}
func ParseAbsComponentInstanceStrOnly(s string) (AbsComponentInstance, hcl.Traversal, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
traversal, hclDiags := hclsyntax.ParseTraversalPartial([]byte(s), "", hcl.InitialPos)
diags = diags.Append(hclDiags)
if diags.HasErrors() {
return AbsComponentInstance{}, traversal, diags
}
ret, rest, moreDiags := ParseAbsComponentInstanceOnly(traversal)
diags = diags.Append(moreDiags)
return ret, rest, diags
}
func ParseAbsComponentInstanceOnly(traversal hcl.Traversal) (AbsComponentInstance, hcl.Traversal, tfdiags.Diagnostics) {
if traversal.IsRelative() {
// This is always a caller bug: caller must only pass absolute
// traversals in here.
panic("ParseAbsComponentInstanceOnly with relative traversal")
}
stackInst, remain, diags := parseInStackInstancePrefix(traversal)
if diags.HasErrors() {
return AbsComponentInstance{}, remain, diags
}
// "remain" should now be the keyword "component" followed by a valid
// component name, optionally followed by an instance key.
const diagSummary = "Invalid component instance address"
if kwStep, ok := remain[0].(hcl.TraverseAttr); !ok || kwStep.Name != "component" {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: diagSummary,
Detail: "The component instance address must include the keyword \"component\" followed by a component name.",
Subject: remain[0].SourceRange().Ptr(),
})
return AbsComponentInstance{}, remain, diags
}
remain = remain[1:]
nameStep, ok := remain[0].(hcl.TraverseAttr)
if !ok {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: diagSummary,
Detail: "The component instance address must include the keyword \"component\" followed by a component name.",
Subject: remain[1].SourceRange().Ptr(),
})
return AbsComponentInstance{}, remain, diags
}
remain = remain[1:]
componentAddr := ComponentInstance{
Component: Component{Name: nameStep.Name},
}
if len(remain) > 0 {
switch instStep := remain[0].(type) {
case hcl.TraverseIndex:
var err error
componentAddr.Key, err = addrs.ParseInstanceKey(instStep.Key)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: diagSummary,
Detail: fmt.Sprintf("Invalid instance key: %s.", err),
Subject: instStep.SourceRange().Ptr(),
})
return AbsComponentInstance{}, remain, diags
}
remain = remain[1:]
case hcl.TraverseSplat:
componentAddr.Key = addrs.WildcardKey
remain = remain[1:]
}
}
return AbsComponentInstance{
Stack: stackInst,
Item: componentAddr,
}, remain, diags
}