diff --git a/CHANGELOG.md b/CHANGELOG.md index 79746df7..07324073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ENHANCEMENTS: - `azapi` resources/data sources: Update embedded schema to the latest version. +- Support specifying the provider version used in the migration in the `terraform` block. ## v2.3.0 diff --git a/go.mod b/go.mod index b32cf3de..3dda74e4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Azure/azapi-lsp go 1.23.0 require ( - github.com/Azure/aztfmigrate v1.15.1-0.20250407062114-3a221613fd4c + github.com/Azure/aztfmigrate v1.15.1-0.20250408084920-0db194f765f0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/apparentlymart/go-textseg v1.0.0 github.com/creachadair/jrpc2 v0.32.0 diff --git a/go.sum b/go.sum index fc35b51a..0c1d3176 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/Azure/aztfmigrate v1.15.1-0.20250407062114-3a221613fd4c h1:6OOBvqvbIpKrK+aNSjSQvddNgIciZ4GVIqibRD1OOHI= -github.com/Azure/aztfmigrate v1.15.1-0.20250407062114-3a221613fd4c/go.mod h1:P199MOJpYH3vD3BsO28KEjRDVeKuhCPwFXNkvOpjrWw= +github.com/Azure/aztfmigrate v1.15.1-0.20250408084920-0db194f765f0 h1:6Dzz58+EP8kCuwVZfpTKGF8NsLPIzZA1aIPYk2NNZTo= +github.com/Azure/aztfmigrate v1.15.1-0.20250408084920-0db194f765f0/go.mod h1:P199MOJpYH3vD3BsO28KEjRDVeKuhCPwFXNkvOpjrWw= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= diff --git a/internal/langserver/handlers/command/aztfmigrate_command.go b/internal/langserver/handlers/command/aztfmigrate_command.go index 8de48706..c517a95a 100644 --- a/internal/langserver/handlers/command/aztfmigrate_command.go +++ b/internal/langserver/handlers/command/aztfmigrate_command.go @@ -18,6 +18,8 @@ import ( lsp "github.com/Azure/azapi-lsp/internal/protocol" "github.com/Azure/azapi-lsp/internal/utils" "github.com/Azure/aztfmigrate/azurerm" + "github.com/Azure/aztfmigrate/cmd" + "github.com/Azure/aztfmigrate/helper" "github.com/Azure/aztfmigrate/tf" "github.com/Azure/aztfmigrate/types" "github.com/hashicorp/hcl/v2" @@ -236,7 +238,7 @@ func (c AztfMigrateCommand) Handle(ctx context.Context, arguments []json.RawMess return nil, err } - if err = os.WriteFile(filepath.Join(tempDir, importFileName), []byte(importConfig(resources)), 0600); err != nil { + if err = os.WriteFile(filepath.Join(tempDir, importFileName), []byte(cmd.ImportConfig(resources, helper.FindHclBlock(workingDirectory, "terraform", nil))), 0600); err != nil { return nil, err } @@ -247,7 +249,7 @@ func (c AztfMigrateCommand) Handle(ctx context.Context, arguments []json.RawMess log.Printf("[ERROR] %+v", err) _ = clientNotifier.Notify(ctx, "window/showMessage", lsp.ShowMessageParams{ Type: lsp.Error, - Message: "Failed to generate new config for " + r.OldAddress(nil), + Message: fmt.Sprintf("Failed to generate new config for %s: %v", r.OldAddress(nil), err), }) } } @@ -345,53 +347,6 @@ func (c AztfMigrateCommand) Handle(ctx context.Context, arguments []json.RawMess return nil, nil } -func importConfig(resources []types.AzureResource) string { - const providerConfig = ` -terraform { - required_providers { - azapi = { - source = "Azure/azapi" - } - } -} - -provider "azurerm" { - features {} - subscription_id = "%s" -} - -provider "azapi" { -} -` - - config := "" - for _, r := range resources { - config += r.EmptyImportConfig() - } - subscriptionId := "" - for _, r := range resources { - switch resource := r.(type) { - case *types.AzapiResource: - for _, instance := range resource.Instances { - if strings.HasPrefix(instance.ResourceId, "/subscriptions/") { - subscriptionId = strings.Split(instance.ResourceId, "/")[2] - break - } - } - case *types.AzapiUpdateResource: - if strings.HasPrefix(resource.Id, "/subscriptions/") { - subscriptionId = strings.Split(resource.Id, "/")[2] - } - } - if subscriptionId != "" { - break - } - } - config = fmt.Sprintf(providerConfig, subscriptionId) + config - - return config -} - func reportProgress(ctx context.Context, message string, percentage uint32) { clientCaller, err := context2.ClientCaller(ctx) if err != nil { diff --git a/vendor/github.com/Azure/aztfmigrate/cmd/commands.go b/vendor/github.com/Azure/aztfmigrate/cmd/commands.go new file mode 100644 index 00000000..7124811c --- /dev/null +++ b/vendor/github.com/Azure/aztfmigrate/cmd/commands.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "flag" + "io" + "strings" +) + +func defaultFlagSet(cmdName string) *flag.FlagSet { + f := flag.NewFlagSet(cmdName, flag.ContinueOnError) + f.SetOutput(io.Discard) + + // Set the default Usage to empty + f.Usage = func() {} + + return f +} + +func helpForFlags(fs *flag.FlagSet) string { + buf := &strings.Builder{} + buf.WriteString("Options:\n\n") + + w := fs.Output() + defer fs.SetOutput(w) + fs.SetOutput(buf) + fs.PrintDefaults() + + return buf.String() +} diff --git a/vendor/github.com/Azure/aztfmigrate/cmd/migrate_command.go b/vendor/github.com/Azure/aztfmigrate/cmd/migrate_command.go new file mode 100644 index 00000000..57106649 --- /dev/null +++ b/vendor/github.com/Azure/aztfmigrate/cmd/migrate_command.go @@ -0,0 +1,242 @@ +package cmd + +import ( + "flag" + "fmt" + "log" + "os" + "path" + "path/filepath" + "strings" + + "github.com/Azure/aztfmigrate/helper" + "github.com/Azure/aztfmigrate/tf" + "github.com/Azure/aztfmigrate/types" + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/mitchellh/cli" +) + +const filenameImport = "imports.tf" + +const tempFolderName = "aztfmigrate_temp" + +type MigrateCommand struct { + Ui cli.Ui + Verbose bool + Strict bool + workingDir string + varFile string + TargetProvider string +} + +func (c *MigrateCommand) flags() *flag.FlagSet { + fs := defaultFlagSet("plan") + fs.BoolVar(&c.Verbose, "v", false, "whether show terraform logs") + fs.BoolVar(&c.Strict, "strict", false, "strict mode: API versions must be matched") + fs.StringVar(&c.workingDir, "working-dir", "", "path to Terraform configuration files") + fs.StringVar(&c.varFile, "var-file", "", "path to the terraform variable file") + fs.StringVar(&c.TargetProvider, "to", "", "Specify the provider to migrate to. The allowed values are: azurerm and azapi. Default is azurerm.") + + fs.Usage = func() { c.Ui.Error(c.Help()) } + return fs +} + +func (c *MigrateCommand) Run(args []string) int { + // AzureRM provider will honor env.var "AZURE_HTTP_USER_AGENT" when constructing for HTTP "User-Agent" header. + // #nosec G104 + _ = os.Setenv("AZURE_HTTP_USER_AGENT", "mig") + // The following env.vars are used to disable enhanced validation and skip provider registration, to speed up the process. + // #nosec G104 + _ = os.Setenv("ARM_PROVIDER_ENHANCED_VALIDATION", "false") + // #nosec G104 + _ = os.Setenv("ARM_SKIP_PROVIDER_REGISTRATION", "true") + f := c.flags() + if err := f.Parse(args); err != nil { + c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s", err)) + return 1 + } + + if c.TargetProvider == "" { + c.TargetProvider = "azurerm" + } + if c.TargetProvider != "azapi" && c.TargetProvider != "azurerm" { + c.Ui.Error("Invalid target provider. The allowed values are: azurerm and azapi.") + return 1 + } + + if c.workingDir == "" { + c.workingDir, _ = os.Getwd() + } + log.Printf("[INFO] working directory: %s", c.workingDir) + + log.Printf("[INFO] initializing terraform...") + terraform, err := tf.NewTerraform(c.workingDir, c.Verbose) + if err != nil { + log.Fatal(err) + } + + planCommand := &PlanCommand{ //nolint + Ui: c.Ui, + Verbose: c.Verbose, + Strict: c.Strict, + workingDir: c.workingDir, + varFile: c.varFile, + TargetProvider: c.TargetProvider, + } + allResources := planCommand.Plan(terraform, false) + c.MigrateResources(terraform, allResources) + return 0 +} + +func (c *MigrateCommand) Help() string { + helpText := ` +Usage: aztfmigrate migrate +` + c.Synopsis() + "\nThe Terraform addresses listed in file `aztfmigrate.ignore` will be ignored during migration.\n\n" + helpForFlags(c.flags()) + + return strings.TrimSpace(helpText) +} + +func (c *MigrateCommand) Synopsis() string { + return "Migrate azapi resources to azurerm resources in current working directory" +} + +func (c *MigrateCommand) MigrateResources(terraform *tf.Terraform, resources []types.AzureResource) { + if len(resources) == 0 { + return + } + + workingDirectory := terraform.GetWorkingDirectory() + // write empty config to temp dir for import + tempDir := filepath.Join(workingDirectory, tempFolderName) + if err := os.MkdirAll(tempDir, 0750); err != nil { + log.Fatalf("creating temp workspace %q: %+v", tempDir, err) + } + if err := os.RemoveAll(path.Join(tempDir, "terraform.tfstate")); err != nil { + log.Printf("[WARN] removing temp workspace %q: %+v", tempDir, err) + } + defer func() { + err := os.RemoveAll(path.Join(tempDir, "terraform.tfstate")) + if err != nil { + log.Printf("[ERROR] removing temp workspace %q: %+v", tempDir, err) + } + }() + tempTerraform, err := tf.NewTerraform(tempDir, c.Verbose) + if err != nil { + log.Fatal(err) + } + + log.Printf("[INFO] generating import config...") + config := ImportConfig(resources, helper.FindHclBlock(workingDirectory, "terraform", nil)) + if err = os.WriteFile(filepath.Join(tempDir, filenameImport), []byte(config), 0600); err != nil { + log.Fatal(err) + } + + log.Printf("[INFO] migrating resources...") + for _, r := range resources { + log.Printf("[INFO] generating new config for resource %s...", r.OldAddress(nil)) + if err := r.GenerateNewConfig(tempTerraform); err != nil { + log.Printf("[ERROR] %+v", err) + } + } + + log.Printf("[INFO] updating config...") + updateResources := make([]types.AzapiUpdateResource, 0) + for _, r := range resources { + if updateResource, ok := r.(*types.AzapiUpdateResource); ok { + updateResources = append(updateResources, *updateResource) + } + } + if err := types.UpdateMigratedResourceBlock(workingDirectory, updateResources); err != nil { + log.Fatal(err) + } + + // migrate depends_on, lifecycle, provisioner + for _, r := range resources { + if existingBlock, err := types.GetResourceBlock(workingDirectory, r.OldAddress(nil)); err == nil && existingBlock != nil { + migratedBlock := r.MigratedBlock() + if attr := existingBlock.Body().GetAttribute("depends_on"); attr != nil { + migratedBlock.Body().SetAttributeRaw("depends_on", attr.Expr().BuildTokens(nil)) + } + for _, block := range existingBlock.Body().Blocks() { + if block.Type() == "lifecycle" || block.Type() == "provisioner" { + migratedBlock.Body().AppendBlock(block) + } + } + } + } + + // remove from config + for _, r := range resources { + if r.IsMigrated() { + log.Printf("[INFO] removing %s from config", r.OldAddress(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) + } + } + } + + log.Printf("[INFO] replacing references with migrated resource...") + outputs := make([]types.Output, 0) + for _, r := range resources { + if r.IsMigrated() { + outputs = append(outputs, r.Outputs()...) + } + } + if err := types.ReplaceGenericOutputs(workingDirectory, outputs); err != nil { + log.Printf("[ERROR] replacing outputs: %+v", err) + } +} + +func ImportConfig(resources []types.AzureResource, terraformBlock *hclwrite.Block) string { + config := `terraform { + required_providers { + azapi = { + source = "Azure/azapi" + } + } +}` + if terraformBlock != nil { + newFile := hclwrite.NewEmptyFile() + newFile.Body().AppendBlock(terraformBlock) + config = string(hclwrite.Format(newFile.Bytes())) + } + + for _, r := range resources { + config += r.EmptyImportConfig() + } + subscriptionId := "" + for _, r := range resources { + switch resource := r.(type) { + case *types.AzapiResource: + for _, instance := range resource.Instances { + if strings.HasPrefix(instance.ResourceId, "/subscriptions/") { + subscriptionId = strings.Split(instance.ResourceId, "/")[2] + break + } + } + case *types.AzapiUpdateResource: + if strings.HasPrefix(resource.Id, "/subscriptions/") { + subscriptionId = strings.Split(resource.Id, "/")[2] + } + } + if subscriptionId != "" { + break + } + } + + const providerConfig = ` +provider "azurerm" { + features {} + subscription_id = "%s" +} + +provider "azapi" { +} +` + + return fmt.Sprintf(providerConfig, subscriptionId) + config +} diff --git a/vendor/github.com/Azure/aztfmigrate/cmd/plan_command.go b/vendor/github.com/Azure/aztfmigrate/cmd/plan_command.go new file mode 100644 index 00000000..06897269 --- /dev/null +++ b/vendor/github.com/Azure/aztfmigrate/cmd/plan_command.go @@ -0,0 +1,187 @@ +package cmd + +import ( + "bufio" + "flag" + "fmt" + "log" + "os" + "path" + "strings" + + "github.com/Azure/aztfmigrate/azurerm" + "github.com/Azure/aztfmigrate/tf" + "github.com/Azure/aztfmigrate/types" + "github.com/mitchellh/cli" +) + +type PlanCommand struct { + Ui cli.Ui + Verbose bool + Strict bool + workingDir string + varFile string + TargetProvider string +} + +func (c *PlanCommand) flags() *flag.FlagSet { + fs := defaultFlagSet("plan") + fs.BoolVar(&c.Verbose, "v", false, "whether show terraform logs") + fs.BoolVar(&c.Strict, "strict", false, "strict mode: API versions must be matched") + fs.StringVar(&c.workingDir, "working-dir", "", "path to Terraform configuration files") + fs.StringVar(&c.varFile, "var-file", "", "path to the terraform variable file") + fs.StringVar(&c.TargetProvider, "to", "", "Specify the provider to migrate to. The allowed values are: azurerm and azapi. Default is azurerm.") + fs.Usage = func() { c.Ui.Error(c.Help()) } + return fs +} + +func (c *PlanCommand) Run(args []string) int { + // AzureRM provider will honor env.var "AZURE_HTTP_USER_AGENT" when constructing for HTTP "User-Agent" header. + // #nosec G104 + _ = os.Setenv("AZURE_HTTP_USER_AGENT", "mig") + f := c.flags() + if err := f.Parse(args); err != nil { + c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s", err)) + return 1 + } + if c.TargetProvider == "" { + c.TargetProvider = "azurerm" + } + if c.TargetProvider != "azapi" && c.TargetProvider != "azurerm" { + c.Ui.Error("Invalid target provider. The allowed values are: azurerm and azapi.") + return 1 + } + log.Printf("[INFO] target provider: %s", c.TargetProvider) + + log.Printf("[INFO] initializing terraform...") + if c.workingDir == "" { + c.workingDir, _ = os.Getwd() + } + terraform, err := tf.NewTerraform(c.workingDir, c.Verbose) + if err != nil { + log.Fatal(err) + } + c.Plan(terraform, true) + return 0 +} + +func (c *PlanCommand) Help() string { + helpText := ` +Usage: aztfmigrate plan +` + c.Synopsis() + "\nThe Terraform addresses listed in file `aztfmigrate.ignore` will be ignored during migration.\n\n" + helpForFlags(c.flags()) + + return strings.TrimSpace(helpText) +} + +func (c *PlanCommand) Synopsis() string { + return "Show terraform resources which can be migrated to azurerm or azapi resources in current working directory" +} + +func (c *PlanCommand) Plan(terraform *tf.Terraform, isPlanOnly bool) []types.AzureResource { + // get azapi resource from state + log.Printf("[INFO] running terraform plan...") + p, err := terraform.Plan(&c.varFile) + if err != nil { + log.Fatal(err) + } + + migrationMessage := "The following resources will be migrated:\n" + unsupportedMessage := "The following resources can't be migrated:\n" + ignoreMessage := "The following resources will be ignored in migration:\n" + ignoreSet := make(map[string]bool) + if file, err := os.ReadFile(path.Join(terraform.GetWorkingDirectory(), "aztfmigrate.ignore")); err == nil { + lines := strings.Split(string(file), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + ignoreSet[line] = true + ignoreMessage += fmt.Sprintf("\t%s\n", line) + } + } + + res := make([]types.AzureResource, 0) + + for _, item := range types.ListResourcesFromPlan(p) { + if item.TargetProvider() != c.TargetProvider { + continue + } + if ignoreSet[item.OldAddress(nil)] { + continue + } + if err := item.CoverageCheck(c.Strict); err != nil { + unsupportedMessage += fmt.Sprintf("\t%s\n", err) + continue + } + + switch resource := item.(type) { + case *types.AzapiResource: + if len(resource.Instances) == 0 { + continue + } + resourceId := resource.Instances[0].ResourceId + resourceTypes, exact, err := azurerm.GetAzureRMResourceType(resourceId) + if err != nil { + log.Fatal(fmt.Errorf("failed to get resource type for %s: %w", resourceId, err)) + } + if exact { + resource.ResourceType = resourceTypes[0] + } else if !isPlanOnly { + resource.ResourceType = c.getUserInputResourceType(resourceId, resourceTypes) + } + + if resource.ResourceType != "" { + migrationMessage += fmt.Sprintf("\t%s will be replaced with %s\n", resource.OldAddress(nil), resource.NewAddress(nil)) + } else { + migrationMessage += fmt.Sprintf("\t%s will be replaced with %v\n", resource.OldAddress(nil), strings.Join(resourceTypes, ", ")) + } + res = append(res, resource) + + case *types.AzapiUpdateResource: + resourceTypes, exact, err := azurerm.GetAzureRMResourceType(resource.Id) + if err != nil { + log.Fatal(fmt.Errorf("failed to get resource type for %s: %w", resource.Id, err)) + } + + if exact { + resource.ResourceType = resourceTypes[0] + } else if !isPlanOnly { + resource.ResourceType = c.getUserInputResourceType(resource.Id, resourceTypes) + } + + if resource.ResourceType != "" { + migrationMessage += fmt.Sprintf("\t%s will be replaced with %s\n", resource.OldAddress(nil), resource.NewAddress(nil)) + } else { + migrationMessage += fmt.Sprintf("\t%s will be replaced with %v\n", resource.OldAddress(nil), strings.Join(resourceTypes, ", ")) + } + res = append(res, resource) + + case *types.AzurermResource: + if len(resource.Instances) == 0 { + continue + } + migrationMessage += fmt.Sprintf("\t%s will be replaced with %s\n", resource.OldAddress(nil), resource.NewAddress(nil)) + res = append(res, resource) + } + } + + log.Printf("[INFO]\n\nThe tool will perform the following actions:\n\n%s\n%s\n%s\n", migrationMessage, unsupportedMessage, ignoreMessage) + return res +} + +func (c *PlanCommand) getUserInputResourceType(resourceId string, values []string) string { + c.Ui.Warn(fmt.Sprintf("Couldn't find unique resource type for id: %s\nPossible values are [%s].\nPlease input an azurerm resource type:", resourceId, strings.Join(values, ", "))) + resourceType := "" + for { + reader := bufio.NewReader(os.Stdin) + resourceType, _ = reader.ReadString('\n') + resourceType = strings.Trim(resourceType, "\r\n") + for _, value := range values { + if value == resourceType { + return resourceType + } + } + c.Ui.Warn("Invalid input. Please input an azurerm resource type:") + } +} diff --git a/vendor/github.com/Azure/aztfmigrate/cmd/version_command.go b/vendor/github.com/Azure/aztfmigrate/cmd/version_command.go new file mode 100644 index 00000000..eb704735 --- /dev/null +++ b/vendor/github.com/Azure/aztfmigrate/cmd/version_command.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "encoding/json" + "flag" + "fmt" + "runtime" + "strings" + + "github.com/Azure/aztfmigrate/azurerm/schema" + "github.com/mitchellh/cli" +) + +type VersionOutput struct { + Version string `json:"version"` + + *BuildInfo +} + +type VersionCommand struct { + Ui cli.Ui + Version string + + jsonOutput bool +} + +type BuildInfo struct { + GoVersion string `json:"go,omitempty"` + GoOS string `json:"os,omitempty"` + GoArch string `json:"arch,omitempty"` + Compiler string `json:"compiler,omitempty"` +} + +func (c *VersionCommand) flags() *flag.FlagSet { + fs := defaultFlagSet("version") + + fs.BoolVar(&c.jsonOutput, "json", false, "output the version information as a JSON object") + + fs.Usage = func() { c.Ui.Error(c.Help()) } + + return fs +} + +func (c *VersionCommand) Run(args []string) int { + f := c.flags() + if err := f.Parse(args); err != nil { + c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s", err)) + return 1 + } + + output := VersionOutput{ + Version: c.Version, + BuildInfo: &BuildInfo{ + GoVersion: runtime.Version(), + GoOS: runtime.GOOS, + GoArch: runtime.GOARCH, + Compiler: runtime.Compiler, + }, + } + + if c.jsonOutput { + jsonOutput, err := json.MarshalIndent(output, "", " ") + if err != nil { + c.Ui.Error(fmt.Sprintf("\nError marshalling JSON: %s", err)) + return 1 + } + c.Ui.Output(string(jsonOutput)) + return 0 + } + + ver := fmt.Sprintf("%s\nazurerm: %s\nplatform: %s/%s\ngo: %s\ncompiler: %s", c.Version, schema.ProviderVersion, output.GoOS, output.GoArch, output.GoVersion, output.Compiler) + c.Ui.Output(ver) + return 0 +} + +func (c *VersionCommand) Help() string { + helpText := ` +Usage: aztfmigrate version [-json] +` + c.Synopsis() + "\n\n" + helpForFlags(c.flags()) + + return strings.TrimSpace(helpText) +} + +func (c *VersionCommand) Synopsis() string { + return "Displays the version of the migration tool" +} diff --git a/vendor/github.com/Azure/aztfmigrate/helper/utils.go b/vendor/github.com/Azure/aztfmigrate/helper/utils.go index a06f4b63..167889cc 100644 --- a/vendor/github.com/Azure/aztfmigrate/helper/utils.go +++ b/vendor/github.com/Azure/aztfmigrate/helper/utils.go @@ -5,6 +5,7 @@ import ( "fmt" "io/fs" "os" + "path" "strings" "github.com/hashicorp/hcl/v2" @@ -79,6 +80,48 @@ func ListHclFiles(workingDirectory string) []fs.DirEntry { return res } +func ListHclBlocks(workingDirectory string) []*hclwrite.Block { + res := make([]*hclwrite.Block, 0) + files := ListHclFiles(workingDirectory) + for _, file := range files { + filePath := path.Join(workingDirectory, file.Name()) + // #nosec G304 + f, err := os.ReadFile(filePath) + if err != nil { + continue + } + hclFile, diags := hclwrite.ParseConfig(f, file.Name(), hcl.InitialPos) + if diags.HasErrors() { + continue + } + res = append(res, hclFile.Body().Blocks()...) + } + return res +} + +func FindHclBlock(workingDirectory string, blockType string, labels []string) *hclwrite.Block { + blocks := ListHclBlocks(workingDirectory) + for _, block := range blocks { + if block.Type() != blockType { + continue + } + if len(block.Labels()) != len(labels) { + continue + } + isLabelsEqual := true + for i, label := range labels { + if block.Labels()[i] != label { + isLabelsEqual = false + break + } + } + if isLabelsEqual { + return block + } + } + return nil +} + // GetTokensForExpression convert a literal value to hclwrite.Tokens func GetTokensForExpression(expression string) hclwrite.Tokens { syntaxTokens, diags := hclsyntax.LexConfig([]byte(expression), "main.tf", hcl.InitialPos) diff --git a/vendor/github.com/Azure/aztfmigrate/tf/terraform.go b/vendor/github.com/Azure/aztfmigrate/tf/terraform.go index 290701d2..34cd0075 100644 --- a/vendor/github.com/Azure/aztfmigrate/tf/terraform.go +++ b/vendor/github.com/Azure/aztfmigrate/tf/terraform.go @@ -88,7 +88,7 @@ func (t *Terraform) ImportAdd(address string, id string) (string, error) { _ = t.Init() err := t.exec.Import(context.TODO(), address, id) if err != nil { - log.Fatal(err) + return "", fmt.Errorf("importing resource %s: %w", address, err) } outputs, err := tfadd.StateForTargets(context.TODO(), t.exec, []string{address}, tfadd.Full(true)) if err != nil { diff --git a/vendor/github.com/Azure/aztfmigrate/types/azapi_resource.go b/vendor/github.com/Azure/aztfmigrate/types/azapi_resource.go index af5f1442..74b77988 100644 --- a/vendor/github.com/Azure/aztfmigrate/types/azapi_resource.go +++ b/vendor/github.com/Azure/aztfmigrate/types/azapi_resource.go @@ -248,18 +248,18 @@ type Instance struct { func importAndGenerateConfig(terraform *tf.Terraform, address string, id string, resourceType string, skipTune bool) (*hclwrite.Block, error) { tpl, err := terraform.ImportAdd(address, id) if err != nil { - return nil, fmt.Errorf("[ERROR] error importing address: %s, id: %s: %+v", address, id, err) + return nil, err } f, diag := hclwrite.ParseConfig([]byte(tpl), "", hcl.InitialPos) if (diag != nil && diag.HasErrors()) || f == nil { - return nil, fmt.Errorf("[ERROR] parsing the HCL generated by \"terraform add\" of %s: %s", address, diag.Error()) + return nil, fmt.Errorf("parsing the HCL generated by \"terraform add\" of %s: %s", address, diag.Error()) } if !skipTune { rb := f.Body().Blocks()[0].Body() sch := schema.ProviderSchemaInfo.ResourceSchemas[resourceType] if err := azurerm.TuneHCLSchemaForResource(rb, sch); err != nil { - return nil, fmt.Errorf("[ERROR] tuning hcl config base on schema: %+v", err) + return nil, fmt.Errorf("tuning hcl config base on schema: %+v", err) } } diff --git a/vendor/github.com/Azure/aztfmigrate/types/azurerm_resource.go b/vendor/github.com/Azure/aztfmigrate/types/azurerm_resource.go index 480a66ae..cc07f0ef 100644 --- a/vendor/github.com/Azure/aztfmigrate/types/azurerm_resource.go +++ b/vendor/github.com/Azure/aztfmigrate/types/azurerm_resource.go @@ -61,17 +61,17 @@ func (r *AzurermResource) GenerateNewConfig(terraform *tf.Terraform) error { if !r.IsMultipleResources() { instance := r.Instances[0] log.Printf("[INFO] importing %s to %s and generating config...", instance.ResourceId, r.NewAddress(nil)) - if block, err := importAndGenerateConfig(terraform, r.NewAddress(nil), instance.ResourceId, "", true); err == nil { - r.Block = block - valuePropMap := GetValuePropMap(r.Block, r.NewAddress(nil)) - for i, output := range r.Instances[0].Outputs { - r.Instances[0].Outputs[i].NewName = valuePropMap[output.GetStringValue()] - } - r.Migrated = true - log.Printf("[INFO] resource %s has migrated to %s", r.OldAddress(nil), r.NewAddress(nil)) - } else { - log.Printf("[ERROR] %+v", err) + block, err := importAndGenerateConfig(terraform, r.NewAddress(nil), instance.ResourceId, "", true) + if err != nil { + return err + } + r.Block = block + valuePropMap := GetValuePropMap(r.Block, r.NewAddress(nil)) + for i, output := range r.Instances[0].Outputs { + r.Instances[0].Outputs[i].NewName = valuePropMap[output.GetStringValue()] } + r.Migrated = true + log.Printf("[INFO] resource %s has migrated to %s", r.OldAddress(nil), r.NewAddress(nil)) r.Block = InjectReference(r.Block, r.References) } else { // import and build combined block diff --git a/vendor/modules.txt b/vendor/modules.txt index ad29fd1a..9c08eda4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,11 +1,12 @@ # dario.cat/mergo v1.0.1 ## explicit; go 1.13 dario.cat/mergo -# github.com/Azure/aztfmigrate v1.15.1-0.20250407062114-3a221613fd4c +# github.com/Azure/aztfmigrate v1.15.1-0.20250408084920-0db194f765f0 ## explicit; go 1.23.0 github.com/Azure/aztfmigrate/azurerm github.com/Azure/aztfmigrate/azurerm/coverage github.com/Azure/aztfmigrate/azurerm/schema +github.com/Azure/aztfmigrate/cmd github.com/Azure/aztfmigrate/helper github.com/Azure/aztfmigrate/tf github.com/Azure/aztfmigrate/types