pkg/transform_update_in_place.go (138 lines of code) (raw):

package pkg import ( "encoding/json" "fmt" "github.com/Azure/golden" "github.com/Azure/mapotf/pkg/terraform" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" ) var _ Transform = &UpdateInPlaceTransform{} var _ golden.CustomDecode = &UpdateInPlaceTransform{} type UpdateInPlaceTransform struct { *golden.BaseBlock *BaseTransform TargetBlockAddress string `hcl:"target_block_address" validate:"required"` DynamicBlockBody string `hcl:"dynamic_block_body,optional"` updateBlock *hclwrite.Block targetBlock *terraform.RootBlock } func (u *UpdateInPlaceTransform) Type() string { return "update_in_place" } func (u *UpdateInPlaceTransform) Apply() error { u.PatchWriteBlock(u.targetBlock, u.updateBlock) return nil } func (u *UpdateInPlaceTransform) Decode(block *golden.HclBlock, context *hcl.EvalContext) error { var err error u.TargetBlockAddress, err = getRequiredStringAttribute("target_block_address", block, context) if err != nil { return err } cfg := u.Config().(*MetaProgrammingTFConfig) b := cfg.RootBlock(u.TargetBlockAddress) if b == nil { return fmt.Errorf("cannot find block: %s", u.TargetBlockAddress) } u.targetBlock = b u.updateBlock = hclwrite.NewBlock("patch", []string{}) dynamicBlockBody, err := getOptionalStringAttribute("dynamic_block_body", block, context) if err != nil { return err } if dynamicBlockBody != nil { u.DynamicBlockBody = *dynamicBlockBody patch, diag := hclwrite.ParseConfig([]byte(fmt.Sprintf("patch {\n%s\n}", u.DynamicBlockBody)), u.Address(), hcl.InitialPos) if diag.HasErrors() { return fmt.Errorf("error while parsing patch body: %s", diag.Error()) } if err = decodeAsDynamicBlockBody(u.updateBlock, patch.Body().Blocks()[0]); err != nil { return err } } for _, b := range block.NestedBlocks() { switch b.Type { case "asraw": { if err = decodeAsRawBlock(u.updateBlock, b); err != nil { return err } continue } case "asstring": { if err = decodeAsStringBlock(u.updateBlock, b, 0, context); err != nil { return err } continue } } } return nil } func decodeAsDynamicBlockBody(dest *hclwrite.Block, patch *hclwrite.Block) error { for n, attribute := range patch.Body().Attributes() { dest.Body().SetAttributeRaw(n, attribute.Expr().BuildTokens(nil)) } for _, b := range patch.Body().Blocks() { blockType := b.Type() newNestedBlock := dest.Body().AppendNewBlock(blockType, b.Labels()) if err := decodeAsDynamicBlockBody(newNestedBlock, b); err != nil { return err } } return nil } func (u *UpdateInPlaceTransform) UpdateBlock() *hclwrite.Block { return u.updateBlock } func (u *UpdateInPlaceTransform) PatchWriteBlock(dest terraform.Block, patch *hclwrite.Block) { // we cannot patch one-line block if dest.Range().Start.Line == dest.Range().End.Line { dest.WriteBody().AppendNewline() } for name, attr := range patch.Body().Attributes() { dest.SetAttributeRaw(name, attr.Expr().BuildTokens(nil)) } // Handle nested blocks for _, patchNestedBlock := range patch.Body().Blocks() { destNestedBlocks := dest.GetNestedBlocks()[patchNestedBlock.Type()] if len(destNestedBlocks) == 0 { // If the nested block does not exist in dest, add it dest.AppendBlock(patchNestedBlock) } else { for _, nb := range destNestedBlocks { u.PatchWriteBlock(nb, patchNestedBlock) } } } } func (u *UpdateInPlaceTransform) String() string { content := make(map[string]any) content["id"] = u.Id() content["target_block_address"] = u.TargetBlockAddress content["patch"] = string(u.updateBlock.BuildTokens(nil).Bytes()) str, err := json.Marshal(content) if err != nil { panic(err.Error()) } return string(str) } // Copy from https://github.com/hashicorp/hcl/blob/v2.20.1/hclwrite/parser.go#L478-L517 func writerTokens(nativeTokens hclsyntax.Tokens) hclwrite.Tokens { tokBuf := make([]hclwrite.Token, len(nativeTokens)) var lastByteOffset int for i, mainToken := range nativeTokens { bytes := make([]byte, len(mainToken.Bytes)) copy(bytes, mainToken.Bytes) tokBuf[i] = hclwrite.Token{ Type: mainToken.Type, Bytes: bytes, SpacesBefore: mainToken.Range.Start.Byte - lastByteOffset, } lastByteOffset = mainToken.Range.End.Byte } ret := make(hclwrite.Tokens, len(tokBuf)) for i := range ret { ret[i] = &tokBuf[i] } return ret }