diff --git a/engine/labels/labels.go b/engine/labels/labels.go index cdd8dbaa..c0174323 100644 --- a/engine/labels/labels.go +++ b/engine/labels/labels.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/PaesslerAG/gval" + "github.com/hashicorp/go-version" ) const ( @@ -204,7 +205,7 @@ func getBooleanExpression(expr string, compareLabels map[string][]string) string } if labelVals, ok := compareLabels[exprLabelKey]; !ok { replaceMap[toReplace] = "false" - } else if exprLabelVal != "" && !contains(exprLabelVal, labelVals) { + } else if exprLabelVal != "" && !matchesAny(exprLabelVal, labelVals) { replaceMap[toReplace] = "false" } else { replaceMap[toReplace] = "true" @@ -235,11 +236,58 @@ func tokenize(expr string) []string { return tokens } -func contains(elem string, items []string) bool { +func matchesAny(elem string, items []string) bool { for _, item := range items { - if item == elem { + if labelValueMatches(item, elem) { return true } } return false } + +// labelValueMatches returns true when candidate matches with matchWith +// label value is divided into two parts - name and version +// version is absolute version or a range denoted by + or - +// returns true when names of values are equal and the version of +// candidate falls within the version range of matchWith +func labelValueMatches(matchWith string, candidate string) bool { + versionRegex := regexp.MustCompile(`(\d(?:[\d\.]*\d)?)([\+-])?$`) + mMatch := versionRegex.FindStringSubmatch(matchWith) + cMatch := versionRegex.FindStringSubmatch(candidate) + if len(mMatch) != 3 { + return matchWith == candidate + } + mName, mVersion, mVersionRangeSymbol := + versionRegex.ReplaceAllString(matchWith, ""), mMatch[1], mMatch[2] + if len(cMatch) != 3 { + // when no version on candidate, match for any version + return mName == candidate + } + cName, cVersion := + versionRegex.ReplaceAllString(candidate, ""), cMatch[1] + if mName != cName { + return false + } + if mVersion == "" { + return mVersion == cVersion + } + if cVersion == "" { + return true + } + cSemver, err := version.NewSemver(cVersion) + if err != nil { + return cVersion == mVersion + } + mSemver, err := version.NewSemver(mVersion) + if err != nil { + return cVersion == mVersion + } + switch mVersionRangeSymbol { + case "+": + return cSemver.GreaterThanOrEqual(mSemver) + case "-": + return mSemver.GreaterThanOrEqual(cSemver) + default: + return cSemver.Equal(mSemver) + } +} diff --git a/engine/labels/labels_test.go b/engine/labels/labels_test.go index 6bf526d4..8ce92560 100644 --- a/engine/labels/labels_test.go +++ b/engine/labels/labels_test.go @@ -336,3 +336,104 @@ func Test_ruleSelector_Matches(t *testing.T) { }) } } + +func Test_labelValueMatches(t *testing.T) { + tests := []struct { + name string + candidate string + matchWith string + want bool + }{ + { + name: "no version range test", + candidate: "eap", + matchWith: "eap", + want: true, + }, + { + name: "name mismatch test", + candidate: "eap", + matchWith: "javaee", + want: false, + }, + { + name: "absolute version test", + candidate: "eap6", + matchWith: "eap6", + want: true, + }, + { + name: "version range test for '+'", + candidate: "eap6", + matchWith: "eap5+", + want: true, + }, + { + name: "version range test for '+'", + candidate: "eap5", + matchWith: "eap5+", + want: true, + }, + { + name: "version range test for '-'", + candidate: "eap7", + matchWith: "eap8-", + want: true, + }, + { + name: "version range negative test for '-'", + candidate: "eap9", + matchWith: "eap8-", + want: false, + }, + { + name: "version range negative test for '+'", + candidate: "eap7", + matchWith: "eap8+", + want: false, + }, + { + name: "complex value version range test", + candidate: "Golang Version", + matchWith: "Golang Version11+", + want: true, + }, + { + name: "match any version test", + candidate: "eap", + matchWith: "eap6+", + want: true, + }, + { + name: "match any version test negative", + candidate: "eap6", + matchWith: "eap", + want: false, + }, + { + name: "float value absolute match", + candidate: "hibernate5.1", + matchWith: "hibernate5.1", + want: true, + }, + { + name: "float value range symbol '+' match", + candidate: "hibernate5.2", + matchWith: "hibernate5.1+", + want: true, + }, + { + name: "float value range symbol '+' negative match", + candidate: "hibernate5.0.12", + matchWith: "hibernate5.1+", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := labelValueMatches(tt.matchWith, tt.candidate); got != tt.want { + t.Errorf("versionRangeMatches() = %v, want %v", got, tt.want) + } + }) + } +}