Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom maven comparator #1571

Merged
merged 22 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1e6f9fa
feat: add outline for new Java comparator
spiffcs Oct 24, 2023
3d96d24
feat: add initial version comparator
spiffcs Oct 24, 2023
363ecc4
feat: add boiler constraint implementation
spiffcs Oct 24, 2023
4975438
test: add version test cases
spiffcs Oct 25, 2023
525a815
test: add test cases for new comparator
spiffcs Oct 25, 2023
9d936ac
test: update tests with correct expectations
spiffcs Oct 25, 2023
0f94a69
chore: reorient compare logic to be consistent with other implementat…
spiffcs Oct 25, 2023
4d54c0a
fix: reorient name scheme around maven spec
spiffcs Oct 25, 2023
ad906d4
feat: update fuzzy constraint to test against request version first
spiffcs Oct 25, 2023
c269350
chore: update to newer test db with new labels and new yardstick
spiffcs Oct 26, 2023
9d12d7a
chore: try 3.11.6 which works locally
spiffcs Oct 26, 2023
748a4c2
chore: bump python to 3.11 for quality gate
spiffcs Oct 26, 2023
532cfbd
chore: reset pipeline version changes
spiffcs Oct 26, 2023
b5511c6
chore: bump yardstick version to bug fix
spiffcs Oct 27, 2023
294ca8a
chore: update submodule for vml with latest YS
spiffcs Oct 27, 2023
188a3db
chore: check if local grype can be used
spiffcs Oct 27, 2023
c0a8d88
chore: try busying penv cache
spiffcs Oct 27, 2023
a905c4f
troubleshoot with tmate
wagoodman Oct 27, 2023
ba3cf48
chore: change labels back to not update init
spiffcs Oct 27, 2023
38eb80f
remove tmate troubleshooting action
wagoodman Oct 27, 2023
74c0d0d
use grype config for quality testing
wagoodman Oct 27, 2023
a006367
chore: bump label for by-cve
spiffcs Oct 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/actions/bootstrap/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ inputs:
cache-key-prefix:
description: "Prefix all cache keys with this value"
required: true
default: "831180ac25"
default: "831180ac26"
build-cache-key-prefix:
description: "Prefix build cache key with this value"
required: true
Expand Down Expand Up @@ -40,9 +40,7 @@ runs:
path: |
test/quality/venv
test/quality/vulnerability-match-labels/venv
key: ${{ runner.os }}-python-${{ inputs.python-version }}-${{ hashFiles('**/test/quality/**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-python-${{ env.python-version }}-
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-python-${{ inputs.python-version }}-${{ hashFiles('**/test/quality/**/requirements.txt') }}

- name: Restore tool cache
id: tool-cache
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/validations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ jobs:
run: make quality
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GRYPE_BY_CVE: "true"

Integration-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ require (
modernc.org/sqlite v1.26.0 // indirect
)

require github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554
require (
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08
github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554
)

require (
cloud.google.com/go v0.110.2 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,8 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 h1:AevUBW4cc99rAF8q8vmddIP8qd/0J5s/UyltGbp66dg=
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08/go.mod h1:JOkBRrE1HvgTyjk6diFtNGgr8XJMtIfiBzkL5krqzVk=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
Expand Down
2 changes: 1 addition & 1 deletion grype/version/apk_constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type apkConstraint struct {

func newApkConstraint(raw string) (apkConstraint, error) {
if raw == "" {
// empy constraints are always satisfied
// empty constraints are always satisfied
return apkConstraint{}, nil
}

Expand Down
2 changes: 2 additions & 0 deletions grype/version/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ func GetConstraint(constStr string, format Format) (Constraint, error) {
return newSemanticConstraint(constStr)
case DebFormat:
return newDebConstraint(constStr)
case MavenFormat:
return newMavenConstraint(constStr)
case RpmFormat:
return newRpmConstraint(constStr)
case PythonFormat:
Expand Down
7 changes: 7 additions & 0 deletions grype/version/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
SemanticFormat
ApkFormat
DebFormat
MavenFormat
RpmFormat
PythonFormat
KBFormat
Expand All @@ -25,6 +26,7 @@ var formatStr = []string{
"Semantic",
"Apk",
"Deb",
"Maven",
"RPM",
"Python",
"KB",
Expand All @@ -36,6 +38,7 @@ var Formats = []Format{
SemanticFormat,
ApkFormat,
DebFormat,
MavenFormat,
RpmFormat,
PythonFormat,
KBFormat,
Expand All @@ -51,6 +54,8 @@ func ParseFormat(userStr string) Format {
return ApkFormat
case strings.ToLower(DebFormat.String()), "dpkg":
return DebFormat
case strings.ToLower(MavenFormat.String()), "maven":
return MavenFormat
case strings.ToLower(RpmFormat.String()), "rpm":
return RpmFormat
case strings.ToLower(PythonFormat.String()), "python":
Expand All @@ -72,6 +77,8 @@ func FormatFromPkgType(t pkg.Type) Format {
format = ApkFormat
case pkg.DebPkg:
format = DebFormat
case pkg.JavaPkg:
format = MavenFormat
case pkg.RpmPkg:
format = RpmFormat
case pkg.GemPkg:
Expand Down
8 changes: 8 additions & 0 deletions grype/version/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ func TestParseFormat(t *testing.T) {
input: "dpkg",
format: DebFormat,
},
{
input: "maven",
format: MavenFormat,
},
{
input: "gem",
format: GemFormat,
Expand Down Expand Up @@ -54,6 +58,10 @@ func TestFormatFromPkgType(t *testing.T) {
pkgType: pkg.DebPkg,
format: DebFormat,
},
{
pkgType: pkg.JavaPkg,
format: MavenFormat,
},
{
pkgType: pkg.GemPkg,
format: GemFormat,
Expand Down
13 changes: 13 additions & 0 deletions grype/version/fuzzy_constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ func (f *fuzzyConstraint) Satisfied(verObj *Version) (bool, error) {

version := verObj.Raw

// rebuild temp constraint based off of ver obj
if verObj.Format != UnknownFormat {
newConstaint, err := GetConstraint(f.rawPhrase, verObj.Format)
// check if constraint is not fuzzyConstraint
_, ok := newConstaint.(*fuzzyConstraint)
if err == nil && !ok {
satisfied, err := newConstaint.Satisfied(verObj)
if err == nil {
return satisfied, nil
}
}
}

// attempt semver first, then fallback to fuzzy part matching...
if f.semanticConstraint != nil {
if pseudoSemverPattern.Match([]byte(version)) {
Expand Down
72 changes: 72 additions & 0 deletions grype/version/maven_constraint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package version

import "fmt"

type mavenConstraint struct {
raw string
expression constraintExpression
}

func newMavenConstraint(raw string) (mavenConstraint, error) {
if raw == "" {
// empty constraints are always satisfied
return mavenConstraint{}, nil
}

constraints, err := newConstraintExpression(raw, newMavenComparator)
if err != nil {
return mavenConstraint{}, fmt.Errorf("unable to parse maven constraint phrase: %w", err)
}

return mavenConstraint{
raw: raw,
expression: constraints,
}, nil
}

func newMavenComparator(unit constraintUnit) (Comparator, error) {
ver, err := newMavenVersion(unit.version)
if err != nil {
return nil, fmt.Errorf("unable to parse constraint version (%s): %w", unit.version, err)
}

return ver, nil
}

func (c mavenConstraint) supported(format Format) bool {
return format == MavenFormat
}

func (c mavenConstraint) Satisfied(version *Version) (satisfied bool, err error) {
if c.raw == "" && version != nil {
// empty constraints are always satisfied
return true, nil
}

if version == nil {
if c.raw != "" {
// a non-empty constraint with no version given should always fail
return false, nil
}

return true, nil
}

if !c.supported(version.Format) {
return false, fmt.Errorf("(maven) unsupported format: %s", version.Format)
}

if version.rich.mavenVer == nil {
return false, fmt.Errorf("no rich apk version given: %+v", version)
}

return c.expression.satisfied(version)
}

func (c mavenConstraint) String() string {
if c.raw == "" {
return "none (maven)"
}

return fmt.Sprintf("%s (maven)", c.raw)
}
104 changes: 104 additions & 0 deletions grype/version/maven_constraint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package version

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestVersionConstraintJava(t *testing.T) {
tests := []testCase{
{version: "1", constraint: "< 2.5", satisfied: true},
{version: "1.0", constraint: "< 1.1", satisfied: true},
{version: "1.1", constraint: "< 1.2", satisfied: true},
{version: "1.0.0", constraint: "< 1.1", satisfied: true},
{version: "1.0.1", constraint: "< 1.1", satisfied: true},
{version: "1.1", constraint: "> 1.2.0", satisfied: false},
{version: "1.0-alpha-1", constraint: "> 1.0", satisfied: false},
{version: "1.0-alpha-1", constraint: "> 1.0-alpha-2", satisfied: false},
{version: "1.0-alpha-1", constraint: "< 1.0-beta-1", satisfied: true},
{version: "1.0-beta-1", constraint: "< 1.0-SNAPSHOT", satisfied: true},
{version: "1.0-SNAPSHOT", constraint: "< 1.0", satisfied: true},
{version: "1.0-alpha-1-SNAPSHOT", constraint: "> 1.0-alpha-1", satisfied: false},
{version: "1.0", constraint: "< 1.0-1", satisfied: true},
{version: "1.0-1", constraint: "< 1.0-2", satisfied: true},
{version: "1.0.0", constraint: "< 1.0-1", satisfied: true},
{version: "2.0-1", constraint: "> 2.0.1", satisfied: false},
{version: "2.0.1-klm", constraint: "> 2.0.1-lmn", satisfied: false},
{version: "2.0.1", constraint: "< 2.0.1-xyz", satisfied: true},
{version: "2.0.1", constraint: "< 2.0.1-123", satisfied: true},
{version: "2.0.1-xyz", constraint: "< 2.0.1-123", satisfied: true},
{version: "2.414.2-cb-5", constraint: "> 2.414.2", satisfied: true},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
constraint, err := newMavenConstraint(test.constraint)

assert.NoError(t, err, "unexpected error from newMavenConstraint %s: %v", test.version, err)
test.assertVersionConstraint(t, MavenFormat, constraint)

})
}
}

func TestVersionEqualityJava(t *testing.T) {
tests := []testCase{
{version: "1", constraint: "1", satisfied: true},
{version: "1", constraint: "1.0", satisfied: true},
{version: "1", constraint: "1.0.0", satisfied: true},
{version: "1.0", constraint: "1.0.0", satisfied: true},
{version: "1", constraint: "1-0", satisfied: true},
{version: "1", constraint: "1.0-0", satisfied: true},
{version: "1.0", constraint: "1.0-0", satisfied: true},
{version: "1a", constraint: "1-a", satisfied: true},
{version: "1a", constraint: "1.0-a", satisfied: true},
{version: "1a", constraint: "1.0.0-a", satisfied: true},
{version: "1.0a", constraint: "1-a", satisfied: true},
{version: "1.0.0a", constraint: "1-a", satisfied: true},
{version: "1x", constraint: "1-x", satisfied: true},
{version: "1x", constraint: "1.0-x", satisfied: true},
{version: "1x", constraint: "1.0.0-x", satisfied: true},
{version: "1.0x", constraint: "1-x", satisfied: true},
{version: "1.0.0x", constraint: "1-x", satisfied: true},
{version: "1ga", constraint: "1", satisfied: true},
{version: "1release", constraint: "1", satisfied: true},
{version: "1final", constraint: "1", satisfied: true},
{version: "1cr", constraint: "1rc", satisfied: true},
{version: "1a1", constraint: "1-alpha-1", satisfied: true},
{version: "1b2", constraint: "1-beta-2", satisfied: true},
{version: "1m3", constraint: "1-milestone-3", satisfied: true},
{version: "1X", constraint: "1x", satisfied: true},
{version: "1A", constraint: "1a", satisfied: true},
{version: "1B", constraint: "1b", satisfied: true},
{version: "1M", constraint: "1m", satisfied: true},
{version: "1Ga", constraint: "1", satisfied: true},
{version: "1GA", constraint: "1", satisfied: true},
{version: "1RELEASE", constraint: "1", satisfied: true},
{version: "1release", constraint: "1", satisfied: true},
{version: "1RELeaSE", constraint: "1", satisfied: true},
{version: "1Final", constraint: "1", satisfied: true},
{version: "1FinaL", constraint: "1", satisfied: true},
{version: "1FINAL", constraint: "1", satisfied: true},
{version: "1Cr", constraint: "1Rc", satisfied: true},
{version: "1cR", constraint: "1rC", satisfied: true},
{version: "1m3", constraint: "1Milestone3", satisfied: true},
{version: "1m3", constraint: "1MileStone3", satisfied: true},
{version: "1m3", constraint: "1MILESTONE3", satisfied: true},
{version: "1", constraint: "01", satisfied: true},
{version: "1", constraint: "001", satisfied: true},
{version: "1.1", constraint: "1.01", satisfied: true},
{version: "1.1", constraint: "1.001", satisfied: true},
{version: "1-1", constraint: "1-01", satisfied: true},
{version: "1-1", constraint: "1-001", satisfied: true},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
constraint, err := newMavenConstraint(test.constraint)

assert.NoError(t, err, "unexpected error from newMavenConstraint %s: %v", test.version, err)
test.assertVersionConstraint(t, MavenFormat, constraint)
})
}
}
51 changes: 51 additions & 0 deletions grype/version/maven_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package version

import (
"fmt"

mvnv "github.com/masahiro331/go-mvn-version"
)

type mavenVersion struct {
raw string
version mvnv.Version
}

func newMavenVersion(raw string) (*mavenVersion, error) {
ver, err := mvnv.NewVersion(raw)
if err != nil {
return nil, fmt.Errorf("could not generate new java version from: %s; %w", raw, err)
}

return &mavenVersion{
raw: raw,
version: ver,
}, nil
}

// Compare returns 0 if j2 == j, 1 if j2 > j, and -1 if j2 < j.
// If an error returns the int value is -1
func (j *mavenVersion) Compare(j2 *Version) (int, error) {
if j2.Format != MavenFormat {
return -1, fmt.Errorf("unable to compare java to given format: %s", j2.Format)
}
if j2.rich.mavenVer == nil {
return -1, fmt.Errorf("given empty mavenVersion object")
}

submittedVersion := j2.rich.mavenVer.version
if submittedVersion.Equal(j.version) {
return 0, nil
}
if submittedVersion.LessThan(j.version) {
return -1, nil
}
if submittedVersion.GreaterThan(j.version) {
return 1, nil
}

return -1, fmt.Errorf(
"could not compare java versions: %v with %v",
submittedVersion.String(),
j.version.String())
}
Loading