github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/filter_util.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  	"regexp"
    10  	"strings"
    11  
    12  	"github.com/araddon/dateparse"
    13  
    14  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    15  )
    16  
    17  var (
    18  	// supportedFilters lists the filters currently supported in the engine, available to users
    19  	supportedFilters = []string{
    20  		types.FilterNameRegex,
    21  		types.FilterDate,
    22  		types.FilterIp,
    23  		types.FilterLatest,
    24  		types.FilterEarliest,
    25  		types.FilterParent,
    26  		types.FilterParentId,
    27  	}
    28  
    29  	// SupportedMetadataTypes are the metadata types recognized so far. "NONE" is the same as ""
    30  	SupportedMetadataTypes = []string{"NONE", "STRING", "NUMBER", "BOOLEAN", "DATETIME"}
    31  )
    32  
    33  // MetadataDef defines a metadata structure
    34  type MetadataDef struct {
    35  	Key      string      // name of the field (addressed as metadata:key)
    36  	Type     string      // Type of the field (one of SupportedMetadataTypes)
    37  	Value    interface{} // contents of the metadata field
    38  	IsSystem bool        // if true, the metadata field will be addressed as metadata@SYSTEM:key
    39  }
    40  
    41  // matchResult stores the result of a condition evaluation
    42  // Used to build the human readable description of the engine operations
    43  type matchResult struct {
    44  	Name       string
    45  	Type       string
    46  	Definition string
    47  	Result     bool
    48  }
    49  
    50  // FilterDef defines all the criteria used by the engine to retrieve data
    51  type FilterDef struct {
    52  	// A collection of filters (with keys from SupportedFilters)
    53  	Filters map[string]string
    54  
    55  	// A list of metadata filters
    56  	Metadata []MetadataDef
    57  
    58  	// If true, the query will include metadata fields and search for exact values.
    59  	// Otherwise, the engine will collect metadata fields and search by regexp
    60  	UseMetadataApiFilter bool
    61  }
    62  
    63  // NewFilterDef builds a new filter definition
    64  func NewFilterDef() *FilterDef {
    65  	return &FilterDef{
    66  		Filters:  make(map[string]string),
    67  		Metadata: nil,
    68  	}
    69  }
    70  
    71  // validateMetadataType checks that a metadata type is within supported types
    72  func validateMetadataType(valueType string) error {
    73  	typeSupported := false
    74  	for _, supported := range SupportedMetadataTypes {
    75  		if valueType == supported {
    76  			typeSupported = true
    77  		}
    78  	}
    79  	if !typeSupported {
    80  		return fmt.Errorf("metadata type '%s' not supported", valueType)
    81  	}
    82  	return nil
    83  }
    84  
    85  // AddFilter adds a new filter to the criteria
    86  func (fd *FilterDef) AddFilter(key, value string) error {
    87  	for _, allowed := range supportedFilters {
    88  		if key == allowed {
    89  			fd.Filters[key] = value
    90  			return nil
    91  		}
    92  	}
    93  	return fmt.Errorf("filter '%s' not supported", key)
    94  }
    95  
    96  // AddMetadataFilter adds a new metadata filter to an existing set
    97  func (fd *FilterDef) AddMetadataFilter(key, value, valueType string, isSystem, useMetadataApiFilter bool) error {
    98  	if valueType == "" {
    99  		valueType = "NONE"
   100  		useMetadataApiFilter = false
   101  	}
   102  	if useMetadataApiFilter {
   103  		fd.UseMetadataApiFilter = true
   104  	}
   105  	err := validateMetadataType(valueType)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	fd.Metadata = append(fd.Metadata, MetadataDef{
   110  		Key:      key,
   111  		Value:    value,
   112  		IsSystem: isSystem,
   113  		Type:     valueType,
   114  	})
   115  	return nil
   116  }
   117  
   118  // stringToBool converts a string to a bool
   119  // The following values are recognized as TRUE:
   120  //
   121  //	t, true, y, yes, ok
   122  func stringToBool(s string) bool {
   123  	switch strings.ToLower(s) {
   124  	case "t", "true", "y", "yes", "ok":
   125  		return true
   126  	default:
   127  		return false
   128  	}
   129  }
   130  
   131  // compareDate will get a date from string `got`, and will parse `wanted`
   132  // for an expression starting with an operator (>, <, >=, <=, ==) followed by a date
   133  // (many formats supported, but 'YYYY-MM-DD[ hh:mm[:ss[.nnnZ]]' preferred)
   134  // For example:
   135  // got:    "2020-03-09T09:50:51.500Z"
   136  // wanted: ">= 2020-03-08"
   137  // result: true
   138  // got:    "2020-03-09T09:50:51.500Z"
   139  // wanted: "< 02-mar-2020"
   140  // result: false
   141  // See https://github.com/araddon/dateparse for more info
   142  func compareDate(wanted, got string) (bool, error) {
   143  
   144  	reExpression := regexp.MustCompile(`(>=|<=|==|<|=|>)\s*(.+)`)
   145  
   146  	expList := reExpression.FindAllStringSubmatch(wanted, -1)
   147  	if len(expList) == 0 || len(expList[0]) == 0 {
   148  		return false, fmt.Errorf("expression not found in '%s'", wanted)
   149  	}
   150  
   151  	operator := expList[0][1]
   152  	wantedTime, err := dateparse.ParseStrict(expList[0][2])
   153  	if err != nil {
   154  		return false, err
   155  	}
   156  
   157  	gotTime, err := dateparse.ParseStrict(got)
   158  	if err != nil {
   159  		return false, err
   160  	}
   161  
   162  	wantedSeconds := wantedTime.UnixNano()
   163  	gotSeconds := gotTime.UnixNano()
   164  
   165  	switch operator {
   166  	case "=", "==":
   167  		return gotSeconds == wantedSeconds, nil
   168  	case ">":
   169  		return gotSeconds > wantedSeconds, nil
   170  	case ">=":
   171  		return gotSeconds >= wantedSeconds, nil
   172  	case "<=":
   173  		return gotSeconds <= wantedSeconds, nil
   174  	case "<":
   175  		return gotSeconds < wantedSeconds, nil
   176  	default:
   177  		return false, fmt.Errorf("unsupported operator '%s'", operator)
   178  	}
   179  }
   180  
   181  // conditionText provides a human readable string of searching criteria
   182  func conditionText(criteria *FilterDef) string {
   183  	result := "criteria: "
   184  
   185  	for k, v := range criteria.Filters {
   186  		result += fmt.Sprintf(`("%s" -> "%s") `, k, v)
   187  	}
   188  	for _, m := range criteria.Metadata {
   189  		marker := "metadata"
   190  		if criteria.UseMetadataApiFilter {
   191  			marker = "metadataApi"
   192  		}
   193  		result += fmt.Sprintf(`%s("%s" -> "%s") `, marker, m.Key, m.Value)
   194  	}
   195  	return result
   196  }
   197  
   198  // matchesToText provides a human readable string of search operations results
   199  func matchesToText(matches []matchResult) string {
   200  	result := ""
   201  	for _, item := range matches {
   202  		result += fmt.Sprintf("name: %s; type: %s definition: %s; result: %v\n", item.Name, item.Type, item.Definition, item.Result)
   203  	}
   204  	return result
   205  }