-
Notifications
You must be signed in to change notification settings - Fork 358
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add terraform/tfhcl package as a fork of hcl/ext/dynblock
- Loading branch information
Showing
7 changed files
with
720 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
package tfhcl | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
"github.com/zclconf/go-cty/cty" | ||
) | ||
|
||
// expandBody wraps another hcl.Body and expands any "dynamic" blocks found | ||
// inside whenever Content or PartialContent is called. | ||
type expandBody struct { | ||
original hcl.Body | ||
forEachCtx *hcl.EvalContext | ||
iteration *iteration // non-nil if we're nested inside another "dynamic" block | ||
|
||
// These are used with PartialContent to produce a "remaining items" | ||
// body to return. They are nil on all bodies fresh out of the transformer. | ||
// | ||
// Note that this is re-implemented here rather than delegating to the | ||
// existing support required by the underlying body because we need to | ||
// retain access to the entire original body on subsequent decode operations | ||
// so we can retain any "dynamic" blocks for types we didn't take consume | ||
// on the first pass. | ||
hiddenAttrs map[string]struct{} | ||
hiddenBlocks map[string]hcl.BlockHeaderSchema | ||
} | ||
|
||
func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { | ||
extSchema := b.extendSchema(schema) | ||
rawContent, diags := b.original.Content(extSchema) | ||
|
||
blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false) | ||
diags = append(diags, blockDiags...) | ||
attrs := b.prepareAttributes(rawContent.Attributes) | ||
|
||
content := &hcl.BodyContent{ | ||
Attributes: attrs, | ||
Blocks: blocks, | ||
MissingItemRange: b.original.MissingItemRange(), | ||
} | ||
|
||
return content, diags | ||
} | ||
|
||
func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { | ||
extSchema := b.extendSchema(schema) | ||
rawContent, _, diags := b.original.PartialContent(extSchema) | ||
// We discard the "remain" argument above because we're going to construct | ||
// our own remain that also takes into account remaining "dynamic" blocks. | ||
|
||
blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true) | ||
diags = append(diags, blockDiags...) | ||
attrs := b.prepareAttributes(rawContent.Attributes) | ||
|
||
content := &hcl.BodyContent{ | ||
Attributes: attrs, | ||
Blocks: blocks, | ||
MissingItemRange: b.original.MissingItemRange(), | ||
} | ||
|
||
remain := &expandBody{ | ||
original: b.original, | ||
forEachCtx: b.forEachCtx, | ||
iteration: b.iteration, | ||
hiddenAttrs: make(map[string]struct{}), | ||
hiddenBlocks: make(map[string]hcl.BlockHeaderSchema), | ||
} | ||
for name := range b.hiddenAttrs { | ||
remain.hiddenAttrs[name] = struct{}{} | ||
} | ||
for typeName, blockS := range b.hiddenBlocks { | ||
remain.hiddenBlocks[typeName] = blockS | ||
} | ||
for _, attrS := range schema.Attributes { | ||
remain.hiddenAttrs[attrS.Name] = struct{}{} | ||
} | ||
for _, blockS := range schema.Blocks { | ||
remain.hiddenBlocks[blockS.Type] = blockS | ||
} | ||
|
||
return content, remain, diags | ||
} | ||
|
||
func (b *expandBody) 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)+len(b.hiddenBlocks)+1), | ||
} | ||
copy(extSchema.Blocks, schema.Blocks) | ||
extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema) | ||
|
||
// If we have any hiddenBlocks then we also need to register those here | ||
// so that a call to "Content" on the underlying body won't fail. | ||
// (We'll filter these out again once we process the result of either | ||
// Content or PartialContent.) | ||
for _, blockS := range b.hiddenBlocks { | ||
extSchema.Blocks = append(extSchema.Blocks, blockS) | ||
} | ||
|
||
// If we have any hiddenAttrs then we also need to register these, for | ||
// the same reason as we deal with hiddenBlocks above. | ||
if len(b.hiddenAttrs) != 0 { | ||
newAttrs := make([]hcl.AttributeSchema, len(schema.Attributes), len(schema.Attributes)+len(b.hiddenAttrs)) | ||
copy(newAttrs, extSchema.Attributes) | ||
for name := range b.hiddenAttrs { | ||
newAttrs = append(newAttrs, hcl.AttributeSchema{ | ||
Name: name, | ||
Required: false, | ||
}) | ||
} | ||
extSchema.Attributes = newAttrs | ||
} | ||
|
||
return extSchema | ||
} | ||
|
||
func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) hcl.Attributes { | ||
if len(b.hiddenAttrs) == 0 && b.iteration == nil { | ||
// Easy path: just pass through the attrs from the original body verbatim | ||
return rawAttrs | ||
} | ||
|
||
// Otherwise we have some work to do: we must filter out any attributes | ||
// that are hidden (since a previous PartialContent call already saw these) | ||
// and wrap the expressions of the inner attributes so that they will | ||
// have access to our iteration variables. | ||
attrs := make(hcl.Attributes, len(rawAttrs)) | ||
for name, rawAttr := range rawAttrs { | ||
if _, hidden := b.hiddenAttrs[name]; hidden { | ||
continue | ||
} | ||
if b.iteration != nil { | ||
attr := *rawAttr // shallow copy so we can mutate it | ||
attr.Expr = exprWrap{ | ||
Expression: attr.Expr, | ||
i: b.iteration, | ||
} | ||
attrs[name] = &attr | ||
} else { | ||
// If we have no active iteration then no wrapping is required. | ||
attrs[name] = rawAttr | ||
} | ||
} | ||
return attrs | ||
} | ||
|
||
func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, partial bool) (hcl.Blocks, hcl.Diagnostics) { | ||
var blocks hcl.Blocks | ||
var diags hcl.Diagnostics | ||
|
||
for _, rawBlock := range rawBlocks { | ||
switch rawBlock.Type { | ||
case "dynamic": | ||
realBlockType := rawBlock.Labels[0] | ||
if _, hidden := b.hiddenBlocks[realBlockType]; hidden { | ||
continue | ||
} | ||
|
||
var blockS *hcl.BlockHeaderSchema | ||
for _, candidate := range schema.Blocks { | ||
if candidate.Type == realBlockType { | ||
blockS = &candidate | ||
break | ||
} | ||
} | ||
if blockS == nil { | ||
// Not a block type that the caller requested. | ||
if !partial { | ||
diags = append(diags, &hcl.Diagnostic{ | ||
Severity: hcl.DiagError, | ||
Summary: "Unsupported block type", | ||
Detail: fmt.Sprintf("Blocks of type %q are not expected here.", realBlockType), | ||
Subject: &rawBlock.LabelRanges[0], | ||
}) | ||
} | ||
continue | ||
} | ||
|
||
spec, specDiags := b.decodeSpec(blockS, rawBlock) | ||
diags = append(diags, specDiags...) | ||
if specDiags.HasErrors() { | ||
continue | ||
} | ||
|
||
if spec.forEachVal.IsKnown() { | ||
for it := spec.forEachVal.ElementIterator(); it.Next(); { | ||
key, value := it.Element() | ||
i := b.iteration.MakeChild(spec.iteratorName, key, value) | ||
|
||
block, blockDiags := spec.newBlock(i, b.forEachCtx) | ||
diags = append(diags, blockDiags...) | ||
if block != nil { | ||
// Attach our new iteration context so that attributes | ||
// and other nested blocks can refer to our iterator. | ||
block.Body = b.expandChild(block.Body, i) | ||
blocks = append(blocks, block) | ||
} | ||
} | ||
} else { | ||
// If our top-level iteration value isn't known then we | ||
// substitute an unknownBody, which will cause the entire block | ||
// to evaluate to an unknown value. | ||
i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal) | ||
block, blockDiags := spec.newBlock(i, b.forEachCtx) | ||
diags = append(diags, blockDiags...) | ||
if block != nil { | ||
block.Body = unknownBody{b.expandChild(block.Body, i)} | ||
blocks = append(blocks, block) | ||
} | ||
} | ||
|
||
default: | ||
if _, hidden := b.hiddenBlocks[rawBlock.Type]; !hidden { | ||
// A static block doesn't create a new iteration context, but | ||
// it does need to inherit _our own_ iteration context in | ||
// case it contains expressions that refer to our inherited | ||
// iterators, or nested "dynamic" blocks. | ||
expandedBlock := *rawBlock // shallow copy | ||
expandedBlock.Body = b.expandChild(rawBlock.Body, b.iteration) | ||
blocks = append(blocks, &expandedBlock) | ||
} | ||
} | ||
} | ||
|
||
return blocks, diags | ||
} | ||
|
||
func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body { | ||
chiCtx := i.EvalContext(b.forEachCtx) | ||
ret := Expand(child, chiCtx) | ||
ret.(*expandBody).iteration = i | ||
return ret | ||
} | ||
|
||
func (b *expandBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { | ||
// blocks aren't allowed in JustAttributes mode and this body can | ||
// only produce blocks, so we'll just pass straight through to our | ||
// underlying body here. | ||
return b.original.JustAttributes() | ||
} | ||
|
||
func (b *expandBody) MissingItemRange() hcl.Range { | ||
return b.original.MissingItemRange() | ||
} |
Oops, something went wrong.