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  }