github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/flatmap/expand.go (about) 1 package flatmap 2 3 import ( 4 "fmt" 5 "sort" 6 "strconv" 7 "strings" 8 9 "github.com/hashicorp/hil" 10 ) 11 12 // Expand takes a map and a key (prefix) and expands that value into 13 // a more complex structure. This is the reverse of the Flatten operation. 14 func Expand(m map[string]string, key string) interface{} { 15 // If the key is exactly a key in the map, just return it 16 if v, ok := m[key]; ok { 17 if v == "true" { 18 return true 19 } else if v == "false" { 20 return false 21 } 22 23 return v 24 } 25 26 // Check if the key is an array, and if so, expand the array 27 if v, ok := m[key+".#"]; ok { 28 // If the count of the key is unknown, then just put the unknown 29 // value in the value itself. This will be detected by Terraform 30 // core later. 31 if v == hil.UnknownValue { 32 return v 33 } 34 35 return expandArray(m, key) 36 } 37 38 // Check if this is a prefix in the map 39 prefix := key + "." 40 for k := range m { 41 if strings.HasPrefix(k, prefix) { 42 return expandMap(m, prefix) 43 } 44 } 45 46 return nil 47 } 48 49 func expandArray(m map[string]string, prefix string) []interface{} { 50 num, err := strconv.ParseInt(m[prefix+".#"], 0, 0) 51 if err != nil { 52 panic(err) 53 } 54 55 // If the number of elements in this array is 0, then return an 56 // empty slice as there is nothing to expand. Trying to expand it 57 // anyway could lead to crashes as any child maps, arrays or sets 58 // that no longer exist are still shown as empty with a count of 0. 59 if num == 0 { 60 return []interface{}{} 61 } 62 63 // The Schema "Set" type stores its values in an array format, but 64 // using numeric hash values instead of ordinal keys. Take the set 65 // of keys regardless of value, and expand them in numeric order. 66 // See GH-11042 for more details. 67 keySet := map[int]bool{} 68 computed := map[string]bool{} 69 for k := range m { 70 if !strings.HasPrefix(k, prefix+".") { 71 continue 72 } 73 74 key := k[len(prefix)+1:] 75 idx := strings.Index(key, ".") 76 if idx != -1 { 77 key = key[:idx] 78 } 79 80 // skip the count value 81 if key == "#" { 82 continue 83 } 84 85 // strip the computed flag if there is one 86 if strings.HasPrefix(key, "~") { 87 key = key[1:] 88 computed[key] = true 89 } 90 91 k, err := strconv.Atoi(key) 92 if err != nil { 93 panic(err) 94 } 95 keySet[int(k)] = true 96 } 97 98 keysList := make([]int, 0, num) 99 for key := range keySet { 100 keysList = append(keysList, key) 101 } 102 sort.Ints(keysList) 103 104 result := make([]interface{}, num) 105 for i, key := range keysList { 106 keyString := strconv.Itoa(key) 107 if computed[keyString] { 108 keyString = "~" + keyString 109 } 110 result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString)) 111 } 112 113 return result 114 } 115 116 func expandMap(m map[string]string, prefix string) map[string]interface{} { 117 // Submaps may not have a '%' key, so we can't count on this value being 118 // here. If we don't have a count, just proceed as if we have have a map. 119 if count, ok := m[prefix+"%"]; ok && count == "0" { 120 return map[string]interface{}{} 121 } 122 123 result := make(map[string]interface{}) 124 for k := range m { 125 if !strings.HasPrefix(k, prefix) { 126 continue 127 } 128 129 key := k[len(prefix):] 130 idx := strings.Index(key, ".") 131 if idx != -1 { 132 key = key[:idx] 133 } 134 if _, ok := result[key]; ok { 135 continue 136 } 137 138 // skip the map count value 139 if key == "%" { 140 continue 141 } 142 143 result[key] = Expand(m, k[:len(prefix)+len(key)]) 144 } 145 146 return result 147 }