Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## v2.4.0 (unreleased)

ENHANCEMENTS:
- Support specifying the provider version used in the migration in the `terraform` block.

## v2.3.0
Target azurerm version: v4.20.0

Expand Down
38 changes: 21 additions & 17 deletions cmd/migrate_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"
"strings"

"github.com/Azure/aztfmigrate/helper"
"github.com/Azure/aztfmigrate/tf"
"github.com/Azure/aztfmigrate/types"
"github.com/hashicorp/hcl/v2/hclwrite"
Expand Down Expand Up @@ -125,7 +126,7 @@ func (c *MigrateCommand) MigrateResources(terraform *tf.Terraform, resources []t
}

log.Printf("[INFO] generating import config...")
config := importConfig(resources)
config := ImportConfig(resources, helper.FindHclBlock(workingDirectory, "terraform", nil))
if err = os.WriteFile(filepath.Join(tempDir, filenameImport), []byte(config), 0600); err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -190,26 +191,20 @@ func (c *MigrateCommand) MigrateResources(terraform *tf.Terraform, resources []t
}
}

func importConfig(resources []types.AzureResource) string {
const providerConfig = `
terraform {
func ImportConfig(resources []types.AzureResource, terraformBlock *hclwrite.Block) string {
config := `terraform {
required_providers {
azapi = {
source = "Azure/azapi"
}
}
}

provider "azurerm" {
features {}
subscription_id = "%s"
}

provider "azapi" {
}
`
}`
if terraformBlock != nil {
newFile := hclwrite.NewEmptyFile()
newFile.Body().AppendBlock(terraformBlock)
config = string(hclwrite.Format(newFile.Bytes()))
}

config := ""
for _, r := range resources {
config += r.EmptyImportConfig()
}
Expand All @@ -232,7 +227,16 @@ provider "azapi" {
break
}
}
config = fmt.Sprintf(providerConfig, subscriptionId) + config

return config
const providerConfig = `
provider "azurerm" {
features {}
subscription_id = "%s"
}

provider "azapi" {
}
`

return fmt.Sprintf(providerConfig, subscriptionId) + config
}
43 changes: 43 additions & 0 deletions helper/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io/fs"
"os"
"path"
"strings"

"github.com/hashicorp/hcl/v2"
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion tf/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions types/azapi_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
20 changes: 10 additions & 10 deletions types/azurerm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading