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 7 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
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 JavaFormat:
return newJavaConstraint(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
JavaFormat
spiffcs marked this conversation as resolved.
Show resolved Hide resolved
RpmFormat
PythonFormat
KBFormat
Expand All @@ -25,6 +26,7 @@ var formatStr = []string{
"Semantic",
"Apk",
"Deb",
"Java",
"RPM",
"Python",
"KB",
Expand All @@ -36,6 +38,7 @@ var Formats = []Format{
SemanticFormat,
ApkFormat,
DebFormat,
JavaFormat,
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(JavaFormat.String()), "java":
return JavaFormat
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 = JavaFormat
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: "java",
format: JavaFormat,
},
{
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: JavaFormat,
},
{
pkgType: pkg.GemPkg,
format: GemFormat,
Expand Down
72 changes: 72 additions & 0 deletions grype/version/java_constraint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package version

import "fmt"

type javaConstraint struct {
raw string
expression constraintExpression
}

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

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

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

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

return ver, nil
}

func (c javaConstraint) supported(format Format) bool {
return format == JavaFormat
}

func (c javaConstraint) 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("(java) unsupported format: %s", version.Format)
}

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

return c.expression.satisfied(version)
}

func (c javaConstraint) String() string {
if c.raw == "" {
return "none (java)"
}

return fmt.Sprintf("%s (java)", c.raw)
}
104 changes: 104 additions & 0 deletions grype/version/java_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 := newJavaConstraint(test.constraint)

assert.NoError(t, err, "unexpected error from newJavaConstraint %s: %v", test.version, err)
test.assertVersionConstraint(t, JavaFormat, 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 := newJavaConstraint(test.constraint)

assert.NoError(t, err, "unexpected error from newJavaConstraint %s: %v", test.version, err)
test.assertVersionConstraint(t, JavaFormat, constraint)
})
}
}
51 changes: 51 additions & 0 deletions grype/version/java_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 javaVersion struct {
raw string
version mvnv.Version
}

func newJavaVersion(raw string) (*javaVersion, 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 &javaVersion{
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 *javaVersion) Compare(j2 *Version) (int, error) {
if j2.Format != JavaFormat {
return -1, fmt.Errorf("unable to compare java to given format: %s", j2.Format)
}
if j2.rich.javaVer == nil {
return -1, fmt.Errorf("given empty javaVersion object")
}

submittedVersion := j2.rich.javaVer.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())
}
49 changes: 49 additions & 0 deletions grype/version/java_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package version

import (
"testing"

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

func Test_javaVersion_Compare(t *testing.T) {
tests := []struct {
name string
compare string
want int
}{
{
name: "1",
compare: "2",
want: -1,
},
{
name: "1.8.0_282",
compare: "1.8.0_282",
want: 0,
},
{
name: "2.5",
compare: "2.0",
want: 1,
},
{
name: "2.414.2-cb-5",
compare: "2.414.2",
want: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
j, err := NewVersion(tt.name, JavaFormat)
assert.NoError(t, err)

j2, err := NewVersion(tt.compare, JavaFormat)
assert.NoError(t, err)

if got, _ := j2.rich.javaVer.Compare(j); got != tt.want {
t.Errorf("Compare() = %v, want %v", got, tt.want)
}
})
}
}
Loading