From 4681ae5d18c08f1c1bc053964aaee9291179732c Mon Sep 17 00:00:00 2001 From: Heng Lu <79895375+ms-henglu@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:23:52 +0800 Subject: [PATCH] support `moved` block in migration flow (#138) --- CHANGELOG.md | 1 + cmd/migrate_command.go | 8 ++-- types/azapi_resource.go | 11 ++++- types/azapi_update_resource.go | 12 +++--- types/azure_resource.go | 3 +- types/azurerm_resource.go | 75 +++++++++++++++++++--------------- types/hcl.go | 2 +- 7 files changed, 65 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f5c979..94c1bf93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/cmd/migrate_command.go b/cmd/migrate_command.go index 6361b6ae..c1439789 100644 --- a/cmd/migrate_command.go +++ b/cmd/migrate_command.go @@ -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) } } diff --git a/types/azapi_resource.go b/types/azapi_resource.go index 68acd1b6..af5f1442 100644 --- a/types/azapi_resource.go +++ b/types/azapi_resource.go @@ -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 { @@ -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) @@ -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) diff --git a/types/azapi_update_resource.go b/types/azapi_update_resource.go index 912b6f86..25315719 100644 --- a/types/azapi_update_resource.go +++ b/types/azapi_update_resource.go @@ -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...) @@ -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) diff --git a/types/azure_resource.go b/types/azure_resource.go index c518c0a2..450c9174 100644 --- a/types/azure_resource.go +++ b/types/azure_resource.go @@ -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 diff --git a/types/azurerm_resource.go b/types/azurerm_resource.go index 35874b6d..07e50f8e 100644 --- a/types/azurerm_resource.go +++ b/types/azurerm_resource.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "net/url" - "strconv" "strings" "github.com/Azure/aztfmigrate/helper" @@ -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{} @@ -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 { @@ -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" } @@ -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 +} diff --git a/types/hcl.go b/types/hcl.go index f92b104b..bd65ade5 100644 --- a/types/hcl.go +++ b/types/hcl.go @@ -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 {