hcl_block.go (228 lines of code) (raw):
package golden
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"
)
type HclBlock struct {
*hclsyntax.Block
wb *hclwrite.Block
*ForEach
attributes map[string]*HclAttribute
blocks []*HclBlock
}
func NewHclBlock(rb *hclsyntax.Block, wb *hclwrite.Block, each *ForEach) *HclBlock {
hb := &HclBlock{
Block: rb,
wb: wb,
ForEach: each,
attributes: make(map[string]*HclAttribute),
}
for n, ra := range rb.Body.Attributes {
hb.attributes[n] = NewHclAttribute(ra, wb.Body().Attributes()[n])
}
for i, nrb := range rb.Body.Blocks {
hb.blocks = append(hb.blocks, NewHclBlock(nrb, wb.Body().Blocks()[i], nil))
}
return hb
}
func (hb *HclBlock) Attributes() map[string]*HclAttribute {
return hb.attributes
}
func (hb *HclBlock) NestedBlocks() []*HclBlock {
return hb.blocks
}
type ForEach struct {
key cty.Value
value cty.Value
}
func NewForEach(key, value cty.Value) *ForEach {
return &ForEach{
key: key,
value: value,
}
}
func AsHclBlocks(syntaxBlocks hclsyntax.Blocks, writeBlocks []*hclwrite.Block) []*HclBlock {
var blocks []*HclBlock
for i, b := range syntaxBlocks {
var rbs = readRawHclSyntaxBlock(b)
var wbs = readRawHclWriteBlock(writeBlocks[i])
for i, hb := range rbs {
blocks = append(blocks, NewHclBlock(hb, wbs[i], nil))
}
}
return blocks
}
func (hb *HclBlock) ExpandDynamicBlocks(evalContext *hcl.EvalContext) (*HclBlock, error) {
newHb := &HclBlock{
Block: hb.Block,
wb: hb.wb,
ForEach: hb.ForEach,
attributes: hb.attributes,
blocks: []*HclBlock{},
}
var newNestedBlocks []*hclsyntax.Block
for _, block := range hb.blocks {
if block.Type != "dynamic" {
expandedBlock, err := block.ExpandDynamicBlocks(evalContext)
if err != nil {
return nil, err
}
if err = expandedBlock.evaluateAttributes(evalContext); err != nil {
return nil, err
}
newHb.blocks = append(newHb.blocks, expandedBlock)
newNestedBlocks = append(newNestedBlocks, expandedBlock.Block)
continue
}
forEachAttr, ok := block.Attributes()["for_each"]
if !ok {
return nil, fmt.Errorf("`dynamic` block must have `for_each` attribute")
}
forEachValue, diag := forEachAttr.Expr.Value(evalContext)
if diag.HasErrors() {
return nil, diag
}
if !forEachValue.CanIterateElements() {
return nil, fmt.Errorf("incorrect type for `for_each`, must be a collection")
}
iterator := forEachValue.ElementIterator()
for iterator.Next() {
_, value := iterator.Element()
newContext := evalContext.NewChild()
newContext.Variables = map[string]cty.Value{
block.Labels[0]: cty.ObjectVal(map[string]cty.Value{
"value": value,
}),
}
for _, innerBlock := range block.blocks {
innerBlock = CloneHclBlock(innerBlock)
if innerBlock.Type != "content" {
return nil, fmt.Errorf("`dynamic` block should contain `content` block only")
}
expandedInnerBlock, err := innerBlock.ExpandDynamicBlocks(newContext)
if err != nil {
return nil, err
}
expandedInnerBlock.Type = block.Labels[0]
if err = expandedInnerBlock.evaluateAttributes(newContext); err != nil {
return nil, err
}
newHb.blocks = append(newHb.blocks, expandedInnerBlock)
newNestedBlocks = append(newNestedBlocks, expandedInnerBlock.Block)
}
}
}
newHb.Body.Blocks = newNestedBlocks
return newHb, nil
}
func readRawHclSyntaxBlock(b *hclsyntax.Block) []*hclsyntax.Block {
switch b.Type {
case "locals":
{
var newBlocks []*hclsyntax.Block
for _, attr := range b.Body.Attributes {
newBlocks = append(newBlocks, &hclsyntax.Block{
Type: "local",
Labels: []string{"", attr.Name},
Body: &hclsyntax.Body{
Attributes: map[string]*hclsyntax.Attribute{
"value": {
Name: "value",
Expr: attr.Expr,
SrcRange: attr.SrcRange,
NameRange: attr.NameRange,
EqualsRange: attr.EqualsRange,
},
},
SrcRange: attr.NameRange,
EndRange: attr.SrcRange,
},
})
}
return newBlocks
}
case "variable":
{
return []*hclsyntax.Block{
{
Type: "variable",
Labels: append([]string{""}, b.Labels...),
Body: b.Body,
TypeRange: b.TypeRange,
LabelRanges: b.LabelRanges,
OpenBraceRange: b.OpenBraceRange,
CloseBraceRange: b.CloseBraceRange,
},
}
}
default:
return []*hclsyntax.Block{b}
}
}
func readRawHclWriteBlock(b *hclwrite.Block) []*hclwrite.Block {
if b.Type() != "locals" {
return []*hclwrite.Block{b}
}
var newBlocks []*hclwrite.Block
for n, attr := range b.Body().Attributes() {
nb := hclwrite.NewBlock("local", []string{"", n})
nb.Body().SetAttributeRaw("value", attr.Expr().BuildTokens(hclwrite.Tokens{}))
newBlocks = append(newBlocks, nb)
}
return newBlocks
}
func clone[T any](v *T) *T {
c := *v
return &c
}
func CloneHclBlock(hb *HclBlock) *HclBlock {
// Clone the hclsyntax.Block
cloneBlock := CloneHclSyntaxBlock(hb.Block)
// Clone the HclBlock
cloneHb := &HclBlock{
Block: cloneBlock,
wb: clone(hb.wb),
ForEach: hb.ForEach,
attributes: make(map[string]*HclAttribute),
blocks: make([]*HclBlock, len(hb.blocks)),
}
// Clone attributes
for name, attr := range hb.attributes {
cloneHb.attributes[name] = clone(attr)
}
// Clone blocks recursively
for i, block := range hb.blocks {
cloneHb.blocks[i] = CloneHclBlock(block)
}
return cloneHb
}
func CloneHclSyntaxBlock(hb *hclsyntax.Block) *hclsyntax.Block {
// Clone the block itself
cloneBlock := clone(hb)
// Clone the body
cloneBody := &hclsyntax.Body{
Attributes: make(hclsyntax.Attributes),
Blocks: make(hclsyntax.Blocks, len(hb.Body.Blocks)),
}
// Clone attributes
for name, attr := range hb.Body.Attributes {
cloneBody.Attributes[name] = clone(attr)
}
// Clone blocks recursively
for i, block := range hb.Body.Blocks {
cloneBody.Blocks[i] = CloneHclSyntaxBlock(block)
}
// Assign the cloned body to the cloned block
cloneBlock.Body = cloneBody
return cloneBlock
}
func (hb *HclBlock) evaluateAttributes(ctx *hcl.EvalContext) error {
for attributeName, attribute := range hb.Body.Attributes {
v, diag := attribute.Expr.Value(ctx)
if diag.HasErrors() {
return diag
}
hb.Body.Attributes[attributeName] = &hclsyntax.Attribute{
Name: attributeName,
Expr: &hclsyntax.LiteralValueExpr{
Val: v,
},
SrcRange: attribute.SrcRange,
}
}
return nil
}