github.com/GoogleCloudPlatform/terraformer@v0.8.18/terraformutils/flatmap.go (about) 1 // Copyright 2018 The Terraformer Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package terraformutils 16 17 import ( 18 "fmt" 19 "reflect" 20 "regexp" 21 "strconv" 22 "strings" 23 24 "github.com/hashicorp/terraform/configs/hcl2shim" 25 "github.com/zclconf/go-cty/cty" 26 ) 27 28 type Flatmapper interface { 29 Parse(ty cty.Type) (map[string]interface{}, error) 30 } 31 32 type FlatmapParser struct { 33 Flatmapper 34 attributes map[string]string 35 ignoreKeys []*regexp.Regexp 36 allowEmptyValues []*regexp.Regexp 37 } 38 39 func NewFlatmapParser(attributes map[string]string, ignoreKeys []*regexp.Regexp, allowEmptyValues []*regexp.Regexp) *FlatmapParser { 40 return &FlatmapParser{ 41 attributes: attributes, 42 ignoreKeys: ignoreKeys, 43 allowEmptyValues: allowEmptyValues, 44 } 45 } 46 47 // FromFlatmap converts a map compatible with what would be produced 48 // by the "flatmap" package to a map[string]interface{} object type. 49 // 50 // The intended result type must be provided in order to guide how the 51 // map contents are decoded. This must be an object type or this function 52 // will panic. 53 // 54 // Flatmap values can only represent maps when they are of primitive types, 55 // so the given type must not have any maps of complex types or the result 56 // is undefined. 57 // 58 // The result may contain null values if the given map does not contain keys 59 // for all of the different key paths implied by the given type. 60 func (p *FlatmapParser) Parse(ty cty.Type) (map[string]interface{}, error) { 61 if p.attributes == nil { 62 return nil, nil 63 } 64 if !ty.IsObjectType() { 65 return nil, fmt.Errorf("FlatmapParser#Parse called on %#v", ty) 66 } 67 return p.fromFlatmapObject("", ty.AttributeTypes()) 68 } 69 70 func (p *FlatmapParser) fromFlatmapValue(key string, ty cty.Type) (interface{}, error) { 71 switch { 72 case ty.IsPrimitiveType(): 73 return p.fromFlatmapPrimitive(key) 74 case ty.IsObjectType(): 75 return p.fromFlatmapObject(key+".", ty.AttributeTypes()) 76 case ty.IsTupleType(): 77 return p.fromFlatmapTuple(key+".", ty.TupleElementTypes()) 78 case ty.IsMapType(): 79 return p.fromFlatmapMap(key+".", ty.ElementType()) 80 case ty.IsListType(): 81 return p.fromFlatmapList(key+".", ty.ElementType()) 82 case ty.IsSetType(): 83 return p.fromFlatmapSet(key+".", ty.ElementType()) 84 default: 85 return nil, fmt.Errorf("cannot decode %s from flatmap", ty.FriendlyName()) 86 } 87 } 88 89 func (p *FlatmapParser) fromFlatmapPrimitive(key string) (interface{}, error) { 90 value, ok := p.attributes[key] 91 if !ok { 92 return nil, nil 93 } 94 return value, nil 95 } 96 97 func (p *FlatmapParser) fromFlatmapObject(prefix string, tys map[string]cty.Type) (map[string]interface{}, error) { 98 values := make(map[string]interface{}) 99 for name, ty := range tys { 100 inAttributes := false 101 attributeName := "" 102 for k := range p.attributes { 103 if k == prefix+name { 104 attributeName = k 105 inAttributes = true 106 break 107 } 108 if k == name { 109 attributeName = k 110 inAttributes = true 111 break 112 } 113 114 if strings.HasPrefix(k, prefix+name+".") { 115 attributeName = k 116 inAttributes = true 117 break 118 } 119 lastAttribute := (prefix + name)[len(prefix):] 120 if lastAttribute == k { 121 attributeName = k 122 inAttributes = true 123 break 124 } 125 } 126 127 if _, exist := p.attributes[prefix+name+".#"]; exist { 128 attributeName = prefix + name + ".#" 129 inAttributes = true 130 } 131 132 if _, exist := p.attributes[prefix+name+".%"]; exist { 133 attributeName = prefix + name + ".%" 134 inAttributes = true 135 } 136 137 if !inAttributes { 138 continue 139 } 140 if p.isAttributeIgnored(prefix + name) { 141 continue 142 } 143 value, err := p.fromFlatmapValue(prefix+name, ty) 144 if err != nil { 145 return nil, err 146 } 147 if p.isValueAllowed(value, attributeName) { 148 values[name] = value 149 } 150 } 151 if len(values) == 0 { 152 return nil, nil 153 } 154 return values, nil 155 } 156 157 func (p *FlatmapParser) fromFlatmapTuple(prefix string, tys []cty.Type) ([]interface{}, error) { 158 // if the container is unknown, there is no count string 159 listName := strings.TrimRight(prefix, ".") 160 if p.attributes[listName] == hcl2shim.UnknownVariableValue { 161 return nil, nil 162 } 163 164 countStr, exists := p.attributes[prefix+"#"] 165 if !exists { 166 return nil, nil 167 } 168 if countStr == hcl2shim.UnknownVariableValue { 169 return nil, nil 170 } 171 172 count, err := strconv.Atoi(countStr) 173 if err != nil { 174 return nil, fmt.Errorf("invalid count value for %q in state: %s", prefix, err) 175 } 176 if count != len(tys) { 177 return nil, fmt.Errorf("wrong number of values for %q in state: got %d, but need %d", prefix, count, len(tys)) 178 } 179 180 var values []interface{} 181 for i, ty := range tys { 182 key := prefix + strconv.Itoa(i) 183 value, err := p.fromFlatmapValue(key, ty) 184 if err != nil { 185 return nil, err 186 } 187 if p.isValueAllowed(value, prefix) { 188 values = append(values, value) 189 } 190 } 191 if len(values) == 0 { 192 return nil, nil 193 } 194 return values, nil 195 } 196 197 func (p *FlatmapParser) fromFlatmapMap(prefix string, ty cty.Type) (map[string]interface{}, error) { 198 // if the container is unknown, there is no count string 199 listName := strings.TrimRight(prefix, ".") 200 if p.attributes[listName] == hcl2shim.UnknownVariableValue { 201 return nil, nil 202 } 203 204 // We actually don't really care about the "count" of a map for our 205 // purposes here, but we do need to check if it _exists_ in order to 206 // recognize the difference between null (not set at all) and empty. 207 strCount, exists := p.attributes[prefix+"%"] 208 if !exists { 209 return nil, nil 210 } 211 if strCount == hcl2shim.UnknownVariableValue { 212 return nil, nil 213 } 214 215 values := make(map[string]interface{}) 216 for fullKey := range p.attributes { 217 if !strings.HasPrefix(fullKey, prefix) { 218 continue 219 } 220 221 // The flatmap format doesn't allow us to distinguish between keys 222 // that contain periods and nested objects, so by convention a 223 // map is only ever of primitive type in flatmap, and we just assume 224 // that the remainder of the raw key (dots and all) is the key we 225 // want in the result value. 226 key := fullKey[len(prefix):] 227 if key == "%" { 228 // Ignore the "count" key 229 continue 230 } 231 if p.isAttributeIgnored(fullKey) { 232 continue 233 } 234 value, err := p.fromFlatmapValue(fullKey, ty) 235 if err != nil { 236 return nil, err 237 } 238 if p.isValueAllowed(value, prefix) { 239 values[key] = value 240 } 241 } 242 if len(values) == 0 { 243 return nil, nil 244 } 245 return values, nil 246 } 247 248 func (p *FlatmapParser) fromFlatmapList(prefix string, ty cty.Type) ([]interface{}, error) { 249 // if the container is unknown, there is no count string 250 listName := strings.TrimRight(prefix, ".") 251 if p.attributes[listName] == hcl2shim.UnknownVariableValue { 252 return nil, nil 253 } 254 255 countStr, exists := p.attributes[prefix+"#"] 256 if !exists { 257 return nil, nil 258 } 259 if countStr == hcl2shim.UnknownVariableValue { 260 return nil, nil 261 } 262 263 count, err := strconv.Atoi(countStr) 264 if err != nil { 265 return nil, fmt.Errorf("invalid count value for %q in state: %s", prefix, err) 266 } 267 268 if count == 0 { 269 return nil, nil 270 } 271 272 var values []interface{} 273 for i := 0; i < count; i++ { 274 key := prefix + strconv.Itoa(i) 275 276 if p.isAttributeIgnored(key) { 277 continue 278 } 279 280 value, err := p.fromFlatmapValue(key, ty) 281 if err != nil { 282 return nil, err 283 } 284 if p.isValueAllowed(value, prefix) { 285 values = append(values, value) 286 } 287 } 288 return values, nil 289 } 290 291 func (p *FlatmapParser) fromFlatmapSet(prefix string, ty cty.Type) ([]interface{}, error) { 292 // if the container is unknown, there is no count string 293 listName := strings.TrimRight(prefix, ".") 294 if p.attributes[listName] == hcl2shim.UnknownVariableValue { 295 return nil, nil 296 } 297 298 strCount, exists := p.attributes[prefix+"#"] 299 if !exists { 300 return nil, nil 301 } 302 if strCount == hcl2shim.UnknownVariableValue { 303 return nil, nil 304 } 305 306 // Keep track of keys we've seen, se we don't add the same set value 307 // multiple times. The cty.Set will normally de-duplicate values, but we may 308 // have unknown values that would not show as equivalent. 309 seen := map[string]bool{} 310 311 var values []interface{} 312 for fullKey := range p.attributes { 313 if !strings.HasPrefix(fullKey, prefix) { 314 continue 315 } 316 317 subKey := fullKey[len(prefix):] 318 if subKey == "#" { 319 // Ignore the "count" key 320 continue 321 } 322 323 key := fullKey 324 325 if p.isAttributeIgnored(fullKey) { 326 continue 327 } 328 329 if dot := strings.IndexByte(subKey, '.'); dot != -1 { 330 key = fullKey[:dot+len(prefix)] 331 } 332 333 if seen[key] { 334 continue 335 } 336 seen[key] = true 337 338 // The flatmap format doesn't allow us to distinguish between keys 339 // that contain periods and nested objects, so by convention a 340 // map is only ever of primitive type in flatmap, and we just assume 341 // that the remainder of the raw key (dots and all) is the key we 342 // want in the result value. 343 344 value, err := p.fromFlatmapValue(key, ty) 345 if err != nil { 346 return nil, err 347 } 348 if p.isValueAllowed(value, prefix) { 349 values = append(values, value) 350 } 351 } 352 if len(values) == 0 { 353 return nil, nil 354 } 355 return values, nil 356 } 357 358 func (p *FlatmapParser) isAttributeIgnored(name string) bool { 359 ignored := false 360 for _, pattern := range p.ignoreKeys { 361 if pattern.MatchString(name) { 362 ignored = true 363 break 364 } 365 } 366 return ignored 367 } 368 369 func (p *FlatmapParser) isValueAllowed(value interface{}, prefix string) bool { 370 if !reflect.ValueOf(value).IsValid() { 371 return false 372 } 373 switch reflect.ValueOf(value).Kind() { 374 case reflect.Slice: 375 if reflect.ValueOf(value).Len() == 0 { 376 return false 377 } 378 379 for i := 0; i < reflect.ValueOf(value).Len(); i++ { 380 if !reflect.ValueOf(value).Index(i).IsZero() { 381 return true 382 } 383 } 384 case reflect.Map: 385 if reflect.ValueOf(value).Len() == 0 { 386 return false 387 } 388 } 389 if !reflect.ValueOf(value).IsZero() { 390 return true 391 } 392 393 allowed := false 394 for _, pattern := range p.allowEmptyValues { 395 if pattern.MatchString(prefix) { 396 allowed = true 397 break 398 } 399 } 400 return allowed 401 }