terraform/flatmap/expand.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
}