github.com/openshift/hashicorp-terraform@v0.11.12-beta1/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 // NOTE: "num" is not necessarily accurate, e.g. if a user tampers 64 // with state, so the following code should not crash when given a 65 // number of items more or less than what's given in num. The 66 // num key is mainly just a hint that this is a list or set. 67 68 // The Schema "Set" type stores its values in an array format, but 69 // using numeric hash values instead of ordinal keys. Take the set 70 // of keys regardless of value, and expand them in numeric order. 71 // See GH-11042 for more details. 72 keySet := map[int]bool{} 73 computed := map[string]bool{} 74 for k := range m { 75 if !strings.HasPrefix(k, prefix+".") { 76 continue 77 } 78 79 key := k[len(prefix)+1:] 80 idx := strings.Index(key, ".") 81 if idx != -1 { 82 key = key[:idx] 83 } 84 85 // skip the count value 86 if key == "#" { 87 continue 88 } 89 90 // strip the computed flag if there is one 91 if strings.HasPrefix(key, "~") { 92 key = key[1:] 93 computed[key] = true 94 } 95 96 k, err := strconv.Atoi(key) 97 if err != nil { 98 panic(err) 99 } 100 keySet[int(k)] = true 101 } 102 103 keysList := make([]int, 0, num) 104 for key := range keySet { 105 keysList = append(keysList, key) 106 } 107 sort.Ints(keysList) 108 109 result := make([]interface{}, len(keysList)) 110 for i, key := range keysList { 111 keyString := strconv.Itoa(key) 112 if computed[keyString] { 113 keyString = "~" + keyString 114 } 115 result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString)) 116 } 117 118 return result 119 } 120 121 func expandMap(m map[string]string, prefix string) map[string]interface{} { 122 // Submaps may not have a '%' key, so we can't count on this value being 123 // here. If we don't have a count, just proceed as if we have have a map. 124 if count, ok := m[prefix+"%"]; ok && count == "0" { 125 return map[string]interface{}{} 126 } 127 128 result := make(map[string]interface{}) 129 for k := range m { 130 if !strings.HasPrefix(k, prefix) { 131 continue 132 } 133 134 key := k[len(prefix):] 135 idx := strings.Index(key, ".") 136 if idx != -1 { 137 key = key[:idx] 138 } 139 if _, ok := result[key]; ok { 140 continue 141 } 142 143 // skip the map count value 144 if key == "%" { 145 continue 146 } 147 148 result[key] = Expand(m, k[:len(prefix)+len(key)]) 149 } 150 151 return result 152 }