github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/tfhcl/expressions_hclext.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package tfhcl
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/terraform-linters/tflint-plugin-sdk/hclext"
     9  )
    10  
    11  // ExpandExpressionsHCLExt is ExpandVariablesHCLExt which returns
    12  // []hcl.Expression instead of []hcl.Traversal.
    13  func ExpandExpressionsHCLExt(body hcl.Body, schema *hclext.BodySchema) []hcl.Expression {
    14  	rootNode := WalkExpandExpressions(body)
    15  	return walkExpressionsWithHCLExt(rootNode, schema)
    16  }
    17  
    18  func walkExpressionsWithHCLExt(node WalkExpressionsNode, schema *hclext.BodySchema) []hcl.Expression {
    19  	exprs, children := node.Visit(extendSchema(asHCLSchema(schema)))
    20  
    21  	if len(children) > 0 {
    22  		childSchemas := childBlockTypes(schema)
    23  		for _, child := range children {
    24  			if childSchema, exists := childSchemas[child.BlockTypeName]; exists {
    25  				exprs = append(exprs, walkExpressionsWithHCLExt(child.Node, childSchema.Body)...)
    26  			}
    27  		}
    28  	}
    29  
    30  	return exprs
    31  }
    32  
    33  // WalkExpandExpressions is dynblock.WalkExpandVariables for expressions.
    34  func WalkExpandExpressions(body hcl.Body) WalkExpressionsNode {
    35  	return WalkExpressionsNode{body: body}
    36  }
    37  
    38  type WalkExpressionsNode struct {
    39  	body hcl.Body
    40  }
    41  
    42  type WalkExpressionsChild struct {
    43  	BlockTypeName string
    44  	Node          WalkExpressionsNode
    45  }
    46  
    47  // Visit returns the expressions required for any "dynamic" blocks
    48  // directly in the body associated with this node, and also returns any child
    49  // nodes that must be visited in order to continue the walk.
    50  //
    51  // Each child node has its associated block type name given in its BlockTypeName
    52  // field, which the calling application should use to determine the appropriate
    53  // schema for the content of each child node and pass it to the child node's
    54  // own Visit method to continue the walk recursively.
    55  func (n WalkExpressionsNode) Visit(schema *hcl.BodySchema) (exprs []hcl.Expression, children []WalkExpressionsChild) {
    56  	extSchema := n.extendSchema(schema)
    57  	container, _, _ := n.body.PartialContent(extSchema)
    58  	if container == nil {
    59  		return exprs, children
    60  	}
    61  
    62  	children = make([]WalkExpressionsChild, 0, len(container.Blocks))
    63  
    64  	for _, attr := range container.Attributes {
    65  		exprs = append(exprs, attr.Expr)
    66  	}
    67  
    68  	for _, block := range container.Blocks {
    69  		switch block.Type {
    70  
    71  		case "dynamic":
    72  			blockTypeName := block.Labels[0]
    73  			inner, _, _ := block.Body.PartialContent(variableDetectionInnerSchema)
    74  			if inner == nil {
    75  				continue
    76  			}
    77  
    78  			if attr, exists := inner.Attributes["for_each"]; exists {
    79  				exprs = append(exprs, attr.Expr)
    80  			}
    81  			if attr, exists := inner.Attributes["labels"]; exists {
    82  				exprs = append(exprs, attr.Expr)
    83  			}
    84  
    85  			for _, contentBlock := range inner.Blocks {
    86  				// We only request "content" blocks in our schema, so we know
    87  				// any blocks we find here will be content blocks. We require
    88  				// exactly one content block for actual expansion, but we'll
    89  				// be more liberal here so that callers can still collect
    90  				// expressions from erroneous "dynamic" blocks.
    91  				children = append(children, WalkExpressionsChild{
    92  					BlockTypeName: blockTypeName,
    93  					Node: WalkExpressionsNode{
    94  						body: contentBlock.Body,
    95  					},
    96  				})
    97  			}
    98  
    99  		default:
   100  			children = append(children, WalkExpressionsChild{
   101  				BlockTypeName: block.Type,
   102  				Node: WalkExpressionsNode{
   103  					body: block.Body,
   104  				},
   105  			})
   106  
   107  		}
   108  	}
   109  
   110  	return exprs, children
   111  }
   112  
   113  func (c WalkExpressionsNode) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
   114  	// We augment the requested schema to also include our special "dynamic"
   115  	// block type, since then we'll get instances of it interleaved with
   116  	// all of the literal child blocks we must also include.
   117  	extSchema := &hcl.BodySchema{
   118  		Attributes: schema.Attributes,
   119  		Blocks:     make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+1),
   120  	}
   121  	copy(extSchema.Blocks, schema.Blocks)
   122  	extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
   123  
   124  	return extSchema
   125  }
   126  
   127  // This is a more relaxed schema than what's in schema.go, since we
   128  // want to maximize the amount of variables we can find even if there
   129  // are erroneous blocks.
   130  var variableDetectionInnerSchema = &hcl.BodySchema{
   131  	Attributes: []hcl.AttributeSchema{
   132  		{
   133  			Name:     "for_each",
   134  			Required: false,
   135  		},
   136  		{
   137  			Name:     "labels",
   138  			Required: false,
   139  		},
   140  		{
   141  			Name:     "iterator",
   142  			Required: false,
   143  		},
   144  	},
   145  	Blocks: []hcl.BlockHeaderSchema{
   146  		{
   147  			Type: "content",
   148  		},
   149  	},
   150  }