Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor resource mapping file #234

Merged
merged 1 commit into from
Sep 27, 2022
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
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