github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/filter_helpers.go (about) 1 package govcd 2 3 /* 4 * Copyright 2020 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. 5 */ 6 7 import ( 8 "fmt" 9 "os" 10 "regexp" 11 "strings" 12 "time" 13 14 "github.com/vmware/go-vcloud-director/v2/types/v56" 15 "github.com/vmware/go-vcloud-director/v2/util" 16 ) 17 18 // This file contains functions that help create tests for filtering. 19 // It is not in the '*_test.go' namespace because we want to use these functions from tests in other packages. 20 // All exported functions from this file have the prefix "Helper" 21 // 22 // Moreover, this file is not in a separate package for the following reasons: 23 // * getExistingMedia is private 24 // * getMetadata is private 25 // * the 'client' component in all entity objects is private 26 // * the tests that are now in filter_engine_test.go would need to go in a separate package, with consequent 27 // need for configuration file parser duplication. 28 29 type StringMap map[string]string 30 31 type DateItem struct { 32 Name string 33 Date string 34 Entity interface{} 35 EntityType string 36 } 37 38 // FilterMatch contains a filter, the name of the item that is expected to match, and the item itself 39 type FilterMatch struct { 40 Criteria *FilterDef 41 ExpectedName string 42 Entity interface{} 43 EntityType string 44 } 45 46 type VappTemplateData struct { 47 Name string 48 ItemCreationDate string 49 VappTemplateCreationDate string 50 Metadata StringMap 51 Created bool 52 } 53 54 // retrievedMetadataTypes maps the internal value of metadata type with the 55 // string needed when searching for a metadata field in the API 56 var retrievedMetadataTypes = map[string]string{ 57 "MetadataBooleanValue": "BOOLEAN", 58 "MetadataStringValue": "STRING", 59 "MetadataNumberValue": "NUMBER", 60 "MetadataDateTimeValue": "STRING", // values for DATETIME can't be passed as such in a query when the date contains colons. 61 } 62 63 // HelperMakeFiltersFromEdgeGateways looks at the existing edge gateways and creates a set of criteria to retrieve each of them 64 func HelperMakeFiltersFromEdgeGateways(vdc *Vdc) ([]FilterMatch, error) { 65 egwList, err := vdc.QueryEdgeGatewayList() 66 if err != nil { 67 return nil, err 68 } 69 70 if len(egwList) == 0 { 71 return []FilterMatch{}, nil 72 } 73 var filters = make([]FilterMatch, len(egwList)) 74 for i, egw := range egwList { 75 76 filter := NewFilterDef() 77 err = filter.AddFilter(types.FilterNameRegex, strToRegex(egw.Name)) 78 if err != nil { 79 return nil, err 80 } 81 filters[i] = FilterMatch{filter, egw.Name, QueryEdgeGateway(*egw), "QueryEdgeGateway"} 82 } 83 return filters, nil 84 } 85 86 // HelperMakeFiltersFromNetworks looks at the existing networks and creates a set of criteria to retrieve each of them 87 func HelperMakeFiltersFromNetworks(vdc *Vdc) ([]FilterMatch, error) { 88 netList, err := vdc.GetNetworkList() 89 if err != nil { 90 return nil, err 91 } 92 var filters = make([]FilterMatch, len(netList)) 93 for i, net := range netList { 94 95 localizedItem := QueryOrgVdcNetwork(*net) 96 qItem := QueryItem(localizedItem) 97 filter, _, err := queryItemToFilter(qItem, "QueryOrgVdcNetwork") 98 if err != nil { 99 return nil, err 100 } 101 102 filter, err = vdc.client.metadataToFilter(net.HREF, net.Name, filter) 103 if err != nil { 104 return nil, err 105 } 106 filters[i] = FilterMatch{filter, net.Name, localizedItem, "QueryOrgVdcNetwork"} 107 } 108 return filters, nil 109 } 110 111 // makeDateFilter creates date filters from a set of date records 112 // If there is more than one item, it creates an 'earliest' and 'latest' filter 113 func makeDateFilter(items []DateItem) ([]FilterMatch, error) { 114 var filters []FilterMatch 115 116 if len(items) == 0 { 117 return filters, nil 118 } 119 entityType := items[0].EntityType 120 if len(items) == 1 { 121 filter := NewFilterDef() 122 err := filter.AddFilter(types.FilterDate, "=="+items[0].Date) 123 filters = append(filters, FilterMatch{filter, items[0].Name, items[0].Entity, entityType}) 124 return filters, err 125 } 126 earliestDate := time.Now().AddDate(100, 0, 0).String() 127 latestDate := "1970-01-01 00:00:00" 128 earliestName := "" 129 latestName := "" 130 var earliestEntity interface{} 131 var latestEntity interface{} 132 earliestFound := false 133 latestFound := false 134 for _, item := range items { 135 greater, err := compareDate(">"+latestDate, item.Date) 136 if err != nil { 137 return nil, err 138 } 139 if greater { 140 latestDate = item.Date 141 latestName = item.Name 142 latestEntity = item.Entity 143 latestFound = true 144 } 145 greater, err = compareDate("<"+earliestDate, item.Date) 146 if err != nil { 147 return nil, err 148 } 149 if greater { 150 earliestDate = item.Date 151 earliestName = item.Name 152 earliestEntity = item.Entity 153 earliestFound = true 154 } 155 exactFilter := NewFilterDef() 156 err = exactFilter.AddFilter(types.FilterDate, "=="+item.Date) 157 if err != nil { 158 return nil, fmt.Errorf("error adding filter '%s' '%s': %s", types.FilterDate, "=="+item.Date, err) 159 } 160 filters = append(filters, FilterMatch{exactFilter, item.Name, item.Entity, item.EntityType}) 161 } 162 163 if earliestFound && latestFound && earliestDate != latestDate { 164 earlyFilter := NewFilterDef() 165 err := earlyFilter.AddFilter(types.FilterDate, "<"+latestDate) 166 if err != nil { 167 return nil, err 168 } 169 err = earlyFilter.AddFilter(types.FilterEarliest, "true") 170 if err != nil { 171 return nil, err 172 } 173 174 lateFilter := NewFilterDef() 175 err = lateFilter.AddFilter(types.FilterDate, ">"+earliestDate) 176 if err != nil { 177 return nil, err 178 } 179 err = lateFilter.AddFilter(types.FilterLatest, "true") 180 if err != nil { 181 return nil, err 182 } 183 184 filters = append(filters, FilterMatch{earlyFilter, earliestName, earliestEntity, entityType}) 185 filters = append(filters, FilterMatch{lateFilter, latestName, latestEntity, entityType}) 186 } 187 188 return filters, nil 189 } 190 191 func HelperMakeFiltersFromCatalogs(org *AdminOrg) ([]FilterMatch, error) { 192 catalogs, err := org.QueryCatalogList() 193 if err != nil { 194 return []FilterMatch{}, err 195 } 196 197 var filters []FilterMatch 198 199 var dateInfo []DateItem 200 for _, cat := range catalogs { 201 localizedItem := QueryCatalog(*cat) 202 qItem := QueryItem(localizedItem) 203 filter, dInfo, err := queryItemToFilter(qItem, "QueryCatalog") 204 if err != nil { 205 return nil, err 206 } 207 208 dateInfo = append(dateInfo, dInfo...) 209 210 filter, err = org.client.metadataToFilter(cat.HREF, cat.Name, filter) 211 if err != nil { 212 return nil, err 213 } 214 215 filters = append(filters, FilterMatch{filter, cat.Name, localizedItem, "QueryCatalog"}) 216 } 217 dateFilter, err := makeDateFilter(dateInfo) 218 if err != nil { 219 return []FilterMatch{}, err 220 } 221 if len(dateFilter) > 0 { 222 filters = append(filters, dateFilter...) 223 } 224 return filters, nil 225 } 226 227 func HelperMakeFiltersFromMedia(vdc *Vdc, catalogName string) ([]FilterMatch, error) { 228 var filters []FilterMatch 229 items, err := getExistingMedia(vdc) 230 if err != nil { 231 return filters, err 232 } 233 var dateInfo []DateItem 234 for _, item := range items { 235 236 if item.CatalogName != catalogName { 237 continue 238 } 239 localizedItem := QueryMedia(*item) 240 qItem := QueryItem(localizedItem) 241 filter, dInfo, err := queryItemToFilter(qItem, "QueryMedia") 242 if err != nil { 243 return nil, err 244 } 245 246 dateInfo = append(dateInfo, dInfo...) 247 248 filter, err = vdc.client.metadataToFilter(item.HREF, item.Name, filter) 249 if err != nil { 250 return nil, err 251 } 252 253 filters = append(filters, FilterMatch{filter, item.Name, localizedItem, "QueryMedia"}) 254 } 255 dateFilter, err := makeDateFilter(dateInfo) 256 if err != nil { 257 return nil, err 258 } 259 if len(dateFilter) > 0 { 260 filters = append(filters, dateFilter...) 261 } 262 return filters, nil 263 } 264 265 func queryItemToFilter(item QueryItem, entityType string) (*FilterDef, []DateItem, error) { 266 267 var dateInfo []DateItem 268 filter := NewFilterDef() 269 err := filter.AddFilter(types.FilterNameRegex, strToRegex(item.GetName())) 270 if err != nil { 271 return nil, nil, err 272 } 273 274 if item.GetIp() != "" { 275 err = filter.AddFilter(types.FilterIp, ipToRegex(item.GetIp())) 276 if err != nil { 277 return nil, nil, err 278 } 279 } 280 if item.GetDate() != "" { 281 dateInfo = append(dateInfo, DateItem{item.GetName(), item.GetDate(), item, entityType}) 282 } 283 return filter, dateInfo, nil 284 } 285 286 func HelperMakeFiltersFromCatalogItem(catalog *Catalog) ([]FilterMatch, error) { 287 var filters []FilterMatch 288 items, err := catalog.QueryCatalogItemList() 289 if err != nil { 290 return filters, err 291 } 292 var dateInfo []DateItem 293 for _, item := range items { 294 295 localItem := QueryCatalogItem(*item) 296 qItem := QueryItem(localItem) 297 298 filter, dInfo, err := queryItemToFilter(qItem, "QueryCatalogItem") 299 if err != nil { 300 return nil, err 301 } 302 303 dateInfo = append(dateInfo, dInfo...) 304 305 filter, err = catalog.client.metadataToFilter(item.HREF, item.Name, filter) 306 if err != nil { 307 return nil, err 308 } 309 310 filters = append(filters, FilterMatch{filter, item.Name, localItem, "QueryCatalogItem"}) 311 } 312 dateFilter, err := makeDateFilter(dateInfo) 313 if err != nil { 314 return nil, err 315 } 316 if len(dateFilter) > 0 { 317 filters = append(filters, dateFilter...) 318 } 319 return filters, nil 320 } 321 322 func HelperMakeFiltersFromVappTemplate(catalog *Catalog) ([]FilterMatch, error) { 323 var filters []FilterMatch 324 items, err := catalog.QueryVappTemplateList() 325 if err != nil { 326 return filters, err 327 } 328 var dateInfo []DateItem 329 for _, item := range items { 330 331 localItem := QueryVAppTemplate(*item) 332 qItem := QueryItem(localItem) 333 334 filter, dInfo, err := queryItemToFilter(qItem, "QueryVAppTemplate") 335 if err != nil { 336 return nil, err 337 } 338 339 dateInfo = append(dateInfo, dInfo...) 340 341 filter, err = catalog.client.metadataToFilter(item.HREF, item.Name, filter) 342 if err != nil { 343 return nil, err 344 } 345 346 filters = append(filters, FilterMatch{filter, item.Name, localItem, "QueryVAppTemplate"}) 347 } 348 dateFilter, err := makeDateFilter(dateInfo) 349 if err != nil { 350 return nil, err 351 } 352 if len(dateFilter) > 0 { 353 filters = append(filters, dateFilter...) 354 } 355 return filters, nil 356 } 357 358 // HelperCreateMultipleCatalogItems deploys several catalog items, as defined in requestData 359 // Returns a set of VappTemplateData with what was created. 360 // If the requested objects exist already, returns updated information about the existing items. 361 func HelperCreateMultipleCatalogItems(catalog *Catalog, requestData []VappTemplateData, verbose bool) ([]VappTemplateData, error) { 362 var data []VappTemplateData 363 ova := "../test-resources/test_vapp_template.ova" 364 _, err := os.Stat(ova) 365 if os.IsNotExist(err) { 366 return nil, fmt.Errorf("[HelperCreateMultipleCatalogItems] sample OVA %s not found", ova) 367 } 368 overallStart := time.Now() 369 for _, requested := range requestData { 370 name := requested.Name 371 372 var item *CatalogItem 373 var vappTemplate VAppTemplate 374 created := false 375 item, err := catalog.GetCatalogItemByName(name, false) 376 if err == nil { 377 // If the item already exists, we skip the creation, and just retrieve the vapp template 378 vappTemplate, err = item.GetVAppTemplate() 379 if err != nil { 380 return nil, fmt.Errorf("[HelperCreateMultipleCatalogItems] error retrieving vApp template from catalog item %s : %s", item.CatalogItem.Name, err) 381 } 382 } else { 383 384 start := time.Now() 385 if verbose { 386 fmt.Printf("%-55s %s ", start, name) 387 } 388 task, err := catalog.UploadOvf(ova, name, "test "+name, 10) 389 if err != nil { 390 return nil, fmt.Errorf("[HelperCreateMultipleCatalogItems] error uploading OVA: %s", err) 391 } 392 err = task.WaitTaskCompletion() 393 if err != nil { 394 return nil, fmt.Errorf("[HelperCreateMultipleCatalogItems] error completing task :%s", err) 395 } 396 item, err = catalog.GetCatalogItemByName(name, true) 397 if err != nil { 398 return nil, fmt.Errorf("[HelperCreateMultipleCatalogItems] error retrieving item %s: %s", name, err) 399 } 400 vappTemplate, err = item.GetVAppTemplate() 401 if err != nil { 402 return nil, fmt.Errorf("[HelperCreateMultipleCatalogItems] error retrieving vApp template: %s", err) 403 } 404 405 for k, v := range requested.Metadata { 406 _, err := vappTemplate.AddMetadata(k, v) 407 if err != nil { 408 return nil, fmt.Errorf("[HelperCreateMultipleCatalogItems], error adding metadata: %s", err) 409 } 410 } 411 duration := time.Since(start) 412 if verbose { 413 fmt.Printf("- elapsed: %s\n", duration) 414 } 415 416 created = true 417 } 418 data = append(data, VappTemplateData{ 419 Name: name, 420 ItemCreationDate: item.CatalogItem.DateCreated, 421 VappTemplateCreationDate: vappTemplate.VAppTemplate.DateCreated, 422 Metadata: requested.Metadata, 423 Created: created, 424 }) 425 } 426 overallDuration := time.Since(overallStart) 427 if verbose { 428 fmt.Printf("total elapsed: %s\n", overallDuration) 429 } 430 431 return data, nil 432 } 433 434 func HelperMakeFiltersFromOrgVdc(org *Org) ([]FilterMatch, error) { 435 var filters []FilterMatch 436 items, err := org.QueryOrgVdcList() 437 if err != nil { 438 return filters, err 439 } 440 for _, item := range items { 441 localItem := QueryOrgVdc(*item) 442 qItem := QueryItem(localItem) 443 444 filter, _, err := queryItemToFilter(qItem, "QueryOrgVdc") 445 if err != nil { 446 return nil, err 447 } 448 449 filter, err = org.client.metadataToFilter(item.HREF, item.Name, filter) 450 if err != nil { 451 return nil, err 452 } 453 454 filters = append(filters, FilterMatch{filter, item.Name, localItem, "QueryOrgVdc"}) 455 } 456 457 return filters, nil 458 } 459 460 // ipToRegex creates a regular expression that matches an IP without the last element 461 func ipToRegex(ip string) string { 462 elements := strings.Split(ip, ".") 463 result := "^" 464 for i := 0; i < len(elements)-1; i++ { 465 result += elements[i] + `\.` 466 } 467 return result 468 } 469 470 // strToRegex creates a regular expression that matches perfectly with the input query 471 func strToRegex(s string) string { 472 var result strings.Builder 473 var err error 474 _, err = result.WriteString("^") 475 if err != nil { 476 util.Logger.Printf("[DEBUG - strToRegex] error writing to string: %s", err) 477 } 478 for _, ch := range s { 479 if ch == '.' { 480 _, err = result.WriteString(fmt.Sprintf("\\%c", ch)) 481 } else { 482 _, err = result.WriteString(fmt.Sprintf("[%c]", ch)) 483 } 484 if err != nil { 485 util.Logger.Printf("[DEBUG - strToRegex] error writing to string: %s", err) 486 } 487 } 488 _, err = result.WriteString("$") 489 if err != nil { 490 util.Logger.Printf("[DEBUG - strToRegex] error writing to string: %s", err) 491 } 492 return result.String() 493 } 494 495 // guessMetadataType guesses the type of a metadata value from its contents 496 // If the value looks like a number, or a true/false value, the corresponding type is returned 497 // Otherwise, we assume it's a string. 498 // We do this because the API doesn't return the metadata type 499 // (it would if the field MetadataTypedValue.XsiType were defined as `xml:"type,attr"`, but then metadata updates would fail.) 500 func guessMetadataType(value string) string { 501 fType := "STRING" 502 reNumber := regexp.MustCompile(`^[0-9]+$`) 503 reBool := regexp.MustCompile(`^(?:true|false)$`) 504 if reNumber.MatchString(value) { 505 fType = "NUMBER" 506 } 507 if reBool.MatchString(value) { 508 fType = "BOOLEAN" 509 } 510 return fType 511 } 512 513 // metadataToFilter adds metadata elements to an existing filter 514 // href is the address of the entity for which we want to retrieve metadata 515 // filter is an existing filter to which we want to add metadata elements 516 // objectName is the name of the entity for which we want to retrieve metadata 517 func (client *Client) metadataToFilter(href, objectName string, filter *FilterDef) (*FilterDef, error) { 518 if filter == nil { 519 filter = &FilterDef{} 520 } 521 metadata, err := getMetadata(client, href, objectName) 522 if err == nil && metadata != nil && len(metadata.MetadataEntry) > 0 { 523 for _, md := range metadata.MetadataEntry { 524 isSystem := false 525 if md.Domain != nil && md.Domain.Domain == "SYSTEM" { 526 isSystem = true 527 } 528 var fType string 529 var ok bool 530 if md.TypedValue.XsiType == "" { 531 fType = guessMetadataType(md.TypedValue.Value) 532 } else { 533 fType, ok = retrievedMetadataTypes[md.TypedValue.XsiType] 534 if !ok { 535 fType = "STRING" 536 } 537 } 538 err = filter.AddMetadataFilter(md.Key, md.TypedValue.Value, fType, isSystem, false) 539 if err != nil { 540 return nil, err 541 } 542 } 543 } 544 return filter, nil 545 }