github.com/hashicorp/hcl/v2@v2.20.0/ext/dynblock/variables.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package dynblock
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/zclconf/go-cty/cty"
     9  )
    10  
    11  // WalkVariables begins the recursive process of walking all expressions and
    12  // nested blocks in the given body and its child bodies while taking into
    13  // account any "dynamic" blocks.
    14  //
    15  // This function requires that the caller walk through the nested block
    16  // structure in the given body level-by-level so that an appropriate schema
    17  // can be provided at each level to inform further processing. This workflow
    18  // is thus easiest to use for calling applications that have some higher-level
    19  // schema representation available with which to drive this multi-step
    20  // process. If your application uses the hcldec package, you may be able to
    21  // use VariablesHCLDec instead for a more automatic approach.
    22  func WalkVariables(body hcl.Body) WalkVariablesNode {
    23  	return WalkVariablesNode{
    24  		body:           body,
    25  		includeContent: true,
    26  	}
    27  }
    28  
    29  // WalkExpandVariables is like Variables but it includes only the variables
    30  // required for successful block expansion, ignoring any variables referenced
    31  // inside block contents. The result is the minimal set of all variables
    32  // required for a call to Expand, excluding variables that would only be
    33  // needed to subsequently call Content or PartialContent on the expanded
    34  // body.
    35  func WalkExpandVariables(body hcl.Body) WalkVariablesNode {
    36  	return WalkVariablesNode{
    37  		body: body,
    38  	}
    39  }
    40  
    41  type WalkVariablesNode struct {
    42  	body hcl.Body
    43  	it   *iteration
    44  
    45  	includeContent bool
    46  }
    47  
    48  type WalkVariablesChild struct {
    49  	BlockTypeName string
    50  	Node          WalkVariablesNode
    51  }
    52  
    53  // Body returns the HCL Body associated with the child node, in case the caller
    54  // wants to do some sort of inspection of it in order to decide what schema
    55  // to pass to Visit.
    56  //
    57  // Most implementations should just fetch a fixed schema based on the
    58  // BlockTypeName field and not access this. Deciding on a schema dynamically
    59  // based on the body is a strange thing to do and generally necessary only if
    60  // your caller is already doing other bizarre things with HCL bodies.
    61  func (c WalkVariablesChild) Body() hcl.Body {
    62  	return c.Node.body
    63  }
    64  
    65  // Visit returns the variable traversals required for any "dynamic" blocks
    66  // directly in the body associated with this node, and also returns any child
    67  // nodes that must be visited in order to continue the walk.
    68  //
    69  // Each child node has its associated block type name given in its BlockTypeName
    70  // field, which the calling application should use to determine the appropriate
    71  // schema for the content of each child node and pass it to the child node's
    72  // own Visit method to continue the walk recursively.
    73  func (n WalkVariablesNode) Visit(schema *hcl.BodySchema) (vars []hcl.Traversal, children []WalkVariablesChild) {
    74  	extSchema := n.extendSchema(schema)
    75  	container, _, _ := n.body.PartialContent(extSchema)
    76  	if container == nil {
    77  		return vars, children
    78  	}
    79  
    80  	children = make([]WalkVariablesChild, 0, len(container.Blocks))
    81  
    82  	if n.includeContent {
    83  		for _, attr := range container.Attributes {
    84  			for _, traversal := range attr.Expr.Variables() {
    85  				var ours, inherited bool
    86  				if n.it != nil {
    87  					ours = traversal.RootName() == n.it.IteratorName
    88  					_, inherited = n.it.Inherited[traversal.RootName()]
    89  				}
    90  
    91  				if !(ours || inherited) {
    92  					vars = append(vars, traversal)
    93  				}
    94  			}
    95  		}
    96  	}
    97  
    98  	for _, block := range container.Blocks {
    99  		switch block.Type {
   100  
   101  		case "dynamic":
   102  			blockTypeName := block.Labels[0]
   103  			inner, _, _ := block.Body.PartialContent(variableDetectionInnerSchema)
   104  			if inner == nil {
   105  				continue
   106  			}
   107  
   108  			iteratorName := blockTypeName
   109  			if attr, exists := inner.Attributes["iterator"]; exists {
   110  				iterTraversal, _ := hcl.AbsTraversalForExpr(attr.Expr)
   111  				if len(iterTraversal) == 0 {
   112  					// Ignore this invalid dynamic block, since it'll produce
   113  					// an error if someone tries to extract content from it
   114  					// later anyway.
   115  					continue
   116  				}
   117  				iteratorName = iterTraversal.RootName()
   118  			}
   119  			blockIt := n.it.MakeChild(iteratorName, cty.DynamicVal, cty.DynamicVal)
   120  
   121  			if attr, exists := inner.Attributes["for_each"]; exists {
   122  				// Filter out iterator names inherited from parent blocks
   123  				for _, traversal := range attr.Expr.Variables() {
   124  					if _, inherited := blockIt.Inherited[traversal.RootName()]; !inherited {
   125  						vars = append(vars, traversal)
   126  					}
   127  				}
   128  			}
   129  			if attr, exists := inner.Attributes["labels"]; exists {
   130  				// Filter out both our own iterator name _and_ those inherited
   131  				// from parent blocks, since we provide _both_ of these to the
   132  				// label expressions.
   133  				for _, traversal := range attr.Expr.Variables() {
   134  					ours := traversal.RootName() == iteratorName
   135  					_, inherited := blockIt.Inherited[traversal.RootName()]
   136  
   137  					if !(ours || inherited) {
   138  						vars = append(vars, traversal)
   139  					}
   140  				}
   141  			}
   142  
   143  			for _, contentBlock := range inner.Blocks {
   144  				// We only request "content" blocks in our schema, so we know
   145  				// any blocks we find here will be content blocks. We require
   146  				// exactly one content block for actual expansion, but we'll
   147  				// be more liberal here so that callers can still collect
   148  				// variables from erroneous "dynamic" blocks.
   149  				children = append(children, WalkVariablesChild{
   150  					BlockTypeName: blockTypeName,
   151  					Node: WalkVariablesNode{
   152  						body:           contentBlock.Body,
   153  						it:             blockIt,
   154  						includeContent: n.includeContent,
   155  					},
   156  				})
   157  			}
   158  
   159  		default:
   160  			children = append(children, WalkVariablesChild{
   161  				BlockTypeName: block.Type,
   162  				Node: WalkVariablesNode{
   163  					body:           block.Body,
   164  					it:             n.it,
   165  					includeContent: n.includeContent,
   166  				},
   167  			})
   168  
   169  		}
   170  	}
   171  
   172  	return vars, children
   173  }
   174  
   175  func (n WalkVariablesNode) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
   176  	// We augment the requested schema to also include our special "dynamic"
   177  	// block type, since then we'll get instances of it interleaved with
   178  	// all of the literal child blocks we must also include.
   179  	extSchema := &hcl.BodySchema{
   180  		Attributes: schema.Attributes,
   181  		Blocks:     make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+1),
   182  	}
   183  	copy(extSchema.Blocks, schema.Blocks)
   184  	extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
   185  
   186  	return extSchema
   187  }
   188  
   189  // This is a more relaxed schema than what's in schema.go, since we
   190  // want to maximize the amount of variables we can find even if there
   191  // are erroneous blocks.
   192  var variableDetectionInnerSchema = &hcl.BodySchema{
   193  	Attributes: []hcl.AttributeSchema{
   194  		{
   195  			Name:     "for_each",
   196  			Required: false,
   197  		},
   198  		{
   199  			Name:     "labels",
   200  			Required: false,
   201  		},
   202  		{
   203  			Name:     "iterator",
   204  			Required: false,
   205  		},
   206  	},
   207  	Blocks: []hcl.BlockHeaderSchema{
   208  		{
   209  			Type: "content",
   210  		},
   211  	},
   212  }