153 lines
3.5 KiB
Go
153 lines
3.5 KiB
Go
package flatmap
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hil"
|
|
)
|
|
|
|
// Expand takes a map and a key (prefix) and expands that value into
|
|
// a more complex structure. This is the reverse of the Flatten operation.
|
|
func Expand(m map[string]string, key string) interface{} {
|
|
// If the key is exactly a key in the map, just return it
|
|
if v, ok := m[key]; ok {
|
|
if v == "true" {
|
|
return true
|
|
} else if v == "false" {
|
|
return false
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
// Check if the key is an array, and if so, expand the array
|
|
if v, ok := m[key+".#"]; ok {
|
|
// If the count of the key is unknown, then just put the unknown
|
|
// value in the value itself. This will be detected by Terraform
|
|
// core later.
|
|
if v == hil.UnknownValue {
|
|
return v
|
|
}
|
|
|
|
return expandArray(m, key)
|
|
}
|
|
|
|
// Check if this is a prefix in the map
|
|
prefix := key + "."
|
|
for k := range m {
|
|
if strings.HasPrefix(k, prefix) {
|
|
return expandMap(m, prefix)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func expandArray(m map[string]string, prefix string) []interface{} {
|
|
num, err := strconv.ParseInt(m[prefix+".#"], 0, 0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// If the number of elements in this array is 0, then return an
|
|
// empty slice as there is nothing to expand. Trying to expand it
|
|
// anyway could lead to crashes as any child maps, arrays or sets
|
|
// that no longer exist are still shown as empty with a count of 0.
|
|
if num == 0 {
|
|
return []interface{}{}
|
|
}
|
|
|
|
// NOTE: "num" is not necessarily accurate, e.g. if a user tampers
|
|
// with state, so the following code should not crash when given a
|
|
// number of items more or less than what's given in num. The
|
|
// num key is mainly just a hint that this is a list or set.
|
|
|
|
// The Schema "Set" type stores its values in an array format, but
|
|
// using numeric hash values instead of ordinal keys. Take the set
|
|
// of keys regardless of value, and expand them in numeric order.
|
|
// See GH-11042 for more details.
|
|
keySet := map[int]bool{}
|
|
computed := map[string]bool{}
|
|
for k := range m {
|
|
if !strings.HasPrefix(k, prefix+".") {
|
|
continue
|
|
}
|
|
|
|
key := k[len(prefix)+1:]
|
|
idx := strings.Index(key, ".")
|
|
if idx != -1 {
|
|
key = key[:idx]
|
|
}
|
|
|
|
// skip the count value
|
|
if key == "#" {
|
|
continue
|
|
}
|
|
|
|
// strip the computed flag if there is one
|
|
if strings.HasPrefix(key, "~") {
|
|
key = key[1:]
|
|
computed[key] = true
|
|
}
|
|
|
|
k, err := strconv.Atoi(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keySet[int(k)] = true
|
|
}
|
|
|
|
keysList := make([]int, 0, num)
|
|
for key := range keySet {
|
|
keysList = append(keysList, key)
|
|
}
|
|
sort.Ints(keysList)
|
|
|
|
result := make([]interface{}, len(keysList))
|
|
for i, key := range keysList {
|
|
keyString := strconv.Itoa(key)
|
|
if computed[keyString] {
|
|
keyString = "~" + keyString
|
|
}
|
|
result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func expandMap(m map[string]string, prefix string) map[string]interface{} {
|
|
// Submaps may not have a '%' key, so we can't count on this value being
|
|
// here. If we don't have a count, just proceed as if we have have a map.
|
|
if count, ok := m[prefix+"%"]; ok && count == "0" {
|
|
return map[string]interface{}{}
|
|
}
|
|
|
|
result := make(map[string]interface{})
|
|
for k := range m {
|
|
if !strings.HasPrefix(k, prefix) {
|
|
continue
|
|
}
|
|
|
|
key := k[len(prefix):]
|
|
idx := strings.Index(key, ".")
|
|
if idx != -1 {
|
|
key = key[:idx]
|
|
}
|
|
if _, ok := result[key]; ok {
|
|
continue
|
|
}
|
|
|
|
// skip the map count value
|
|
if key == "%" {
|
|
continue
|
|
}
|
|
|
|
result[key] = Expand(m, k[:len(prefix)+len(key)])
|
|
}
|
|
|
|
return result
|
|
}
|