6c4344623b
The main HCL package is more visible this way, and so it's easier than having to pick it out from dozens of other package directories.
210 lines
6.6 KiB
Go
210 lines
6.6 KiB
Go
package dynblock
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// WalkVariables begins the recursive process of walking all expressions and
|
|
// nested blocks in the given body and its child bodies while taking into
|
|
// account any "dynamic" blocks.
|
|
//
|
|
// This function requires that the caller walk through the nested block
|
|
// structure in the given body level-by-level so that an appropriate schema
|
|
// can be provided at each level to inform further processing. This workflow
|
|
// is thus easiest to use for calling applications that have some higher-level
|
|
// schema representation available with which to drive this multi-step
|
|
// process. If your application uses the hcldec package, you may be able to
|
|
// use VariablesHCLDec instead for a more automatic approach.
|
|
func WalkVariables(body hcl.Body) WalkVariablesNode {
|
|
return WalkVariablesNode{
|
|
body: body,
|
|
includeContent: true,
|
|
}
|
|
}
|
|
|
|
// WalkExpandVariables is like Variables but it includes only the variables
|
|
// required for successful block expansion, ignoring any variables referenced
|
|
// inside block contents. The result is the minimal set of all variables
|
|
// required for a call to Expand, excluding variables that would only be
|
|
// needed to subsequently call Content or PartialContent on the expanded
|
|
// body.
|
|
func WalkExpandVariables(body hcl.Body) WalkVariablesNode {
|
|
return WalkVariablesNode{
|
|
body: body,
|
|
}
|
|
}
|
|
|
|
type WalkVariablesNode struct {
|
|
body hcl.Body
|
|
it *iteration
|
|
|
|
includeContent bool
|
|
}
|
|
|
|
type WalkVariablesChild struct {
|
|
BlockTypeName string
|
|
Node WalkVariablesNode
|
|
}
|
|
|
|
// Body returns the HCL Body associated with the child node, in case the caller
|
|
// wants to do some sort of inspection of it in order to decide what schema
|
|
// to pass to Visit.
|
|
//
|
|
// Most implementations should just fetch a fixed schema based on the
|
|
// BlockTypeName field and not access this. Deciding on a schema dynamically
|
|
// based on the body is a strange thing to do and generally necessary only if
|
|
// your caller is already doing other bizarre things with HCL bodies.
|
|
func (c WalkVariablesChild) Body() hcl.Body {
|
|
return c.Node.body
|
|
}
|
|
|
|
// Visit returns the variable traversals required for any "dynamic" blocks
|
|
// directly in the body associated with this node, and also returns any child
|
|
// nodes that must be visited in order to continue the walk.
|
|
//
|
|
// Each child node has its associated block type name given in its BlockTypeName
|
|
// field, which the calling application should use to determine the appropriate
|
|
// schema for the content of each child node and pass it to the child node's
|
|
// own Visit method to continue the walk recursively.
|
|
func (n WalkVariablesNode) Visit(schema *hcl.BodySchema) (vars []hcl.Traversal, children []WalkVariablesChild) {
|
|
extSchema := n.extendSchema(schema)
|
|
container, _, _ := n.body.PartialContent(extSchema)
|
|
if container == nil {
|
|
return vars, children
|
|
}
|
|
|
|
children = make([]WalkVariablesChild, 0, len(container.Blocks))
|
|
|
|
if n.includeContent {
|
|
for _, attr := range container.Attributes {
|
|
for _, traversal := range attr.Expr.Variables() {
|
|
var ours, inherited bool
|
|
if n.it != nil {
|
|
ours = traversal.RootName() == n.it.IteratorName
|
|
_, inherited = n.it.Inherited[traversal.RootName()]
|
|
}
|
|
|
|
if !(ours || inherited) {
|
|
vars = append(vars, traversal)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, block := range container.Blocks {
|
|
switch block.Type {
|
|
|
|
case "dynamic":
|
|
blockTypeName := block.Labels[0]
|
|
inner, _, _ := block.Body.PartialContent(variableDetectionInnerSchema)
|
|
if inner == nil {
|
|
continue
|
|
}
|
|
|
|
iteratorName := blockTypeName
|
|
if attr, exists := inner.Attributes["iterator"]; exists {
|
|
iterTraversal, _ := hcl.AbsTraversalForExpr(attr.Expr)
|
|
if len(iterTraversal) == 0 {
|
|
// Ignore this invalid dynamic block, since it'll produce
|
|
// an error if someone tries to extract content from it
|
|
// later anyway.
|
|
continue
|
|
}
|
|
iteratorName = iterTraversal.RootName()
|
|
}
|
|
blockIt := n.it.MakeChild(iteratorName, cty.DynamicVal, cty.DynamicVal)
|
|
|
|
if attr, exists := inner.Attributes["for_each"]; exists {
|
|
// Filter out iterator names inherited from parent blocks
|
|
for _, traversal := range attr.Expr.Variables() {
|
|
if _, inherited := blockIt.Inherited[traversal.RootName()]; !inherited {
|
|
vars = append(vars, traversal)
|
|
}
|
|
}
|
|
}
|
|
if attr, exists := inner.Attributes["labels"]; exists {
|
|
// Filter out both our own iterator name _and_ those inherited
|
|
// from parent blocks, since we provide _both_ of these to the
|
|
// label expressions.
|
|
for _, traversal := range attr.Expr.Variables() {
|
|
ours := traversal.RootName() == iteratorName
|
|
_, inherited := blockIt.Inherited[traversal.RootName()]
|
|
|
|
if !(ours || inherited) {
|
|
vars = append(vars, traversal)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, contentBlock := range inner.Blocks {
|
|
// We only request "content" blocks in our schema, so we know
|
|
// any blocks we find here will be content blocks. We require
|
|
// exactly one content block for actual expansion, but we'll
|
|
// be more liberal here so that callers can still collect
|
|
// variables from erroneous "dynamic" blocks.
|
|
children = append(children, WalkVariablesChild{
|
|
BlockTypeName: blockTypeName,
|
|
Node: WalkVariablesNode{
|
|
body: contentBlock.Body,
|
|
it: blockIt,
|
|
includeContent: n.includeContent,
|
|
},
|
|
})
|
|
}
|
|
|
|
default:
|
|
children = append(children, WalkVariablesChild{
|
|
BlockTypeName: block.Type,
|
|
Node: WalkVariablesNode{
|
|
body: block.Body,
|
|
it: n.it,
|
|
includeContent: n.includeContent,
|
|
},
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
return vars, children
|
|
}
|
|
|
|
func (n WalkVariablesNode) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
|
|
// We augment the requested schema to also include our special "dynamic"
|
|
// block type, since then we'll get instances of it interleaved with
|
|
// all of the literal child blocks we must also include.
|
|
extSchema := &hcl.BodySchema{
|
|
Attributes: schema.Attributes,
|
|
Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+1),
|
|
}
|
|
copy(extSchema.Blocks, schema.Blocks)
|
|
extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
|
|
|
|
return extSchema
|
|
}
|
|
|
|
// This is a more relaxed schema than what's in schema.go, since we
|
|
// want to maximize the amount of variables we can find even if there
|
|
// are erroneous blocks.
|
|
var variableDetectionInnerSchema = &hcl.BodySchema{
|
|
Attributes: []hcl.AttributeSchema{
|
|
{
|
|
Name: "for_each",
|
|
Required: false,
|
|
},
|
|
{
|
|
Name: "labels",
|
|
Required: false,
|
|
},
|
|
{
|
|
Name: "iterator",
|
|
Required: false,
|
|
},
|
|
},
|
|
Blocks: []hcl.BlockHeaderSchema{
|
|
{
|
|
Type: "content",
|
|
},
|
|
},
|
|
}
|