pkg/transform_new_block.go (149 lines of code) (raw):
package pkg
import (
"fmt"
"strings"
"github.com/Azure/golden"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
avmfix "github.com/lonegunmanb/avmfix/pkg"
"github.com/zclconf/go-cty/cty"
)
var _ golden.ApplyBlock = &NewBlockTransform{}
var _ golden.CustomDecode = &NewBlockTransform{}
type NewBlockTransform struct {
*golden.BaseBlock
*BaseTransform
NewBlockType string `hcl:"new_block_type"`
FileName string `hcl:"filename" validate:"endswith=.tf"`
Labels []string `hcl:"labels,optional"`
NewBody string `hcl:"body,optional"`
newWriteBlock *hclwrite.Block
}
func (n *NewBlockTransform) Decode(block *golden.HclBlock, context *hcl.EvalContext) error {
var err error
n.NewBlockType, err = getRequiredStringAttribute("new_block_type", block, context)
if err != nil {
return err
}
n.FileName, err = getRequiredStringAttribute("filename", block, context)
if err != nil {
return err
}
var labels []string
labelsAttr, ok := block.Attributes()["labels"]
if ok {
labelsValue, err := labelsAttr.Value(context)
if err != nil {
return fmt.Errorf("error while evaluating labels: %+v", err)
}
for i := 0; i < labelsValue.LengthInt(); i++ {
labels = append(labels, labelsValue.Index(cty.NumberIntVal(int64(i))).AsString())
}
}
n.Labels = labels
bodyStr, err := getOptionalStringAttribute("body", block, context)
if err != nil {
return err
}
if bodyStr != nil {
n.NewBody = *bodyStr
}
n.newWriteBlock = hclwrite.NewBlock(n.NewBlockType, n.Labels)
decodeByNestedBlock := false
for _, b := range block.NestedBlocks() {
if b.Type == "asraw" {
decodeByNestedBlock = true
if err := decodeAsRawBlock(n.newWriteBlock, b); err != nil {
return err
}
continue
}
if b.Type == "asstring" {
decodeByNestedBlock = true
if err = decodeAsStringBlock(n.newWriteBlock, b, 0, context); err != nil {
return err
}
continue
}
}
if decodeByNestedBlock && n.NewBody != "" {
return fmt.Errorf("can only set either one of `asraw`, `asstring` or `body`")
}
if n.NewBody != "" {
newBody, diag := hclwrite.ParseConfig([]byte(fmt.Sprintf(`%s %s {
%s
}`, n.NewBlockType, strings.Join(n.Labels, " "), n.NewBody)), "", hcl.InitialPos)
if diag.HasErrors() {
return fmt.Errorf("cannot decode body %s: %+v", n.NewBody, diag)
}
n.newWriteBlock = newBody.Body().Blocks()[0]
}
formattedBlock, err := n.Format(n.newWriteBlock)
if err == nil {
n.newWriteBlock = formattedBlock
}
return nil
}
func (n *NewBlockTransform) Type() string {
return "new_block"
}
func (n *NewBlockTransform) Apply() error {
n.Config().(*MetaProgrammingTFConfig).AddBlock(n.FileName, n.newWriteBlock)
return nil
}
func (n *NewBlockTransform) NewWriteBlock() *hclwrite.Block {
return n.newWriteBlock
}
func (n *NewBlockTransform) Format(block *hclwrite.Block) (*hclwrite.Block, error) {
if block.Type() != "resource" && block.Type() != "data" && block.Type() != "variable" {
return block, nil
}
bytes := block.BuildTokens(nil).Bytes()
syntaxFile, diag := hclsyntax.ParseConfig(bytes, "dummy.hcl", hcl.InitialPos)
if diag.HasErrors() {
return nil, diag
}
syntaxBlock := syntaxFile.Body.(*hclsyntax.Body).Blocks[0]
avmBlock := avmfix.NewHclBlock(syntaxBlock, block)
if block.Type() == "resource" || block.Type() == "data" {
resourceBlock := avmfix.BuildBlockWithSchema(avmBlock, &hcl.File{})
err := resourceBlock.AutoFix()
return resourceBlock.HclBlock.WriteBlock, err
}
if block.Type() == "variable" {
variableBlock := avmfix.BuildVariableBlock(&hcl.File{}, avmBlock)
err := variableBlock.AutoFix()
return variableBlock.Block.WriteBlock, err
}
return nil, nil
}
func getRequiredStringAttribute(name string, block *golden.HclBlock, context *hcl.EvalContext) (string, error) {
attr, ok := block.Attributes()[name]
if !ok {
return "", fmt.Errorf("`%s` is required", name)
}
v, err := attr.Value(context)
if err != nil {
return "", err
}
if v.Type() != cty.String {
return "", fmt.Errorf("`%s` must be a string", name)
}
return v.AsString(), nil
}
func getOptionalStringAttribute(name string, block *golden.HclBlock, context *hcl.EvalContext) (*string, error) {
attr, ok := block.Attributes()[name]
if !ok {
return nil, nil
}
v, err := attr.Value(context)
if err != nil {
return nil, err
}
if v.Type() != cty.String {
return nil, fmt.Errorf("`%s` must be a string", name)
}
asString := v.AsString()
return &asString, nil
}