Skip to content

Commit

Permalink
Refactor resource mapping file (#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
magodo authored Sep 27, 2022
1 parent a94af8e commit 77f2c5d
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 142 deletions.
39 changes: 29 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,20 @@ As the last step, `aztfy` will leverage the ARM template to inject dependencies

#### Batch Mode

In batch mode, instead of interactively specifying the mapping from Azurem resource id to the Terraform resource address, users are expected to provide that mapping via the resource mapping file, with the following format:
In batch mode, instead of interactively specifying the mapping from Azurem resource id to the Terraform resource info, users are expected to provide that mapping via the resource mapping file, with the following format:

```json
{
"<azure resource id1>": "<terraform resource type1>.<terraform resource name>",
"<azure resource id2>": "<terraform resource type2>.<terraform resource name>",
"<AZURE RESOURCE ID1 IN UPPERCASE>": {
"resource_type" : "<terraform resource type>",
"resource_name" : "<terraform resource name>",
"resource_id" : "<terraform resource id>"
},
"<AZURE RESOURCE ID2 IN UPPERCASE>": {
"resource_type" : "<terraform resource type>",
"resource_name" : "<terraform resource name>",
"resource_id" : "<terraform resource id>"
},
...
}
```
Expand All @@ -162,19 +170,30 @@ Example:

```json
{
"/subscriptions/0-0-0-0/resourceGroups/tfy-vm/providers/Microsoft.Network/virtualNetworks/example-network": "azurerm_virtual_network.res-0",
"/subscriptions/0-0-0-0/resourceGroups/tfy-vm/providers/Microsoft.Compute/virtualMachines/example-machine": "azurerm_linux_virtual_machine.res-1",
"/subscriptions/0-0-0-0/resourceGroups/tfy-vm/providers/Microsoft.Network/networkInterfaces/example-nic": "azurerm_network_interface.res-2",
"/subscriptions/0-0-0-0/resourceGroups/tfy-vm/providers/Microsoft.Network/networkInterfaces/example-nic1": "azurerm_network_interface.res-3",
"/subscriptions/0-0-0-0/resourceGroups/tfy-vm/providers/Microsoft.Network/virtualNetworks/example-network/subnets/internal": "azurerm_subnet.res-4"
"/SUBSCRIPTIONS/0000/RESOURCEGROUPS/AZTFY-VMDISK": {
"resource_id": "/subscriptions/0000/resourceGroups/aztfy-vmdisk",
"resource_type": "azurerm_resource_group",
"resource_name": "res-1"
},
"/SUBSCRIPTIONS/0000/RESOURCEGROUPS/AZTFY-VMDISK/PROVIDERS/MICROSOFT.COMPUTE/DISKS/AZTFY-TEST-TEST": {
"resource_id": "/subscriptions/0000/resourceGroups/aztfy-vmdisk/providers/Microsoft.Compute/disks/aztfy-test-test",
"resource_type": "azurerm_managed_disk",
"resource_name": "res-2"
},
"/SUBSCRIPTIONS/0000/RESOURCEGROUPS/AZTFY-VMDISK/PROVIDERS/MICROSOFT.COMPUTE/VIRTUALMACHINES/AZTFY-TEST-TEST": {
"resource_id": "/subscriptions/0000/resourceGroups/aztfy-vmdisk/providers/Microsoft.Compute/virtualMachines/aztfy-test-test",
"resource_type": "azurerm_linux_virtual_machine",
"resource_name": "res-3"
},
...
}
```

Then the tool will import each specified resource in the mapping file (if exists) and skip the others.

Especially if the no resource mapping file is specified, `aztfy` will only import the "recognized" resources for you, based on its limited knowledge on the ARM and Terraform resource mappings.
Especially if the no resource mapping file is specified, `aztfy` will only import the "recognized" resources for you, based on its knowledge on the ARM and Terraform resource mappings.

In the batch import mode, users can further specify the `--continue`/`-k` option to make the tool continue even on hitting import error(s) on any resource.
In the batch import mode, users can further specify the `--continue`/`-k` option to make the tool continue even on hitting import error of any resource.

### Remote Backend

Expand Down
36 changes: 15 additions & 21 deletions internal/meta/meta_group_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,6 @@ func (meta *MetaGroupImpl) ListResource() (ImportList, error) {

rl := rset.ToTFResources()

// Some RP will flip the casing on fields like resource group name, causing the TF ID is inconsistent on casing.
// We shall check the existance of the resource id case insensitively.
type MapInfo struct {
id string
addr tfaddr.TFAddr
}
caseInsensitiveMapping := map[string]MapInfo{}
for k, v := range meta.resourceMapping {
caseInsensitiveMapping[strings.ToUpper(k)] = MapInfo{
id: k,
addr: v,
}
}

var l ImportList
for i, res := range rl {
item := ImportItem{
Expand All @@ -120,12 +106,13 @@ func (meta *MetaGroupImpl) ListResource() (ImportList, error) {
item.Recommendations = []string{res.TFType}
}

if len(caseInsensitiveMapping) != 0 {
// TODO: There is a potential issue here that the more than one Azure resources might have the same TF resource id (e.g. a parent resource and its singleton child resource).
// Ideally, we should refactor the resource mapping file to make the Azure resource id as key, and record the TF id and TF address (type + name) as its value.
if info, ok := caseInsensitiveMapping[strings.ToUpper(res.TFId)]; ok {
item.TFResourceId = info.id
item.TFAddr = info.addr
if len(meta.resourceMapping) != 0 {
if entity, ok := meta.resourceMapping[strings.ToUpper(res.AzureId.String())]; ok {
item.TFResourceId = entity.ResourceId
item.TFAddr = tfaddr.TFAddr{
Type: entity.ResourceType,
Name: entity.ResourceName,
}
}
} else {
// Only auto deduce the TF resource type from recommendations when there is no resource mapping file specified.
Expand All @@ -142,7 +129,14 @@ func (meta *MetaGroupImpl) ListResource() (ImportList, error) {
func (meta MetaGroupImpl) ExportResourceMapping(l ImportList) error {
m := resmap.ResourceMapping{}
for _, item := range l {
m[item.TFResourceId] = item.TFAddr
if item.TFAddr.Type == "" {
continue
}
m[strings.ToUpper(item.AzureResourceID.String())] = resmap.ResourceMapEntity{
ResourceId: item.TFResourceId,
ResourceType: item.TFAddr.Type,
ResourceName: item.TFAddr.Name,
}
}
output := filepath.Join(meta.Workspace(), ResourceMappingFileName)
b, err := json.MarshalIndent(m, "", "\t")
Expand Down
44 changes: 9 additions & 35 deletions internal/resmap/resmap.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,13 @@
package resmap

import (
"encoding/json"
"fmt"

"github.com/Azure/aztfy/internal/tfaddr"
)

type ResourceMapping map[string]tfaddr.TFAddr

func (res ResourceMapping) MarshalJSON() ([]byte, error) {
m := map[string]string{}
for id, addr := range res {
m[id] = addr.String()
}
return json.Marshal(m)
type ResourceMapEntity struct {
// TF resource ID
ResourceId string `json:"resource_id"`
// TF resource type
ResourceType string `json:"resource_type"`
// TF resource name
ResourceName string `json:"resource_name"`
}

func (res *ResourceMapping) UnmarshalJSON(b []byte) error {
out := ResourceMapping{}
var m map[string]string
if err := json.Unmarshal(b, &m); err != nil {
return err
}
for id, addr := range m {
var tfAddr tfaddr.TFAddr
if addr != "" {
pTFAddr, err := tfaddr.ParseTFResourceAddr(addr)
if err != nil {
return fmt.Errorf("parsing TF address %q: %v", addr, err)
}
tfAddr = *pTFAddr
}
out[id] = tfAddr
}
*res = out
return nil
}
// ResourceMapping is the resource mapping file, the key is the Azure resource Id in uppercase.
type ResourceMapping map[string]ResourceMapEntity
28 changes: 17 additions & 11 deletions internal/test/cases/case_applicationinsight_webtest.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cases

import (
"encoding/json"
"fmt"

"github.com/Azure/aztfy/internal/test"
Expand Down Expand Up @@ -54,17 +53,24 @@ XML
}

func (CaseApplicationInsightWebTest) ResourceMapping(d test.Data) (resmap.ResourceMapping, error) {
rm := fmt.Sprintf(`{
"/subscriptions/%[1]s/resourceGroups/%[2]s": "azurerm_resource_group.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Insights/components/test-%[3]s": "azurerm_application_insights.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Insights/webTests/test-%[3]s": "azurerm_application_insights_web_test.test"
return test.ResourceMapping(fmt.Sprintf(`{
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_resource_group",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.insights/components/test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_application_insights",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Insights/components/test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.insights/webtests/test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_application_insights_web_test",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Insights/webTests/test-%[3]s"
}
`, d.SubscriptionId, d.RandomRgName(), d.RandomStringOfLength(8))
m := resmap.ResourceMapping{}
if err := json.Unmarshal([]byte(rm), &m); err != nil {
return nil, err
}
return m, nil
}
`, d.SubscriptionId, d.RandomRgName(), d.RandomStringOfLength(8)))
}

func (CaseApplicationInsightWebTest) SingleResourceContext(d test.Data) ([]SingleResourceContext, error) {
Expand Down
59 changes: 44 additions & 15 deletions internal/test/cases/case_compute_vm_disk.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cases

import (
"encoding/json"
"fmt"

"github.com/Azure/aztfy/internal/test"
Expand Down Expand Up @@ -90,21 +89,51 @@ resource "azurerm_virtual_machine_data_disk_attachment" "test" {
}

func (CaseComputeVMDisk) ResourceMapping(d test.Data) (resmap.ResourceMapping, error) {
rm := fmt.Sprintf(`{
"/subscriptions/%[1]s/resourceGroups/%[2]s": "azurerm_resource_group.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Compute/disks/aztfy-test-%[3]s": "azurerm_managed_disk.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Compute/virtualMachines/aztfy-test-%[3]s/dataDisks/aztfy-test-%[3]s": "azurerm_virtual_machine_data_disk_attachment.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Compute/virtualMachines/aztfy-test-%[3]s": "azurerm_linux_virtual_machine.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Network/networkInterfaces/aztfy-test-%[3]s": "azurerm_network_interface.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Network/virtualNetworks/aztfy-test-%[3]s": "azurerm_virtual_network.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Network/virtualNetworks/aztfy-test-%[3]s/subnets/internal": "azurerm_subnet.test"
return test.ResourceMapping(fmt.Sprintf(`{
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_resource_group",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.compute/disks/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_managed_disk",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Compute/disks/aztfy-test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.compute/virtualmachines/aztfy-test-%[3]s/datadisks/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_virtual_machine_data_disk_attachment",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Compute/virtualMachines/aztfy-test-%[3]s/dataDisks/aztfy-test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.compute/virtualmachines/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_linux_virtual_machine",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Compute/virtualMachines/aztfy-test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.network/networkinterfaces/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_network_interface",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Network/networkInterfaces/aztfy-test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.network/virtualnetworks/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_virtual_network",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Network/virtualNetworks/aztfy-test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.network/virtualnetworks/aztfy-test-%[3]s/subnets/internal" | ToUpper | Quote }}: {
"resource_type": "azurerm_subnet",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Network/virtualNetworks/aztfy-test-%[3]s/subnets/internal"
}
}
`, d.SubscriptionId, d.RandomRgName(), d.RandomStringOfLength(8))
m := resmap.ResourceMapping{}
if err := json.Unmarshal([]byte(rm), &m); err != nil {
return nil, err
}
return m, nil
`, d.SubscriptionId, d.RandomRgName(), d.RandomStringOfLength(8)))
}

func (CaseComputeVMDisk) SingleResourceContext(d test.Data) ([]SingleResourceContext, error) {
Expand Down
46 changes: 33 additions & 13 deletions internal/test/cases/case_function_app_slot.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cases

import (
"encoding/json"
"fmt"

"github.com/Azure/aztfy/internal/test"
Expand Down Expand Up @@ -61,20 +60,41 @@ resource "azurerm_windows_function_app_slot" "test" {
}

func (CaseFunctionAppSlot) ResourceMapping(d test.Data) (resmap.ResourceMapping, error) {
rm := fmt.Sprintf(`{
"/subscriptions/%[1]s/resourceGroups/%[2]s": "azurerm_resource_group.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Storage/storageAccounts/aztfytest%[3]s": "azurerm_storage_account.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Web/serverfarms/aztfy-test-%[3]s": "azurerm_service_plan.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Web/sites/aztfy-test-%[3]s": "azurerm_windows_function_app.test",
"/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Web/sites/aztfy-test-%[3]s/slots/aztfy-test-%[3]s": "azurerm_windows_function_app_slot.test"
return test.ResourceMapping(fmt.Sprintf(`{
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_resource_group",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.storage/storageaccounts/aztfytest%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_storage_account",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Storage/storageAccounts/aztfytest%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.web/serverfarms/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_service_plan",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Web/serverfarms/aztfy-test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.web/sites/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_windows_function_app",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Web/sites/aztfy-test-%[3]s"
},
{{ "/subscriptions/%[1]s/resourcegroups/%[2]s/providers/microsoft.web/sites/aztfy-test-%[3]s/slots/aztfy-test-%[3]s" | ToUpper | Quote }}: {
"resource_type": "azurerm_windows_function_app_slot",
"resource_name": "test",
"resource_id": "/subscriptions/%[1]s/resourceGroups/%[2]s/providers/Microsoft.Web/sites/aztfy-test-%[3]s/slots/aztfy-test-%[3]s"
}
`, d.SubscriptionId, d.RandomRgName(), d.RandomStringOfLength(8))
m := resmap.ResourceMapping{}
if err := json.Unmarshal([]byte(rm), &m); err != nil {
return nil, err
}
return m, nil
}
`, d.SubscriptionId, d.RandomRgName(), d.RandomStringOfLength(8)))
}

func (CaseFunctionAppSlot) SingleResourceContext(d test.Data) ([]SingleResourceContext, error) {
return []SingleResourceContext{
{
Expand Down
Loading

0 comments on commit 77f2c5d

Please sign in to comment.