Skip to content

Commit

Permalink
support moved block in migration flow (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
ms-henglu authored Nov 13, 2024
1 parent 3cd01a1 commit 4681ae5
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
FEATURES:
- Support `-var-file` option to specify the path to the terraform variable file.
- Support migrating resources from `azurerm` provider to `azapi` provider.
- When migrating resources from `azurerm` provider to `azapi` provider, it will generate `moved` block to modify the terraform state.

## v2.0.0-beta

Expand Down
8 changes: 5 additions & 3 deletions cmd/migrate_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ func (c *MigrateCommand) MigrateResources(terraform *tf.Terraform, resources []t
for _, r := range resources {
if r.IsMigrated() {
log.Printf("[INFO] removing %s from config", r.OldAddress(nil))
importBlock := r.ImportBlock()
removedBlock := r.RemovedBlock()
if err := types.ReplaceResourceBlock(workingDirectory, r.OldAddress(nil), []*hclwrite.Block{removedBlock, importBlock, r.MigratedBlock()}); err != nil {
stateUpdateBlocks := r.StateUpdateBlocks()
newBlocks := make([]*hclwrite.Block, 0)
newBlocks = append(newBlocks, stateUpdateBlocks...)
newBlocks = append(newBlocks, r.MigratedBlock())
if err := types.ReplaceResourceBlock(workingDirectory, r.OldAddress(nil), newBlocks); err != nil {
log.Printf("[ERROR] error removing %s from state: %+v", r.OldAddress(nil), err)
}
}
Expand Down
11 changes: 9 additions & 2 deletions types/azapi_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ type AzapiResource struct {
Migrated bool
}

func (r *AzapiResource) StateUpdateBlocks() []*hclwrite.Block {
blocks := make([]*hclwrite.Block, 0)
blocks = append(blocks, r.removedBlock())
blocks = append(blocks, r.importBlock())
return blocks
}

func (r *AzapiResource) Outputs() []Output {
res := make([]Output, 0)
for _, instance := range r.Instances {
Expand All @@ -49,7 +56,7 @@ func (r *AzapiResource) IsMigrated() bool {
return r.Migrated
}

func (r *AzapiResource) ImportBlock() *hclwrite.Block {
func (r *AzapiResource) importBlock() *hclwrite.Block {
importBlock := hclwrite.NewBlock("import", nil)
if r.IsMultipleResources() {
forEachMap := make(map[string]cty.Value)
Expand All @@ -72,7 +79,7 @@ func (r *AzapiResource) ImportBlock() *hclwrite.Block {
return importBlock
}

func (r *AzapiResource) RemovedBlock() *hclwrite.Block {
func (r *AzapiResource) removedBlock() *hclwrite.Block {
removedBlock := hclwrite.NewBlock("removed", nil)
removedBlock.Body().SetAttributeTraversal("from", hcl.Traversal{hcl.TraverseRoot{Name: "azapi_resource"}, hcl.TraverseAttr{Name: r.Label}})
removedLifecycleBlock := hclwrite.NewBlock("lifecycle", nil)
Expand Down
12 changes: 7 additions & 5 deletions types/azapi_update_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ type AzapiUpdateResource struct {
OutputProperties []string
}

func (r *AzapiUpdateResource) StateUpdateBlocks() []*hclwrite.Block {
blocks := make([]*hclwrite.Block, 0)
blocks = append(blocks, r.removedBlock())
return blocks
}

func (r *AzapiUpdateResource) Outputs() []Output {
res := make([]Output, 0)
res = append(res, r.outputs...)
Expand Down Expand Up @@ -63,11 +69,7 @@ func (r *AzapiUpdateResource) GenerateNewConfig(terraform *tf.Terraform) error {
return nil
}

func (r *AzapiUpdateResource) ImportBlock() *hclwrite.Block {
return nil
}

func (r *AzapiUpdateResource) RemovedBlock() *hclwrite.Block {
func (r *AzapiUpdateResource) removedBlock() *hclwrite.Block {
removedBlock := hclwrite.NewBlock("removed", nil)
removedBlock.Body().SetAttributeTraversal("from", hcl.Traversal{hcl.TraverseRoot{Name: "azapi_update_resource"}, hcl.TraverseAttr{Name: r.OldLabel}})
removedLifecycleBlock := hclwrite.NewBlock("lifecycle", nil)
Expand Down
3 changes: 1 addition & 2 deletions types/azure_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ type AzureResource interface {
GenerateNewConfig(terraform *tf.Terraform) error
EmptyImportConfig() string

ImportBlock() *hclwrite.Block
RemovedBlock() *hclwrite.Block
StateUpdateBlocks() []*hclwrite.Block
MigratedBlock() *hclwrite.Block

IsMigrated() bool
Expand Down
75 changes: 41 additions & 34 deletions types/azurerm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"log"
"net/url"
"strconv"
"strings"

"github.com/Azure/aztfmigrate/helper"
Expand All @@ -14,7 +13,6 @@ import (
_ "github.com/gertd/go-pluralize"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/zclconf/go-cty/cty"
)

var _ AzureResource = &AzurermResource{}
Expand All @@ -30,6 +28,15 @@ type AzurermResource struct {
Migrated bool
}

func (r *AzurermResource) StateUpdateBlocks() []*hclwrite.Block {
movedBlock := hclwrite.NewBlock("moved", nil)
movedBlock.Body().SetAttributeTraversal("from", hcl.Traversal{hcl.TraverseRoot{Name: r.OldResourceType}, hcl.TraverseAttr{Name: r.OldLabel}})
movedBlock.Body().SetAttributeTraversal("to", hcl.Traversal{hcl.TraverseRoot{Name: r.NewResourceType}, hcl.TraverseAttr{Name: r.NewLabel}})
blocks := make([]*hclwrite.Block, 0)
blocks = append(blocks, movedBlock)
return blocks
}

func (r *AzurermResource) Outputs() []Output {
res := make([]Output, 0)
for _, instance := range r.Instances {
Expand Down Expand Up @@ -109,42 +116,12 @@ func (r *AzurermResource) GenerateNewConfig(terraform *tf.Terraform) error {
}
r.Block = InjectReference(r.Block, r.References)
}

r.Block = sortAttributes(r.Block)
r.Migrated = true
return nil
}

func (r *AzurermResource) ImportBlock() *hclwrite.Block {
importBlock := hclwrite.NewBlock("import", nil)
if r.IsMultipleResources() {
forEachMap := make(map[string]cty.Value)
for _, instance := range r.Instances {
switch v := instance.Index.(type) {
case string:
forEachMap[instance.ResourceId] = cty.StringVal(v)
default:
value, _ := strconv.ParseInt(fmt.Sprintf("%v", v), 10, 64)
forEachMap[instance.ResourceId] = cty.NumberIntVal(value)
}
}
importBlock.Body().SetAttributeValue("for_each", cty.MapVal(forEachMap))
importBlock.Body().SetAttributeTraversal("id", hcl.Traversal{hcl.TraverseRoot{Name: "each"}, hcl.TraverseAttr{Name: "key"}})
importBlock.Body().SetAttributeTraversal("to", hcl.Traversal{hcl.TraverseRoot{Name: r.NewResourceType}, hcl.TraverseAttr{Name: fmt.Sprintf("%s[each.value]", r.NewLabel)}})
} else {
importBlock.Body().SetAttributeValue("id", cty.StringVal(r.Instances[0].ResourceId))
importBlock.Body().SetAttributeTraversal("to", hcl.Traversal{hcl.TraverseRoot{Name: r.NewResourceType}, hcl.TraverseAttr{Name: r.NewLabel}})
}
return importBlock
}

func (r *AzurermResource) RemovedBlock() *hclwrite.Block {
removedBlock := hclwrite.NewBlock("removed", nil)
removedBlock.Body().SetAttributeTraversal("from", hcl.Traversal{hcl.TraverseRoot{Name: r.OldResourceType}, hcl.TraverseAttr{Name: r.OldLabel}})
removedLifecycleBlock := hclwrite.NewBlock("lifecycle", nil)
removedLifecycleBlock.Body().SetAttributeValue("destroy", cty.BoolVal(false))
removedBlock.Body().AppendBlock(removedLifecycleBlock)
return removedBlock
}

func (r *AzurermResource) TargetProvider() string {
return "azapi"
}
Expand Down Expand Up @@ -270,3 +247,33 @@ func ResourceTypeOfResourceId(input string) string {
}
return resourceType
}

func sortAttributes(input *hclwrite.Block) *hclwrite.Block {
output := hclwrite.NewBlock(input.Type(), input.Labels())
attrList := []string{"count", "for_each", "type", "parent_id", "name", "location", "identity", "body", "tags"}
usedAttr := make(map[string]bool)
for _, attr := range attrList {
if attribute := input.Body().GetAttribute(attr); attribute != nil {
output.Body().SetAttributeRaw(attr, attribute.Expr().BuildTokens(nil))
usedAttr[attr] = true
} else {
for _, block := range input.Body().Blocks() {
if block.Type() == attr {
output.Body().AppendBlock(block)
usedAttr[attr] = true
}
}
}
}
for attrName, attribute := range input.Body().Attributes() {
if _, ok := usedAttr[attrName]; !ok {
output.Body().SetAttributeRaw(attrName, attribute.Expr().BuildTokens(nil))
}
}
for _, block := range input.Body().Blocks() {
if _, ok := usedAttr[block.Type()]; !ok {
output.Body().AppendBlock(block)
}
}
return output
}
2 changes: 1 addition & 1 deletion types/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func ReplaceGenericOutputs(workingDirectory string, outputs []Output) error {
continue
}
for _, block := range f.Body().Blocks() {
if block.Type() == "removed" || block.Type() == "import" {
if block.Type() == "removed" || block.Type() == "import" || block.Type() == "moved" {
continue
}
if block != nil {
Expand Down

0 comments on commit 4681ae5

Please sign in to comment.