Explorar el Código

Add filterServices wildcard support to AllocFilter

This is done by overloading the FilterStartsWith operator. This is a
little ugly, but achieves the functionality goal. The other option was
to introduce a new operator like FilterContainsPrefix. This felt clunky,
but may actually be the right choice as we continue to work through this
API.
Michael Dresser hace 4 años
padre
commit
6542140029

+ 27 - 8
pkg/kubecost/allocationfilter.go

@@ -39,10 +39,21 @@ type FilterOp string
 // If you add a FilterOp, MAKE SURE TO UPDATE ALL FILTER IMPLEMENTATIONS! Go
 // does not enforce exhaustive pattern matching on "enum" types.
 const (
-	FilterEquals     FilterOp = "equals"
-	FilterNotEquals           = "notequals"
-	FilterContains            = "contains"
-	FilterStartsWith          = "startswith"
+	// FilterEquals is the equality operator
+	// "kube-system" FilterEquals "kube-system" = true
+	// "kube-syste" FilterEquals "kube-system" = false
+	FilterEquals FilterOp = "equals"
+	// FilterNotEquals is the inequality operator
+	FilterNotEquals = "notequals"
+	// FilterContains is an array/slice membership operator
+	// ["a", "b", "c"] FilterContains "a" = true
+	FilterContains = "contains"
+	// FilterStartsWith matches strings with the given prefix.
+	// "kube-system" StartsWith "kube" = true
+	//
+	// When comparing with a field represented by an array/slice, this is like
+	// applying FilterContains to every element of the slice.
+	FilterStartsWith = "startswith"
 )
 
 // AllocationFilter represents anything that can be used to filter an
@@ -203,13 +214,21 @@ func (filter AllocationFilterCondition) Matches(a *Allocation) bool {
 		// asking for "__unallocated__" won't have a wildcard and unallocated
 		// properties are the empty string.
 
-		s, ok := valueToCompare.(string)
-		if !ok {
-			log.Warnf("Allocation Filter: invalid 'startswith' call for non-string filter value")
+		switch v := valueToCompare.(type) {
+		case string:
+			return strings.HasPrefix(v, filter.Value)
+		case []string:
+			for _, s := range v {
+				if strings.HasPrefix(s, filter.Value) {
+					return true
+				}
+			}
+			return false
+		default:
+			log.Warnf("Allocation Filter: invalid 'startswith' call for field with unsupported type")
 			return false
 		}
 
-		return strings.HasPrefix(s, filter.Value)
 	default:
 		log.Errorf("Allocation Filter: Unhandled filter op. This is a filter implementation error and requires immediate patching. Op: %s", filter.Op)
 		return false

+ 30 - 0
pkg/kubecost/allocationfilter_test.go

@@ -381,6 +381,36 @@ func Test_AllocationFilterCondition_Matches(t *testing.T) {
 
 			expected: false,
 		},
+		{
+			name: `services startswith -> true`,
+			a: &Allocation{
+				Properties: &AllocationProperties{
+					Services: []string{"serv1", "serv2"},
+				},
+			},
+			filter: AllocationFilterCondition{
+				Field: FilterServices,
+				Op:    FilterStartsWith,
+				Value: "serv",
+			},
+
+			expected: true,
+		},
+		{
+			name: `services startswith -> false`,
+			a: &Allocation{
+				Properties: &AllocationProperties{
+					Services: []string{"foo", "bar"},
+				},
+			},
+			filter: AllocationFilterCondition{
+				Field: FilterServices,
+				Op:    FilterStartsWith,
+				Value: "serv",
+			},
+
+			expected: false,
+		},
 		{
 			name: `services contains unallocated -> false`,
 			a: &Allocation{

+ 10 - 7
pkg/util/filterutil/allocationfilters.go

@@ -210,13 +210,16 @@ func AllocationFilterFromParamsV1(
 	}
 	for _, filterValue := range qp.GetList("filterServices", ",") {
 		// TODO: wildcard support
-		servicesFilter.Filters = append(servicesFilter.Filters,
-			kubecost.AllocationFilterCondition{
-				Field: kubecost.FilterServices,
-				Op:    kubecost.FilterContains,
-				Value: filterValue,
-			},
-		)
+		filterValue, wildcard := parseWildcardEnd(filterValue)
+		subFilter := kubecost.AllocationFilterCondition{
+			Field: kubecost.FilterServices,
+			Op:    kubecost.FilterContains,
+			Value: filterValue,
+		}
+		if wildcard {
+			subFilter.Op = kubecost.FilterStartsWith
+		}
+		servicesFilter.Filters = append(servicesFilter.Filters, subFilter)
 	}
 	filter.Filters = append(filter.Filters, servicesFilter)