Skip to content

Commit

Permalink
Account for maven bundle plugin and fix filename matching (#2220)
Browse files Browse the repository at this point in the history
* account for maven bundle plugin and fix filename matching

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add in-repo jar tests based on metadata to cover #2130

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* tests: fix test merge commit

Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
Co-authored-by: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
  • Loading branch information
3 people authored Oct 19, 2023
1 parent 6c7900f commit 07f1304
Show file tree
Hide file tree
Showing 11 changed files with 1,056 additions and 63 deletions.
9 changes: 8 additions & 1 deletion syft/pkg/cataloger/java/archive_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func (j *archiveParser) guessMainPackageNameAndVersionFromPomInfo() (name, versi
projects, _ := pomProjectByParentPath(j.archivePath, j.location, pomMatches)

for parentPath, propertiesObj := range properties {
if propertiesObj.ArtifactID != "" && j.fileInfo.name != "" && strings.HasPrefix(propertiesObj.ArtifactID, j.fileInfo.name) {
if artifactIDMatchesFilename(propertiesObj.ArtifactID, j.fileInfo.name) {
pomPropertiesObject = propertiesObj
if proj, exists := projects[parentPath]; exists {
pomProjectObject = proj
Expand All @@ -276,6 +276,13 @@ func (j *archiveParser) guessMainPackageNameAndVersionFromPomInfo() (name, versi
return name, version, pomProjectObject.Licenses
}

func artifactIDMatchesFilename(artifactID, fileName string) bool {
if artifactID == "" || fileName == "" {
return false
}
return strings.HasPrefix(artifactID, fileName) || strings.HasSuffix(fileName, artifactID)
}

// discoverPkgsFromAllMavenFiles parses Maven POM properties/xml for a given
// parent package, returning all listed Java packages found for each pom
// properties discovered and potentially updating the given parentPkg with new
Expand Down
270 changes: 229 additions & 41 deletions syft/pkg/cataloger/java/archive_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import (
"syscall"
"testing"

"github.com/google/go-cmp/cmp/cmpopts"
"github.com/gookit/color"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg"
Expand All @@ -38,47 +41,7 @@ func generateJavaBuildFixture(t *testing.T, fixturePath string) {
cmd := exec.Command("make", makeTask)
cmd.Dir = filepath.Join(cwd, "test-fixtures/java-builds/")

stderr, err := cmd.StderrPipe()
if err != nil {
t.Fatalf("could not get stderr: %+v", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatalf("could not get stdout: %+v", err)
}

err = cmd.Start()
if err != nil {
t.Fatalf("failed to start cmd: %+v", err)
}

show := func(label string, reader io.ReadCloser) {
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
t.Logf("%s: %s", label, scanner.Text())
}
}
go show("out", stdout)
go show("err", stderr)

if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0

// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() != 0 {
t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
}
}
} else {
t.Fatalf("unable to get generate fixture result: %+v", err)
}
}
run(t, cmd)
}

func TestParseJar(t *testing.T) {
Expand Down Expand Up @@ -1020,3 +983,228 @@ func Test_newPackageFromMavenData(t *testing.T) {
})
}
}

func Test_artifactIDMatchesFilename(t *testing.T) {
tests := []struct {
name string
artifactID string
fileName string // without version or extension
want bool
}{
{
name: "artifact id within file name",
artifactID: "atlassian-extras-api",
fileName: "com.atlassian.extras_atlassian-extras-api",
want: true,
},
{
name: "file name within artifact id",
artifactID: "atlassian-extras-api-something",
fileName: "atlassian-extras-api",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, artifactIDMatchesFilename(tt.artifactID, tt.fileName))
})
}
}

