| 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 |
| } |