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

Account for maven bundle plugin and fix filename matching #2220

Merged
merged 4 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -237,7 +237,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to untangle this many thanks

pomPropertiesObject = propertiesObj
if proj, exists := projects[parentPath]; exists {
pomProjectObject = proj
Expand All @@ -256,6 +256,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/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/syft/internal"
"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 @@ -1004,3 +967,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)
}
}
}