types/utils.go (235 lines of code) (raw):
package types
import (
"encoding/json"
"fmt"
"strconv"
"strings"
tfjson "github.com/hashicorp/terraform-json"
)
func getApiVersion(value interface{}) string {
if valueMap, ok := value.(map[string]interface{}); ok && valueMap["type"] != nil {
if typeValue, ok := valueMap["type"].(string); ok {
if parts := strings.Split(typeValue, "@"); len(parts) == 2 {
return parts[1]
}
}
}
return ""
}
func getId(value interface{}) string {
if valueMap, ok := value.(map[string]interface{}); ok && valueMap["id"] != nil {
if resourceId, ok := valueMap["id"].(string); ok {
return resourceId
}
}
return ""
}
func getOutputsForAddress(address string, refValueMap map[string]interface{}) []Output {
res := make([]Output, 0)
for key, value := range refValueMap {
if strings.HasPrefix(key, fmt.Sprintf("%s.output.", address)) {
res = append(res, Output{
OldName: key,
Value: value,
})
}
}
return res
}
func getReferencesForAddress(address string, p *tfjson.Plan, refValueMap map[string]interface{}) []Reference {
res := make([]Reference, 0)
for _, r := range p.Config.RootModule.Resources {
if r.Address == address {
for _, expression := range r.Expressions {
res = append(res, listReferences(expression)...)
}
temp := make([]Reference, 0)
for _, ref := range res {
// if it refers to some resource's id before, after migration, it will refer to its name now
// TODO: use regex
if len(strings.Split(ref.Name, ".")) == 3 && strings.HasSuffix(ref.Name, "id") {
temp = append(temp, Reference{
Name: ref.Name[0:strings.LastIndex(ref.Name, ".")] + ".name",
})
if strings.HasPrefix(ref.Name, "azurerm_resource_group") {
temp = append(temp, Reference{
Name: ref.Name[0:strings.LastIndex(ref.Name, ".")] + ".location",
})
}
}
if len(strings.Split(ref.Name, ".")) == 3 && strings.HasSuffix(ref.Name, "name") {
temp = append(temp, Reference{
Name: ref.Name[0:strings.LastIndex(ref.Name, ".")] + ".id",
})
}
}
res = append(res, temp...)
break
}
}
refSet := make(map[string]Reference)
for i := range res {
refSet[res[i].Name] = res[i]
}
res = make([]Reference, 0)
for _, ref := range refSet {
ref.Value = refValueMap[ref.Name]
res = append(res, ref)
}
return res
}
func listReferences(expression *tfjson.Expression) []Reference {
res := make([]Reference, 0)
for _, ref := range expression.References {
res = append(res, Reference{
Name: ref,
})
}
for _, block := range expression.NestedBlocks {
for _, exp := range block {
res = append(res, listReferences(exp)...)
}
}
return res
}
func getRefValueMap(p *tfjson.Plan) map[string]interface{} {
refValueMap := make(map[string]interface{})
for _, resourceChange := range p.ResourceChanges {
if resourceChange == nil || resourceChange.Change == nil || resourceChange.Change.Before == nil {
continue
}
prefix := resourceChange.Address
if strings.HasPrefix(prefix, "azapi") {
if beforeMap, ok := resourceChange.Change.Before.(map[string]interface{}); ok && beforeMap["output"] != nil {
if output, ok := beforeMap["output"].(string); ok {
var outputObj interface{}
if err := json.Unmarshal([]byte(output), &outputObj); err == nil {
propValueMap := getPropValueMap(outputObj, fmt.Sprintf("jsondecode(%s.output)", prefix))
for key, value := range propValueMap {
refValueMap[key] = value
}
} else {
if outputObj := beforeMap["output"]; outputObj != nil {
propValueMap := getPropValueMap(outputObj, fmt.Sprintf("%s.output", prefix))
for key, value := range propValueMap {
refValueMap[key] = value
}
}
}
}
}
}
propValueMap := getPropValueMap(resourceChange.Change.Before, prefix)
for key, value := range propValueMap {
refValueMap[key] = value
}
}
for key, variable := range p.Variables {
refValueMap["var."+key] = variable.Value
}
return refValueMap
}
func getPropValueMap(input interface{}, prefix string) map[string]interface{} {
res := make(map[string]interface{})
if input == nil {
return res
}
switch cur := input.(type) {
case map[string]interface{}:
for key, value := range cur {
propValueMap := getPropValueMap(value, prefix+"."+key)
for k, v := range propValueMap {
res[k] = v
}
}
case []interface{}:
for index, value := range cur {
propValueMap := getPropValueMap(value, fmt.Sprintf("%s.%d", prefix, index))
for k, v := range propValueMap {
res[k] = v
}
}
default:
res[prefix] = cur
}
return res
}
func getInputProperties(address string, p *tfjson.Plan) []string {
for _, resourceChange := range p.ResourceChanges {
if resourceChange == nil || resourceChange.Change == nil || resourceChange.Change.Before == nil ||
(resourceChange.Address != address && !strings.HasPrefix(resourceChange.Address, address+"[")) {
continue
}
stateMap, ok := resourceChange.Change.Before.(map[string]interface{})
if !ok {
return nil
}
props := make([]string, 0)
if stateMap["tags"] != nil {
if tags, ok := stateMap["tags"].(map[string]interface{}); ok && len(tags) > 0 {
props = append(props, "tags")
}
}
if stateMap["identity"] != nil {
if identities, ok := stateMap["identity"].([]interface{}); ok && len(identities) > 0 {
if identity, ok := identities[0].(map[string]interface{}); ok {
if identity["type"] != nil {
if identityType, ok := identity["type"].(string); ok && len(identityType) > 0 {
props = append(props, "identity.type")
}
if identityIds, ok := identity["identity_ids"].([]interface{}); ok && len(identityIds) > 0 {
props = append(props, "identity.userAssignedIdentities")
}
}
}
}
}
if stateMap["body"] != nil {
if body, ok := stateMap["body"].(string); ok {
var bodyObj interface{}
if err := json.Unmarshal([]byte(body), &bodyObj); err == nil {
propValueMap := getPropValueMap(bodyObj, "")
propSet := make(map[string]bool)
for key := range propValueMap {
key = strings.TrimPrefix(key, ".")
if strings.HasPrefix(key, "tags") {
key = "tags"
}
propSet[key] = true
}
for key := range propSet {
key = removeIndexOfProp(key)
props = append(props, key)
}
}
} else {
if bodyObj := stateMap["body"]; bodyObj != nil {
propValueMap := getPropValueMap(bodyObj, "")
propSet := make(map[string]bool)
for key := range propValueMap {
key = strings.TrimPrefix(key, ".")
if strings.HasPrefix(key, "tags") {
key = "tags"
}
propSet[key] = true
}
for key := range propSet {
key = removeIndexOfProp(key)
props = append(props, key)
}
}
}
}
return props
}
return nil
}
func removeIndexOfProp(prop string) string {
parts := strings.Split(prop, ".")
res := make([]string, 0)
for _, part := range parts {
if _, err := strconv.Atoi(part); err == nil {
continue
}
res = append(res, part)
}
return strings.Join(res, ".")
}