2018-01-21 23:29:43 +00:00
|
|
|
package dynblock
|
|
|
|
|
|
|
|
import (
|
2019-09-09 23:08:19 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
2018-01-22 02:06:49 +00:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2018-01-21 23:29:43 +00:00
|
|
|
)
|
|
|
|
|
2019-03-18 23:28:30 +00:00
|
|
|
// 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.
|
2018-01-22 02:06:49 +00:00
|
|
|
//
|
|
|
|
// 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
|
2019-03-18 23:28:30 +00:00
|
|
|
// 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 {
|
2018-01-22 02:06:49 +00:00
|
|
|
return WalkVariablesNode{
|
|
|
|
body: body,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type WalkVariablesNode struct {
|
|
|
|
body hcl.Body
|
|
|
|
it *iteration
|
2019-03-18 23:28:30 +00:00
|
|
|
|
|
|
|
includeContent bool
|
2018-01-22 02:06:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type WalkVariablesChild struct {
|
|
|
|
BlockTypeName string
|
|
|
|
Node WalkVariablesNode
|
|
|
|
}
|
|
|
|
|
2019-03-27 22:38:17 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-01-22 02:06:49 +00:00
|
|
|
// 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)
|
2018-01-21 23:29:43 +00:00
|
|
|
if container == nil {
|
2018-01-22 02:06:49 +00:00
|
|
|
return vars, children
|
2018-01-21 23:29:43 +00:00
|
|
|
}
|
|
|
|
|
2018-01-22 02:06:49 +00:00
|
|
|
children = make([]WalkVariablesChild, 0, len(container.Blocks))
|
|
|
|
|
2019-03-18 23:28:30 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-21 23:29:43 +00:00
|
|
|
for _, block := range container.Blocks {
|
2018-01-22 02:06:49 +00:00
|
|
|
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
|
|
|
|
}
|
2018-01-21 23:29:43 +00:00
|
|
|
iteratorName = iterTraversal.RootName()
|
|
|
|
}
|
2018-01-22 02:06:49 +00:00
|
|
|
blockIt := n.it.MakeChild(iteratorName, cty.DynamicVal, cty.DynamicVal)
|
2018-01-21 23:29:43 +00:00
|
|
|
|
2018-01-22 02:06:49 +00:00
|
|
|
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)
|
|
|
|
}
|
2018-01-21 23:29:43 +00:00
|
|
|
}
|
|
|
|
}
|
2018-01-22 02:06:49 +00:00
|
|
|
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{
|
2019-03-18 23:28:30 +00:00
|
|
|
body: contentBlock.Body,
|
|
|
|
it: blockIt,
|
|
|
|
includeContent: n.includeContent,
|
2018-01-22 02:06:49 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
children = append(children, WalkVariablesChild{
|
|
|
|
BlockTypeName: block.Type,
|
|
|
|
Node: WalkVariablesNode{
|
2019-03-18 23:28:30 +00:00
|
|
|
body: block.Body,
|
|
|
|
it: n.it,
|
|
|
|
includeContent: n.includeContent,
|
2018-01-22 02:06:49 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2018-01-21 23:29:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-22 02:06:49 +00:00
|
|
|
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
|
2018-01-21 23:29:43 +00:00
|
|
|
}
|
|
|
|
|
2018-01-22 02:06:49 +00:00
|
|
|
// This is a more relaxed schema than what's in schema.go, since we
|
2018-01-21 23:29:43 +00:00
|
|
|
// 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,
|
|
|
|
},
|
|
|
|
},
|
2018-01-22 02:06:49 +00:00
|
|
|
Blocks: []hcl.BlockHeaderSchema{
|
|
|
|
{
|
|
|
|
Type: "content",
|
|
|
|
},
|
|
|
|
},
|
2018-01-21 23:29:43 +00:00
|
|
|
}
|