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 }