internal/resourceset/azure_resource_set_hack.go (87 lines of code) (raw):
package resourceset
import (
"encoding/json"
"fmt"
"strings"
"github.com/magodo/armid"
"github.com/tidwall/gjson"
)
// PopulateResourceTypes is a map to record resources that might to be populate other resources.
// This is used in single resource mode to decide whether to call API to get its body.
var PopulateResourceTypes = map[string]bool{
"MICROSOFT.COMPUTE/VIRTUALMACHINES": true,
}
// PopulateResource populate single resource for certain Azure resouce type that is known might maps to more than one TF resources, which are missing from azlist.
// In most cases, this step is used to populate the Azure managed resource.
func (rset *AzureResourceSet) PopulateResource() error {
// Populate managed data disk (and the association) for VMs that are missing from Azure exported resource set.
if err := rset.populateForVirtualMachine(); err != nil {
return err
}
return nil
}
// ReduceResource reduce the resource set for certain multiple Azure resources that are known to be mapped to only one TF resource.
func (rset *AzureResourceSet) ReduceResource() error {
// KeyVault certificate is a special resource that its data plane entity is composed of two control plane resources.
// Azure exports the control plane resource ids, while Terraform uses its data plane counterpart.
if err := rset.reduceForKeyVaultCertificate(); err != nil {
return err
}
return nil
}
func (rset *AzureResourceSet) reduceForKeyVaultCertificate() error {
newResoruces := []AzureResource{}
pending := map[string]AzureResource{}
for _, res := range rset.Resources {
if !strings.EqualFold(res.Id.RouteScopeString(), "/Microsoft.KeyVault/vaults/keys") && !strings.EqualFold(res.Id.RouteScopeString(), "/Microsoft.KeyVault/vaults/secrets") {
newResoruces = append(newResoruces, res)
continue
}
names := res.Id.Names()
certName := names[len(names)-1]
if _, ok := pending[certName]; !ok {
pending[certName] = res
continue
}
delete(pending, certName)
certId := res.Id.Clone().(*armid.ScopedResourceId)
certId.AttrTypes[len(certId.AttrTypes)-1] = "certificates"
newResoruces = append(newResoruces, AzureResource{
Id: certId,
})
}
for _, res := range pending {
newResoruces = append(newResoruces, res)
}
rset.Resources = newResoruces
return nil
}
func (rset *AzureResourceSet) populateForVirtualMachine() error {
for _, res := range rset.Resources[:] {
if strings.ToUpper(res.Id.RouteScopeString()) != "/MICROSOFT.COMPUTE/VIRTUALMACHINES" {
continue
}
disks, err := populateManagedResourcesByPath(res, "properties.storageProfile.dataDisks.#.managedDisk.id")
if err != nil {
return fmt.Errorf(`populating managed disks for %q: %v`, res.Id, err)
}
rset.Resources = append(rset.Resources, disks...)
}
return nil
}
// populateManagedResourcesByPath populate the managed resources in the specified paths.
func populateManagedResourcesByPath(res AzureResource, paths ...string) ([]AzureResource, error) {
b, err := json.Marshal(res.Properties)
if err != nil {
return nil, fmt.Errorf("marshaling %v: %v", res.Properties, err)
}
var resources []AzureResource
for _, path := range paths {
result := gjson.GetBytes(b, path)
if !result.Exists() {
continue
}
for _, exprResult := range result.Array() {
mid := exprResult.String()
id, err := armid.ParseResourceId(mid)
if err != nil {
return nil, fmt.Errorf("parsing managed resource id %s: %v", mid, err)
}
resources = append(resources, AzureResource{
Id: id,
})
}
}
return resources, nil
}