// 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 }