func Test_parseJavaArchive_regressions(t *testing.T) {
tests := []struct {
name string
fixtureName string
expectedPkgs []pkg.Package
expectedRelationships []artifact.Relationship
want bool
}{
{
name: "duplicate jar regression - go case (issue #2130)",
fixtureName: "jackson-core-2.15.2",
expectedPkgs: []pkg.Package{
{
Name: "jackson-core",
Version: "2.15.2",
Type: pkg.JavaPkg,
Language: pkg.Java,
MetadataType: pkg.JavaMetadataType,
PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicensesFromLocation(
file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar"),
"https://www.apache.org/licenses/LICENSE-2.0.txt",
)...,
),
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Build-Jdk-Spec": "1.8",
"Bundle-Description": "Core Jackson processing abstractions",
"Bundle-DocURL": "https://github.com/FasterXML/jackson-core",
"Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt",
"Bundle-ManifestVersion": "2",
"Bundle-Name": "Jackson-core",
"Bundle-SymbolicName": "com.fasterxml.jackson.core.jackson-core",
"Bundle-Vendor": "FasterXML",
"Bundle-Version": "2.15.2",
"Created-By": "Apache Maven Bundle Plugin 5.1.8",
"Export-Package": "com.fasterxml.jackson.core;version...snip",
"Implementation-Title": "Jackson-core",
"Implementation-Vendor": "FasterXML",
"Implementation-Vendor-Id": "com.fasterxml.jackson.core",
"Implementation-Version": "2.15.2",
"Import-Package": "com.fasterxml.jackson.core;version=...snip",
"Manifest-Version": "1.0",
"Multi-Release": "true",
"Require-Capability": `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`,
"Specification-Title": "Jackson-core",
"Specification-Vendor": "FasterXML",
"Specification-Version": "2.15.2",
"Tool": "Bnd-6.3.1.202206071316",
"X-Compile-Source-JDK": "1.8",
"X-Compile-Target-JDK": "1.8",
},
},
// not under test
//ArchiveDigests: []file.Digest{{Algorithm: "sha1", Value: "d8bc1d9c428c96fe447e2c429fc4304d141024df"}},
},
},
},
},
{
name: "duplicate jar regression - bad case (issue #2130)",
fixtureName: "com.fasterxml.jackson.core.jackson-core-2.15.2",
expectedPkgs: []pkg.Package{
{
Name: "jackson-core",
Version: "2.15.2",
Type: pkg.JavaPkg,
Language: pkg.Java,
MetadataType: pkg.JavaMetadataType,
PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicensesFromLocation(
file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar"),
"https://www.apache.org/licenses/LICENSE-2.0.txt",
)...,
),
Metadata: pkg.JavaMetadata{
VirtualPath: "test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar",
Manifest: &pkg.JavaManifest{
Main: map[string]string{
"Build-Jdk-Spec": "1.8",
"Bundle-Description": "Core Jackson processing abstractions",
"Bundle-DocURL": "https://github.com/FasterXML/jackson-core",
"Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt",
"Bundle-ManifestVersion": "2",
"Bundle-Name": "Jackson-core",
"Bundle-SymbolicName": "com.fasterxml.jackson.core.jackson-core",
"Bundle-Vendor": "FasterXML",
"Bundle-Version": "2.15.2",
"Created-By": "Apache Maven Bundle Plugin 5.1.8",
"Export-Package": "com.fasterxml.jackson.core;version...snip",
"Implementation-Title": "Jackson-core",
"Implementation-Vendor": "FasterXML",
"Implementation-Vendor-Id": "com.fasterxml.jackson.core",
"Implementation-Version": "2.15.2",
"Import-Package": "com.fasterxml.jackson.core;version=...snip",
"Manifest-Version": "1.0",
"Multi-Release": "true",
"Require-Capability": `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`,
"Specification-Title": "Jackson-core",
"Specification-Vendor": "FasterXML",
"Specification-Version": "2.15.2",
"Tool": "Bnd-6.3.1.202206071316",
"X-Compile-Source-JDK": "1.8",
"X-Compile-Target-JDK": "1.8",
},
},
// not under test
//ArchiveDigests: []file.Digest{{Algorithm: "sha1", Value: "abd3e329270fc54a2acaceb45420fd5710ecefd5"}},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
FromFile(t, generateJavaMetadataJarFixture(t, tt.fixtureName)).
Expects(tt.expectedPkgs, tt.expectedRelationships).
WithCompareOptions(cmpopts.IgnoreFields(pkg.JavaMetadata{}, "ArchiveDigests")).
TestParser(t, parseJavaArchive)
})
}
}

func generateJavaMetadataJarFixture(t *testing.T, fixtureName string) string {
fixturePath := filepath.Join("test-fixtures/jar-metadata/cache/", fixtureName+".jar")
if _, err := os.Stat(fixturePath); !os.IsNotExist(err) {
// fixture already exists...
return fixturePath
}

makeTask := filepath.Join("cache", fixtureName+".jar")
t.Logf(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask))

cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}

cmd := exec.Command("make", makeTask)
cmd.Dir = filepath.Join(cwd, "test-fixtures/jar-metadata")

run(t, cmd)

return fixturePath
}

func run(t testing.TB, cmd *exec.Cmd) {

stderr, err := cmd.StderrPipe()
if err != nil {
t.Fatalf("could not get stderr: %+v", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatalf("could not get stdout: %+v", err)
}

err = cmd.Start()
if err != nil {
t.Fatalf("failed to start cmd: %+v", err)
}

show := func(label string, reader io.ReadCloser) {
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
t.Logf("%s: %s", label, scanner.Text())
}
}
go show("out", stdout)
go show("err", stderr)

if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0

// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() != 0 {
t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
}
}
} else {
t.Fatalf("unable to get generate fixture result: %+v", err)
}
}
}
Loading

0 comments on commit 07f1304

Please # to comment.