mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 10:00:09 -04:00
304 lines
8.4 KiB
Go
304 lines
8.4 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package globalref
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
)
|
|
|
|
// walkBlock walks through the block following the given traversal. If it finds
|
|
// any invalid steps in the traversal, the walk is halted and the traversal
|
|
// until that point is returned. This effectively validates and corrects the
|
|
// given traversal.
|
|
func walkBlock(block *configschema.Block, traversal hcl.Traversal) hcl.Traversal {
|
|
if len(traversal) == 0 {
|
|
// then we've reached the end of the traversal, so we won't keep trying
|
|
// to walk through the block
|
|
return nil
|
|
}
|
|
|
|
// within a block the next step should always be a string attribute, but we
|
|
// want to be lenient.
|
|
|
|
current, ok := coerceToAttribute(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if attr, ok := current.(hcl.TraverseAttr); ok {
|
|
// this is the valid case, we're expecting an attribute that's going to
|
|
// point to an attribute or a block
|
|
|
|
if attr, ok := block.Attributes[attr.Name]; ok {
|
|
return append(hcl.Traversal{current}, walkAttribute(attr, traversal[1:])...)
|
|
}
|
|
|
|
if block, ok := block.BlockTypes[attr.Name]; ok {
|
|
return append(hcl.Traversal{current}, walkBlockType(block, traversal[1:])...)
|
|
}
|
|
}
|
|
|
|
// if nothing was triggered, this was an invalid reference so we'll cut the
|
|
// traversal here and return nothing.
|
|
|
|
return nil
|
|
}
|
|
|
|
// walkBlock walks through the block following the given traversal. If it finds
|
|
// any invalid steps in the traversal, the walk is halted and the traversal
|
|
// until that point is returned. This effectively validates and corrects the
|
|
// given traversal.
|
|
func walkBlockType(block *configschema.NestedBlock, traversal hcl.Traversal) hcl.Traversal {
|
|
if len(traversal) == 0 {
|
|
return nil
|
|
}
|
|
|
|
switch block.Nesting {
|
|
case configschema.NestingSingle, configschema.NestingGroup:
|
|
// we don't expect an intermediary step for single or group nested
|
|
// blocks, so we can just keep going.
|
|
return walkBlock(&block.Block, traversal)
|
|
case configschema.NestingList:
|
|
// this should be an index type, but we'll tolerate an attribute as long
|
|
// as it's a number
|
|
current, ok := coerceToIntegerIndex(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return append(hcl.Traversal{current}, walkBlock(&block.Block, traversal[1:])...)
|
|
case configschema.NestingMap:
|
|
// this should be an index type, but we'll tolerate an attribute
|
|
current, ok := coerceToStringIndex(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return append(hcl.Traversal{current}, walkBlock(&block.Block, traversal[1:])...)
|
|
case configschema.NestingSet:
|
|
return nil // can't reference into a set
|
|
}
|
|
|
|
// above switch should have been exhaustive
|
|
return nil
|
|
}
|
|
|
|
// walkAttribute walks through the attribute following the given traversal. If
|
|
// it finds any invalid steps in the traversal, the walk is haled and the
|
|
// traversal until that point is returned. This effectively validates and
|
|
// corrects the given traversal.
|
|
func walkAttribute(attr *configschema.Attribute, traversal hcl.Traversal) hcl.Traversal {
|
|
if len(traversal) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if attr.NestedType != nil {
|
|
return walkNestedAttribute(attr.NestedType, traversal)
|
|
}
|
|
|
|
return walkType(attr.Type, traversal)
|
|
}
|
|
|
|
func walkNestedAttributes(attrs map[string]*configschema.Attribute, traversal hcl.Traversal) hcl.Traversal {
|
|
if len(traversal) == 0 {
|
|
return nil
|
|
}
|
|
|
|
current, ok := coerceToAttribute(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
if attr, ok := attrs[current.(hcl.TraverseAttr).Name]; ok {
|
|
return append(hcl.Traversal{current}, walkAttribute(attr, traversal[1:])...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func walkNestedAttribute(attr *configschema.Object, traversal hcl.Traversal) hcl.Traversal {
|
|
if len(traversal) == 0 {
|
|
return nil
|
|
}
|
|
|
|
switch attr.Nesting {
|
|
case configschema.NestingSingle, configschema.NestingGroup:
|
|
return walkNestedAttributes(attr.Attributes, traversal)
|
|
case configschema.NestingList:
|
|
// this should be an index type, but we'll tolerate an attribute as long
|
|
// as it's a number
|
|
current, ok := coerceToIntegerIndex(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return append(hcl.Traversal{current}, walkNestedAttributes(attr.Attributes, traversal[1:])...)
|
|
case configschema.NestingMap:
|
|
// this should be an index type, but we'll tolerate an attribute
|
|
current, ok := coerceToStringIndex(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return append(hcl.Traversal{current}, walkNestedAttributes(attr.Attributes, traversal[1:])...)
|
|
case configschema.NestingSet:
|
|
return nil // can't reference into a set
|
|
}
|
|
|
|
// above switch should have been exhaustive
|
|
return nil
|
|
}
|
|
|
|
func walkType(t cty.Type, traversal hcl.Traversal) hcl.Traversal {
|
|
if len(traversal) == 0 {
|
|
return nil
|
|
}
|
|
|
|
switch {
|
|
case t.IsPrimitiveType():
|
|
return nil // can't traverse into primitives
|
|
case t.IsListType():
|
|
current, ok := coerceToIntegerIndex(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return append(hcl.Traversal{current}, walkType(t.ElementType(), traversal[1:])...)
|
|
case t.IsMapType():
|
|
current, ok := coerceToStringIndex(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return append(hcl.Traversal{current}, walkType(t.ElementType(), traversal[1:])...)
|
|
case t.IsSetType():
|
|
return nil // can't traverse into sets
|
|
case t.IsObjectType():
|
|
current, ok := coerceToAttribute(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
key := current.(hcl.TraverseAttr).Name
|
|
if !t.HasAttribute(key) {
|
|
return nil
|
|
}
|
|
return append(hcl.Traversal{current}, walkType(t.AttributeType(key), traversal[1:])...)
|
|
case t.IsTupleType():
|
|
current, ok := coerceToIntegerIndex(traversal[0])
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
key := current.(hcl.TraverseIndex).Key
|
|
if !key.IsKnown() {
|
|
return nil // we can't keep traversing if the index is unknown
|
|
}
|
|
|
|
bf := key.AsBigFloat()
|
|
if !bf.IsInt() {
|
|
return nil
|
|
}
|
|
|
|
ix, _ := bf.Int64()
|
|
types := t.TupleElementTypes()
|
|
if ix < 0 || ix >= int64(len(types)) {
|
|
return nil
|
|
}
|
|
|
|
return append(hcl.Traversal{current}, walkType(types[int(ix)], traversal[1:])...)
|
|
}
|
|
|
|
return nil // the above should have been exhaustive
|
|
}
|
|
|
|
// coerceToAttribute converts the provided step into a hcl.TraverseAttr if
|
|
// possible.
|
|
func coerceToAttribute(step hcl.Traverser) (hcl.Traverser, bool) {
|
|
|
|
switch step := step.(type) {
|
|
case hcl.TraverseIndex:
|
|
if !step.Key.IsKnown() {
|
|
// this is the only failure case here - we can't put unknown values
|
|
// into attributes so we return false
|
|
return step, false
|
|
}
|
|
|
|
name, err := convert.Convert(step.Key, cty.String)
|
|
if err != nil {
|
|
// integers should be able to be converted into strings, so this
|
|
// should never happen
|
|
return step, false
|
|
}
|
|
|
|
// all else being good, package this up as an attribute and keep
|
|
// going
|
|
|
|
return hcl.TraverseAttr{
|
|
Name: name.AsString(),
|
|
SrcRange: step.SrcRange,
|
|
}, true
|
|
case hcl.TraverseAttr:
|
|
return step, true
|
|
}
|
|
|
|
// we'll just give up if we see any other types, but we shouldn't see
|
|
// anything except index and attribute traversals by this point.
|
|
return step, false
|
|
}
|
|
|
|
// coerceToStringIndex converts the provided step into a string-valued
|
|
// hcl.TraverseIndex if possible.
|
|
func coerceToStringIndex(step hcl.Traverser) (hcl.Traverser, bool) {
|
|
switch step := step.(type) {
|
|
case hcl.TraverseIndex:
|
|
key, err := convert.Convert(step.Key, cty.String)
|
|
if err != nil {
|
|
return step, false
|
|
}
|
|
|
|
return hcl.TraverseIndex{
|
|
Key: key,
|
|
SrcRange: step.SrcRange,
|
|
}, true
|
|
case hcl.TraverseAttr:
|
|
return hcl.TraverseIndex{
|
|
Key: cty.StringVal(step.Name),
|
|
SrcRange: step.SrcRange,
|
|
}, true
|
|
}
|
|
|
|
// we'll just give up if we see any other types, but we shouldn't see
|
|
// anything except index and attribute traversals by this point.
|
|
return step, false
|
|
}
|
|
|
|
// coerceToIntegerIndex converts the provided step into an int-valued
|
|
// hcl.TraverseIndex if possible.
|
|
func coerceToIntegerIndex(step hcl.Traverser) (hcl.Traverser, bool) {
|
|
switch step := step.(type) {
|
|
case hcl.TraverseIndex:
|
|
key, err := convert.Convert(step.Key, cty.Number)
|
|
if err != nil {
|
|
return step, false
|
|
}
|
|
|
|
return hcl.TraverseIndex{
|
|
Key: key,
|
|
SrcRange: step.SrcRange,
|
|
}, true
|
|
case hcl.TraverseAttr:
|
|
number, err := cty.ParseNumberVal(step.Name)
|
|
if err != nil {
|
|
return step, false
|
|
}
|
|
|
|
return hcl.TraverseIndex{
|
|
Key: number,
|
|
SrcRange: step.SrcRange,
|
|
}, true
|
|
}
|
|
|
|
// we'll just give up if we see any other types, but we shouldn't see
|
|
// anything except index and attribute traversals by this point.
|
|
return step, false
|
|
}
|