github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/hcl2shim/flatmap.go (about) 1 package hcl2shim 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/zclconf/go-cty/cty/convert" 9 10 "github.com/zclconf/go-cty/cty" 11 ) 12 13 // FlatmapValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic 14 // types library that HCL2 uses) to a map compatible with what would be 15 // produced by the "flatmap" package. 16 // 17 // The type of the given value informs the structure of the resulting map. 18 // The value must be of an object type or this function will panic. 19 // 20 // Flatmap values can only represent maps when they are of primitive types, 21 // so the given value must not have any maps of complex types or the result 22 // is undefined. 23 func FlatmapValueFromHCL2(v cty.Value) map[string]string { 24 if v.IsNull() { 25 return nil 26 } 27 28 if !v.Type().IsObjectType() { 29 panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", v.Type())) 30 } 31 32 m := make(map[string]string) 33 flatmapValueFromHCL2Map(m, "", v) 34 return m 35 } 36 37 func flatmapValueFromHCL2Value(m map[string]string, key string, val cty.Value) { 38 ty := val.Type() 39 switch { 40 case ty.IsPrimitiveType() || ty == cty.DynamicPseudoType: 41 flatmapValueFromHCL2Primitive(m, key, val) 42 case ty.IsObjectType() || ty.IsMapType(): 43 flatmapValueFromHCL2Map(m, key+".", val) 44 case ty.IsTupleType() || ty.IsListType() || ty.IsSetType(): 45 flatmapValueFromHCL2Seq(m, key+".", val) 46 default: 47 panic(fmt.Sprintf("cannot encode %s to flatmap", ty.FriendlyName())) 48 } 49 } 50 51 func flatmapValueFromHCL2Primitive(m map[string]string, key string, val cty.Value) { 52 if !val.IsKnown() { 53 m[key] = UnknownVariableValue 54 return 55 } 56 if val.IsNull() { 57 // Omit entirely 58 return 59 } 60 61 var err error 62 val, err = convert.Convert(val, cty.String) 63 if err != nil { 64 // Should not be possible, since all primitive types can convert to string. 65 panic(fmt.Sprintf("invalid primitive encoding to flatmap: %s", err)) 66 } 67 m[key] = val.AsString() 68 } 69 70 func flatmapValueFromHCL2Map(m map[string]string, prefix string, val cty.Value) { 71 if val.IsNull() { 72 // Omit entirely 73 return 74 } 75 if !val.IsKnown() { 76 switch { 77 case val.Type().IsObjectType(): 78 // Whole objects can't be unknown in flatmap, so instead we'll 79 // just write all of the attribute values out as unknown. 80 for name, aty := range val.Type().AttributeTypes() { 81 flatmapValueFromHCL2Value(m, prefix+name, cty.UnknownVal(aty)) 82 } 83 default: 84 m[prefix+"%"] = UnknownVariableValue 85 } 86 return 87 } 88 89 len := 0 90 for it := val.ElementIterator(); it.Next(); { 91 ak, av := it.Element() 92 name := ak.AsString() 93 flatmapValueFromHCL2Value(m, prefix+name, av) 94 len++ 95 } 96 if !val.Type().IsObjectType() { // objects don't have an explicit count included, since their attribute count is fixed 97 m[prefix+"%"] = strconv.Itoa(len) 98 } 99 } 100 101 func flatmapValueFromHCL2Seq(m map[string]string, prefix string, val cty.Value) { 102 if val.IsNull() { 103 // Omit entirely 104 return 105 } 106 if !val.IsKnown() { 107 m[prefix+"#"] = UnknownVariableValue 108 return 109 } 110 111 // For sets this won't actually generate exactly what helper/schema would've 112 // generated, because we don't have access to the set key function it 113 // would've used. However, in practice it doesn't actually matter what the 114 // keys are as long as they are unique, so we'll just generate sequential 115 // indexes for them as if it were a list. 116 // 117 // An important implication of this, however, is that the set ordering will 118 // not be consistent across mutations and so different keys may be assigned 119 // to the same value when round-tripping. Since this shim is intended to 120 // be short-lived and not used for round-tripping, we accept this. 121 i := 0 122 for it := val.ElementIterator(); it.Next(); { 123 _, av := it.Element() 124 key := prefix + strconv.Itoa(i) 125 flatmapValueFromHCL2Value(m, key, av) 126 i++ 127 } 128 m[prefix+"#"] = strconv.Itoa(i) 129 } 130 131 // HCL2ValueFromFlatmap converts a map compatible with what would be produced 132 // by the "flatmap" package to a HCL2 (really, the cty dynamic types library 133 // that HCL2 uses) object type. 134 // 135 // The intended result type must be provided in order to guide how the 136 // map contents are decoded. This must be an object type or this function 137 // will panic. 138 // 139 // Flatmap values can only represent maps when they are of primitive types, 140 // so the given type must not have any maps of complex types or the result 141 // is undefined. 142 // 143 // The result may contain null values if the given map does not contain keys 144 // for all of the different key paths implied by the given type. 145 func HCL2ValueFromFlatmap(m map[string]string, ty cty.Type) (cty.Value, error) { 146 if m == nil { 147 return cty.NullVal(ty), nil 148 } 149 if !ty.IsObjectType() { 150 panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", ty)) 151 } 152 153 return hcl2ValueFromFlatmapObject(m, "", ty.AttributeTypes()) 154 } 155 156 func hcl2ValueFromFlatmapValue(m map[string]string, key string, ty cty.Type) (cty.Value, error) { 157 var val cty.Value 158 var err error 159 switch { 160 case ty.IsPrimitiveType(): 161 val, err = hcl2ValueFromFlatmapPrimitive(m, key, ty) 162 case ty.IsObjectType(): 163 val, err = hcl2ValueFromFlatmapObject(m, key+".", ty.AttributeTypes()) 164 case ty.IsTupleType(): 165 val, err = hcl2ValueFromFlatmapTuple(m, key+".", ty.TupleElementTypes()) 166 case ty.IsMapType(): 167 val, err = hcl2ValueFromFlatmapMap(m, key+".", ty) 168 case ty.IsListType(): 169 val, err = hcl2ValueFromFlatmapList(m, key+".", ty) 170 case ty.IsSetType(): 171 val, err = hcl2ValueFromFlatmapSet(m, key+".", ty) 172 default: 173 err = fmt.Errorf("cannot decode %s from flatmap", ty.FriendlyName()) 174 } 175 176 if err != nil { 177 return cty.DynamicVal, err 178 } 179 return val, nil 180 } 181 182 func hcl2ValueFromFlatmapPrimitive(m map[string]string, key string, ty cty.Type) (cty.Value, error) { 183 rawVal, exists := m[key] 184 if !exists { 185 return cty.NullVal(ty), nil 186 } 187 if rawVal == UnknownVariableValue { 188 return cty.UnknownVal(ty), nil 189 } 190 191 var err error 192 val := cty.StringVal(rawVal) 193 val, err = convert.Convert(val, ty) 194 if err != nil { 195 // This should never happen for _valid_ input, but flatmap data might 196 // be tampered with by the user and become invalid. 197 return cty.DynamicVal, fmt.Errorf("invalid value for %q in state: %s", key, err) 198 } 199 200 return val, nil 201 } 202 203 func hcl2ValueFromFlatmapObject(m map[string]string, prefix string, atys map[string]cty.Type) (cty.Value, error) { 204 vals := make(map[string]cty.Value) 205 for name, aty := range atys { 206 val, err := hcl2ValueFromFlatmapValue(m, prefix+name, aty) 207 if err != nil { 208 return cty.DynamicVal, err 209 } 210 vals[name] = val 211 } 212 return cty.ObjectVal(vals), nil 213 } 214 215 func hcl2ValueFromFlatmapTuple(m map[string]string, prefix string, etys []cty.Type) (cty.Value, error) { 216 var vals []cty.Value 217 218 // if the container is unknown, there is no count string 219 listName := strings.TrimRight(prefix, ".") 220 if m[listName] == UnknownVariableValue { 221 return cty.UnknownVal(cty.Tuple(etys)), nil 222 } 223 224 countStr, exists := m[prefix+"#"] 225 if !exists { 226 return cty.NullVal(cty.Tuple(etys)), nil 227 } 228 if countStr == UnknownVariableValue { 229 return cty.UnknownVal(cty.Tuple(etys)), nil 230 } 231 232 count, err := strconv.Atoi(countStr) 233 if err != nil { 234 return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err) 235 } 236 if count != len(etys) { 237 return cty.DynamicVal, fmt.Errorf("wrong number of values for %q in state: got %d, but need %d", prefix, count, len(etys)) 238 } 239 240 vals = make([]cty.Value, len(etys)) 241 for i, ety := range etys { 242 key := prefix + strconv.Itoa(i) 243 val, err := hcl2ValueFromFlatmapValue(m, key, ety) 244 if err != nil { 245 return cty.DynamicVal, err 246 } 247 vals[i] = val 248 } 249 return cty.TupleVal(vals), nil 250 } 251 252 func hcl2ValueFromFlatmapMap(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) { 253 vals := make(map[string]cty.Value) 254 ety := ty.ElementType() 255 256 // if the container is unknown, there is no count string 257 listName := strings.TrimRight(prefix, ".") 258 if m[listName] == UnknownVariableValue { 259 return cty.UnknownVal(ty), nil 260 } 261 262 // We actually don't really care about the "count" of a map for our 263 // purposes here, but we do need to check if it _exists_ in order to 264 // recognize the difference between null (not set at all) and empty. 265 if strCount, exists := m[prefix+"%"]; !exists { 266 return cty.NullVal(ty), nil 267 } else if strCount == UnknownVariableValue { 268 return cty.UnknownVal(ty), nil 269 } 270 271 for fullKey := range m { 272 if !strings.HasPrefix(fullKey, prefix) { 273 continue 274 } 275 276 // The flatmap format doesn't allow us to distinguish between keys 277 // that contain periods and nested objects, so by convention a 278 // map is only ever of primitive type in flatmap, and we just assume 279 // that the remainder of the raw key (dots and all) is the key we 280 // want in the result value. 281 key := fullKey[len(prefix):] 282 if key == "%" { 283 // Ignore the "count" key 284 continue 285 } 286 287 val, err := hcl2ValueFromFlatmapValue(m, fullKey, ety) 288 if err != nil { 289 return cty.DynamicVal, err 290 } 291 vals[key] = val 292 } 293 294 if len(vals) == 0 { 295 return cty.MapValEmpty(ety), nil 296 } 297 return cty.MapVal(vals), nil 298 } 299 300 func hcl2ValueFromFlatmapList(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) { 301 var vals []cty.Value 302 303 // if the container is unknown, there is no count string 304 listName := strings.TrimRight(prefix, ".") 305 if m[listName] == UnknownVariableValue { 306 return cty.UnknownVal(ty), nil 307 } 308 309 countStr, exists := m[prefix+"#"] 310 if !exists { 311 return cty.NullVal(ty), nil 312 } 313 if countStr == UnknownVariableValue { 314 return cty.UnknownVal(ty), nil 315 } 316 317 count, err := strconv.Atoi(countStr) 318 if err != nil { 319 return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err) 320 } 321 322 ety := ty.ElementType() 323 if count == 0 { 324 return cty.ListValEmpty(ety), nil 325 } 326 327 vals = make([]cty.Value, count) 328 for i := 0; i < count; i++ { 329 key := prefix + strconv.Itoa(i) 330 val, err := hcl2ValueFromFlatmapValue(m, key, ety) 331 if err != nil { 332 return cty.DynamicVal, err 333 } 334 vals[i] = val 335 } 336 337 return cty.ListVal(vals), nil 338 } 339 340 func hcl2ValueFromFlatmapSet(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) { 341 var vals []cty.Value 342 ety := ty.ElementType() 343 344 // if the container is unknown, there is no count string 345 listName := strings.TrimRight(prefix, ".") 346 if m[listName] == UnknownVariableValue { 347 return cty.UnknownVal(ty), nil 348 } 349 350 strCount, exists := m[prefix+"#"] 351 if !exists { 352 return cty.NullVal(ty), nil 353 } else if strCount == UnknownVariableValue { 354 return cty.UnknownVal(ty), nil 355 } 356 357 // Keep track of keys we've seen, se we don't add the same set value 358 // multiple times. The cty.Set will normally de-duplicate values, but we may 359 // have unknown values that would not show as equivalent. 360 seen := map[string]bool{} 361 362 for fullKey := range m { 363 if !strings.HasPrefix(fullKey, prefix) { 364 continue 365 } 366 subKey := fullKey[len(prefix):] 367 if subKey == "#" { 368 // Ignore the "count" key 369 continue 370 } 371 key := fullKey 372 if dot := strings.IndexByte(subKey, '.'); dot != -1 { 373 key = fullKey[:dot+len(prefix)] 374 } 375 376 if seen[key] { 377 continue 378 } 379 380 seen[key] = true 381 382 // The flatmap format doesn't allow us to distinguish between keys 383 // that contain periods and nested objects, so by convention a 384 // map is only ever of primitive type in flatmap, and we just assume 385 // that the remainder of the raw key (dots and all) is the key we 386 // want in the result value. 387 388 val, err := hcl2ValueFromFlatmapValue(m, key, ety) 389 if err != nil { 390 return cty.DynamicVal, err 391 } 392 vals = append(vals, val) 393 } 394 395 if len(vals) == 0 && strCount == "1" { 396 // An empty set wouldn't be represented in the flatmap, so this must be 397 // a single empty object since the count is actually 1. 398 // Add an appropriately typed null value to the set. 399 var val cty.Value 400 switch { 401 case ety.IsMapType(): 402 val = cty.MapValEmpty(ety) 403 case ety.IsListType(): 404 val = cty.ListValEmpty(ety) 405 case ety.IsSetType(): 406 val = cty.SetValEmpty(ety) 407 case ety.IsObjectType(): 408 // TODO: cty.ObjectValEmpty 409 objectMap := map[string]cty.Value{} 410 for attr, ty := range ety.AttributeTypes() { 411 objectMap[attr] = cty.NullVal(ty) 412 } 413 val = cty.ObjectVal(objectMap) 414 default: 415 val = cty.NullVal(ety) 416 } 417 vals = append(vals, val) 418 419 } else if len(vals) == 0 { 420 return cty.SetValEmpty(ety), nil 421 } 422 423 return cty.SetVal(vals), nil 424 }