Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package grype for openSUSE:Factory checked in at 2025-05-22 16:55:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/grype (Old) and /work/SRC/openSUSE:Factory/.grype.new.2732 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "grype" Thu May 22 16:55:53 2025 rev:91 rq:1278913 version:0.92.2 Changes: -------- --- /work/SRC/openSUSE:Factory/grype/grype.changes 2025-05-20 10:36:45.049493639 +0200 +++ /work/SRC/openSUSE:Factory/.grype.new.2732/grype.changes 2025-05-22 16:55:56.923574522 +0200 @@ -1,0 +2,17 @@ +Wed May 21 04:29:32 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 0.92.2: + * Bug Fixes + - unpin dockerfile base images to prevent wget TLS errors + [#2671 @spiffcs] + - Parse java group ID and artifact ID from PURL when missing + [#2675 @wagoodman] + - Grype can't update DB in docker volume (regression) [#2517 + #2672 @willmurphyscode] + * Additional Changes + - Remove getDB() from the v6 DB reader [#2669 @wagoodman] + * Dependencies + - chore(deps): update anchore dependencies (#2676) + - chore(deps): update tools to latest versions (#2673) + +------------------------------------------------------------------- Old: ---- grype-0.92.1.obscpio New: ---- grype-0.92.2.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ grype.spec ++++++ --- /var/tmp/diff_new_pack.gPyM6X/_old 2025-05-22 16:55:59.291674908 +0200 +++ /var/tmp/diff_new_pack.gPyM6X/_new 2025-05-22 16:55:59.295675079 +0200 @@ -17,7 +17,7 @@ Name: grype -Version: 0.92.1 +Version: 0.92.2 Release: 0 Summary: A vulnerability scanner for container images and filesystems License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.gPyM6X/_old 2025-05-22 16:55:59.331676604 +0200 +++ /var/tmp/diff_new_pack.gPyM6X/_new 2025-05-22 16:55:59.331676604 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/anchore/grype</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.92.1</param> + <param name="revision">v0.92.2</param> <param name="match-tag">v*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.gPyM6X/_old 2025-05-22 16:55:59.351677452 +0200 +++ /var/tmp/diff_new_pack.gPyM6X/_new 2025-05-22 16:55:59.355677622 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/anchore/grype</param> - <param name="changesrevision">4d630fdfd3e8b2d4e3c02674c60bf355dfba4d7b</param></service></servicedata> + <param name="changesrevision">3f52c46d440ac17b2924c633826ce8fd30a5e16d</param></service></servicedata> (No newline at EOF) ++++++ grype-0.92.1.obscpio -> grype-0.92.2.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/.binny.yaml new/grype-0.92.2/.binny.yaml --- old/grype-0.92.1/.binny.yaml 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/.binny.yaml 2025-05-21 00:12:30.000000000 +0200 @@ -98,7 +98,7 @@ # used for triggering a release - name: gh version: - want: v2.72.0 + want: v2.73.0 method: github-release with: repo: cli/cli diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/Dockerfile new/grype-0.92.2/Dockerfile --- old/grype-0.92.1/Dockerfile 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/Dockerfile 2025-05-21 00:12:30.000000000 +0200 @@ -1,5 +1,4 @@ -FROM gcr.io/distroless/static-debian11@sha256:5759d194607e472ff80fff5833442d3991dd89b219c96552837a2c8f74058617 AS build - +FROM gcr.io/distroless/static-debian12:latest AS build FROM scratch # needed for version check HTTPS request diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/Dockerfile.debug new/grype-0.92.2/Dockerfile.debug --- old/grype-0.92.1/Dockerfile.debug 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/Dockerfile.debug 2025-05-21 00:12:30.000000000 +0200 @@ -1,5 +1,4 @@ -FROM gcr.io/distroless/static-debian11:debug@sha256:c66a6ecb5aa7704a68c89d3ead1398adc7f16e214dda5f5f8e5d44351bcbf67d - +FROM gcr.io/distroless/static-debian12:debug # create the /tmp dir, which is needed for image content cache WORKDIR /tmp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/go.mod new/grype-0.92.2/go.mod --- old/grype-0.92.1/go.mod 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/go.mod 2025-05-21 00:12:30.000000000 +0200 @@ -19,7 +19,7 @@ github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 github.com/anchore/stereoscope v0.1.4 - github.com/anchore/syft v1.25.1 + github.com/anchore/syft v1.26.0 github.com/aquasecurity/go-pep440-version v0.0.1 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/bmatcuk/doublestar/v2 v2.0.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/go.sum new/grype-0.92.2/go.sum --- old/grype-0.92.1/go.sum 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/go.sum 2025-05-21 00:12:30.000000000 +0200 @@ -710,8 +710,8 @@ github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= github.com/anchore/stereoscope v0.1.4 h1:e+iT9UdUzLBabWGe84hn5sTHDRioY+4IHsVzJXuJlek= github.com/anchore/stereoscope v0.1.4/go.mod h1:omWgXDEp/XfqCJlZXIByEo1c3ArZg/qTJ5LBKVLAIdw= -github.com/anchore/syft v1.25.1 h1:HaG5/0r1UdZ7zyscEFeFz0pQsBLTXdCgEDXa5LqFjcg= -github.com/anchore/syft v1.25.1/go.mod h1:xa15pYmHrXKe7IlvaO+EAD/krawWYUtILTpMcL/S+Gw= +github.com/anchore/syft v1.26.0 h1:u3x143wnU9ZuKxksrTugUtZ2dt1hY9fpc8dbd/iMysI= +github.com/anchore/syft v1.26.0/go.mod h1:xa15pYmHrXKe7IlvaO+EAD/krawWYUtILTpMcL/S+Gw= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/db/v6/db.go new/grype-0.92.2/grype/db/v6/db.go --- old/grype-0.92.1/grype/db/v6/db.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/db/v6/db.go 2025-05-21 00:12:30.000000000 +0200 @@ -57,7 +57,6 @@ AffectedPackageStoreReader AffectedCPEStoreReader io.Closer - getDB() *gorm.DB attachBlobValue(...blobable) error } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/db/v6/installation/curator.go new/grype-0.92.2/grype/db/v6/installation/curator.go --- old/grype-0.92.1/grype/db/v6/installation/curator.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/db/v6/installation/curator.go 2025-05-21 00:12:30.000000000 +0200 @@ -298,7 +298,14 @@ if err != nil { return nil, fmt.Errorf("unable to resolve vulnerability DB URL: %w", err) } - dest, err := c.client.Download(url, filepath.Dir(c.config.DBRootDir), mon.downloadProgress.Manual) + + // Ensure parent of DBRootDir exists for the download client to create a temp dir within DBRootDir + // This might be redundant if DBRootDir must already exist, but good for safety. + if err := os.MkdirAll(c.config.DBRootDir, 0o700); err != nil { + return nil, fmt.Errorf("unable to create db root dir %s for download: %w", c.config.DBRootDir, err) + } + + dest, err := c.client.Download(url, c.config.DBRootDir, mon.downloadProgress.Manual) if err != nil { return nil, fmt.Errorf("unable to update vulnerability database: %w", err) } @@ -307,6 +314,8 @@ mon.downloadProgress.SetCompleted() if err = c.activate(dest, url, mon); err != nil { + log.Warnf("Failed to activate downloaded database from %s, attempting cleanup of temporary download directory.", dest) + removeAllOrLog(c.fs, dest) return nil, fmt.Errorf("unable to activate new vulnerability database: %w", err) } @@ -397,7 +406,7 @@ // is a prerequisite for a successful update). filePath := filepath.Join(c.config.DBDirectoryPath(), lastUpdateCheckFileName) - fh, err := c.fs.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + fh, err := c.fs.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) if err != nil { log.WithFields("error", err).Trace("unable to write last update check timestamp") return @@ -414,7 +423,7 @@ mon.Set("preparing") defer mon.SetCompleted() - if err := os.MkdirAll(c.config.DBRootDir, 0700); err != nil { + if err := os.MkdirAll(c.config.DBRootDir, 0o700); err != nil { return fmt.Errorf("unable to create db root dir: %w", err) } @@ -424,7 +433,7 @@ mon.Set("downloading") var err error - tempDir, err = c.client.Download(reference, filepath.Dir(c.config.DBRootDir), mon.downloadProgress.Manual) + tempDir, err = c.client.Download(reference, c.config.DBRootDir, mon.downloadProgress.Manual) if err != nil { return fmt.Errorf("unable to update vulnerability database: %w", err) } @@ -526,12 +535,17 @@ } // ensure parent db directory exists - if err := c.fs.MkdirAll(filepath.Dir(dbDir), 0700); err != nil { + if err = c.fs.MkdirAll(filepath.Dir(dbDir), 0o700); err != nil { return fmt.Errorf("unable to create db parent directory: %w", err) } // activate the new db cache by moving the temp dir to final location + // the rename should be safe because the temp dir is under GRYPE_DB_CACHE_DIR + // and so on the same filesystem as the final location err = c.fs.Rename(dbDirPath, dbDir) + if err != nil { + err = fmt.Errorf("failed to move database directory to activate: %w", err) + } log.WithFields("from", dbDirPath, "to", dbDir, "error", err).Debug("moved database directory to activate") return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/db/v6/installation/curator_test.go new/grype-0.92.2/grype/db/v6/installation/curator_test.go --- old/grype-0.92.1/grype/db/v6/installation/curator_test.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/db/v6/installation/curator_test.go 2025-05-21 00:12:30.000000000 +0200 @@ -758,6 +758,153 @@ } } +func TestCurator_Update_UsesDBRootDirForDownloadTempBase(t *testing.T) { + c := newTestCurator(t) // This sets up c.fs as afero.NewOsFs() rooted in t.TempDir() + mc := c.client.(*mockClient) + + // This is the path that the mocked Download method will return. + // It simulates a temporary directory created by the download client within DBRootDir. + expectedDownloadedContentPath := filepath.Join(c.config.DBRootDir, "temp-downloaded-db-content-123") + + // Pre-create this directory and make it look like a valid DB source for the hydrator and replaceDB. + require.NoError(t, c.fs.MkdirAll(expectedDownloadedContentPath, 0755)) + // Write minimal valid DB metadata so that hydration/activation can proceed far enough. + // Using existing helpers to create a semblance of a DB. + writeTestDB(t, c.fs, expectedDownloadedContentPath) // This creates a basic DB file and import metadata. + + // Mock client responses + mc.On("IsUpdateAvailable", mock.Anything).Return(&distribution.Archive{}, nil) + // CRUCIAL ASSERTION: + // Verify that Download is called with c.config.DBRootDir as its second argument (baseDirForTemp). + // It will return the expectedDownloadedContentPath, simulating successful download and extraction. + mc.On("Download", mock.Anything, c.config.DBRootDir, mock.Anything).Return(expectedDownloadedContentPath, nil) + + hydrateCalled := false + c.hydrator = func(path string) error { + // Ensure hydrator is called with the path returned by Download + assert.Equal(t, expectedDownloadedContentPath, path, "hydrator called with incorrect path") + hydrateCalled = true + return nil // Simulate successful hydration + } + + // Call Update to trigger the download and activation sequence + updated, err := c.Update() + + // Assertions + require.NoError(t, err, "Update should succeed") + require.True(t, updated, "Update should report true") + mc.AssertExpectations(t) // Verifies that Download was called with the expected arguments + assert.True(t, hydrateCalled, "expected hydrator to be called") + + // Check if the DB was "activated" (i.e., renamed) + finalDBPath := c.config.DBDirectoryPath() + _, err = c.fs.Stat(finalDBPath) + require.NoError(t, err, "final DB directory should exist after successful update") + // And the temporary downloaded content path should no longer exist as it was renamed + _, err = c.fs.Stat(expectedDownloadedContentPath) + require.True(t, os.IsNotExist(err), "temporary download path should not exist after rename") +} + +func TestCurator_Update_CleansUpDownloadDirOnActivationFailure(t *testing.T) { + c := newTestCurator(t) // Sets up c.fs as afero.NewOsFs() rooted in t.TempDir() + mc := c.client.(*mockClient) + + // This is the path that the mocked Download method will return. + // This directory should be cleaned up if activation fails. + downloadedContentPath := filepath.Join(c.config.DBRootDir, "temp-download-to-be-cleaned-up") + + // Simulate the download client successfully creating this directory. + require.NoError(t, c.fs.MkdirAll(downloadedContentPath, 0755)) + // Optionally, put a dummy file inside to make the cleanup more tangible. + require.NoError(t, afero.WriteFile(c.fs, filepath.Join(downloadedContentPath, "dummy_file.txt"), []byte("test data"), 0644)) + + // Mock client responses + mc.On("IsUpdateAvailable", mock.Anything).Return(&distribution.Archive{}, nil) + // Download is called with DBRootDir as base, and returns the path to the (simulated) downloaded content. + mc.On("Download", mock.Anything, c.config.DBRootDir, mock.Anything).Return(downloadedContentPath, nil) + + // Configure the hydrator to fail, which will cause c.activate() to fail. + expectedHydrationError := "simulated hydration failure" + c.hydrator = func(path string) error { + assert.Equal(t, downloadedContentPath, path, "hydrator called with incorrect path") + return errors.New(expectedHydrationError) + } + + // Call Update, expecting it to fail during activation. + updated, err := c.Update() + + // Assertions + require.Error(t, err, "Update should fail due to activation error") + require.Contains(t, err.Error(), expectedHydrationError, "Error message should reflect hydration failure") + require.False(t, updated, "Update should report false on failure") + mc.AssertExpectations(t) // Verifies Download was called as expected. + + // CRUCIAL ASSERTION: + // Verify that the temporary download directory was cleaned up. + _, statErr := c.fs.Stat(downloadedContentPath) + require.True(t, os.IsNotExist(statErr), "expected temporary download directory to be cleaned up after activation failure") +} + +// Test for the Import path (URL case) - very similar to the Update tests +func TestCurator_Import_URL_UsesDBRootDirForDownloadTempBaseAndCleansUp(t *testing.T) { + t.Run("successful import from URL", func(t *testing.T) { + c := newTestCurator(t) + mc := c.client.(*mockClient) + + importURL := "http://localhost/some/db.tar.gz" + expectedDownloadedContentPath := filepath.Join(c.config.DBRootDir, "temp-imported-db-content-url") + + require.NoError(t, c.fs.MkdirAll(expectedDownloadedContentPath, 0755)) + writeTestDB(t, c.fs, expectedDownloadedContentPath) + + mc.On("Download", importURL, c.config.DBRootDir, mock.Anything).Return(expectedDownloadedContentPath, nil) + + hydrateCalled := false + c.hydrator = func(path string) error { + assert.Equal(t, expectedDownloadedContentPath, path) + hydrateCalled = true + return nil + } + + err := c.Import(importURL) + + require.NoError(t, err) + mc.AssertExpectations(t) + assert.True(t, hydrateCalled) + _, err = c.fs.Stat(c.config.DBDirectoryPath()) + require.NoError(t, err, "final DB directory should exist") + _, err = c.fs.Stat(expectedDownloadedContentPath) + require.True(t, os.IsNotExist(err), "temp import path should not exist after rename") + }) + + t.Run("import from URL fails activation", func(t *testing.T) { + c := newTestCurator(t) + mc := c.client.(*mockClient) + + importURL := "http://localhost/some/other/db.tar.gz" + downloadedContentPath := filepath.Join(c.config.DBRootDir, "temp-imported-to-cleanup-url") + + require.NoError(t, c.fs.MkdirAll(downloadedContentPath, 0755)) + require.NoError(t, afero.WriteFile(c.fs, filepath.Join(downloadedContentPath, "dummy.txt"), []byte("test"), 0644)) + + mc.On("Download", importURL, c.config.DBRootDir, mock.Anything).Return(downloadedContentPath, nil) + + expectedHydrationError := "simulated hydration failure for import" + c.hydrator = func(path string) error { + return errors.New(expectedHydrationError) + } + + err := c.Import(importURL) + + require.Error(t, err) + require.Contains(t, err.Error(), expectedHydrationError) + mc.AssertExpectations(t) + + _, statErr := c.fs.Stat(downloadedContentPath) + require.True(t, os.IsNotExist(statErr), "expected temp import directory to be cleaned up") + }) +} + func setupTestDB(t *testing.T, dbDir string) db.ReadWriter { s, err := db.NewWriter(db.Config{ DBDirPath: dbDir, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/db/v6/refs.go new/grype-0.92.2/grype/db/v6/refs.go --- old/grype-0.92.1/grype/db/v6/refs.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/db/v6/refs.go 2025-05-21 00:12:30.000000000 +0200 @@ -2,6 +2,8 @@ import ( "slices" + + "gorm.io/gorm" ) type ref[ID, T any] struct { @@ -38,7 +40,7 @@ // load a map with all id -> ref results var values []R - tx := reader.getDB().Where("id IN (?)", ids) + tx := reader.(lowLevelReader).GetDB().Where("id IN (?)", ids) err := tx.Find(&values).Error if err != nil { return err @@ -73,3 +75,7 @@ } return out } + +type lowLevelReader interface { + GetDB() *gorm.DB +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/db/v6/store.go new/grype-0.92.2/grype/db/v6/store.go --- old/grype-0.92.1/grype/db/v6/store.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/db/v6/store.go 2025-05-21 00:12:30.000000000 +0200 @@ -23,7 +23,7 @@ writable bool } -func (s *store) getDB() *gorm.DB { +func (s *store) GetDB() *gorm.DB { return s.db } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/pkg/package.go new/grype-0.92.2/grype/pkg/package.go --- old/grype-0.92.1/grype/pkg/package.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/pkg/package.go 2025-05-21 00:12:30.000000000 +0200 @@ -212,6 +212,7 @@ var metadata interface{} var upstreams []UpstreamPackage + // use the metadata to determine the type of package switch p.Metadata.(type) { case syftPkg.GolangModuleEntry, syftPkg.GolangBinaryBuildinfoEntry: metadata = golangMetadataFromPkg(p) @@ -226,7 +227,7 @@ metadata = *m } case syftPkg.JavaArchive: - if m := javaDataFromPkg(p); m != nil { + if m := javaDataFromPkgMetadata(p); m != nil { metadata = *m } case syftPkg.ApkDBEntry: @@ -236,6 +237,13 @@ metadata = javaVMDataFromPkg(p) } + // there are still cases where we could still fill the metadata from other info (such as the PURL) + if metadata == nil { + if p.Type == syftPkg.JavaPkg { + metadata = javaDataFromPkgData(p) + } + } + return metadata, upstreams } @@ -309,7 +317,7 @@ }) } default: - log.Warnf("unable to extract DPKG metadata for %s", p) + log.Debugf("unable to extract DPKG metadata for %s", p) } return upstreams @@ -343,7 +351,7 @@ var upstreams []UpstreamPackage name, version := getNameAndELVersion(sourceRpm) if name == "" && version == "" { - log.Warnf("unable to extract name and version from SourceRPM=%q ", sourceRpm) + log.Debugf("unable to extract name and version from SourceRPM=%q", sourceRpm) } else if name != pkgName { // don't include matches if the source package name matches the current package name if name != "" && version != "" { @@ -364,13 +372,17 @@ return groupMatches["name"], version } -func javaDataFromPkg(p syftPkg.Package) (metadata *JavaMetadata) { +func javaDataFromPkgMetadata(p syftPkg.Package) (metadata *JavaMetadata) { if value, ok := p.Metadata.(syftPkg.JavaArchive); ok { var artifactID, groupID, name string if value.PomProperties != nil { artifactID = value.PomProperties.ArtifactID groupID = value.PomProperties.GroupID + } else { + // get the group ID / artifact ID from the PURL + artifactID, groupID = javaGroupArtifactIDFromPurl(p.PURL) } + if value.Manifest != nil { for _, kv := range value.Manifest.Main { if kv.Key == "Name" { @@ -396,12 +408,36 @@ ManifestName: name, ArchiveDigests: archiveDigests, } - } else { - log.Warnf("unable to extract Java metadata for %s", p) } return metadata } +func javaDataFromPkgData(p syftPkg.Package) (metadata *JavaMetadata) { + switch p.Type { + case syftPkg.JavaPkg: + artifactID, groupID := javaGroupArtifactIDFromPurl(p.PURL) + if artifactID != "" && groupID != "" { + metadata = &JavaMetadata{ + PomArtifactID: artifactID, + PomGroupID: groupID, + } + } + default: + log.Debugf("unable to extract metadata for %s", p) + } + + return metadata +} + +func javaGroupArtifactIDFromPurl(p string) (string, string) { + purl, err := packageurl.FromString(p) + if err != nil { + log.WithFields("purl", purl, "error", err).Debug("unable to parse java PURL") + return "", "" + } + return purl.Name, purl.Namespace +} + func apkDataFromPkg(p syftPkg.Package) (upstreams []UpstreamPackage) { if value, ok := p.Metadata.(syftPkg.ApkDBEntry); ok { if value.OriginPackage != "" { @@ -410,7 +446,7 @@ }) } } else { - log.Warnf("unable to extract APK metadata for %s", p) + log.Debugf("unable to extract APK metadata for %s", p) } return upstreams } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/pkg/package_test.go new/grype-0.92.2/grype/pkg/package_test.go --- old/grype-0.92.1/grype/pkg/package_test.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/pkg/package_test.go 2025-05-21 00:12:30.000000000 +0200 @@ -842,6 +842,19 @@ }, }, }, + { + name: "pe binary metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.PEBinary{ + VersionResources: syftPkg.KeyValues{ + { + Key: "k", + Value: "k", + }, + }, + }, + }, + }, } // capture each observed metadata type, we should see all of them relate to what syft provides by the end of testing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.92.1/grype/pkg/purl_provider_test.go new/grype-0.92.2/grype/pkg/purl_provider_test.go --- old/grype-0.92.1/grype/pkg/purl_provider_test.go 2025-05-16 21:48:10.000000000 +0200 +++ new/grype-0.92.2/grype/pkg/purl_provider_test.go 2025-05-21 00:12:30.000000000 +0200 @@ -40,6 +40,29 @@ }, }, { + name: "java metadata decoded from purl", + userInput: "pkg:maven/org.apache.commons/commons-lang3@3.12.0", + context: Context{ + Source: &source.Description{ + Metadata: PURLLiteralMetadata{ + PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0", + }, + }, + }, + pkgs: []Package{ + { + Name: "commons-lang3", + Version: "3.12.0", + Type: pkg.JavaPkg, + PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0", + Metadata: JavaMetadata{ + PomArtifactID: "commons-lang3", + PomGroupID: "org.apache.commons", + }, + }, + }, + }, + { name: "os with codename", userInput: "pkg:deb/debian/sysv-rc@2.88dsf-59?arch=all&distro=debian-jessie&upstream=sysvinit", context: Context{ ++++++ grype.obsinfo ++++++ --- /var/tmp/diff_new_pack.gPyM6X/_old 2025-05-22 16:56:03.207840918 +0200 +++ /var/tmp/diff_new_pack.gPyM6X/_new 2025-05-22 16:56:03.211841087 +0200 @@ -1,5 +1,5 @@ name: grype -version: 0.92.1 -mtime: 1747424890 -commit: 4d630fdfd3e8b2d4e3c02674c60bf355dfba4d7b +version: 0.92.2 +mtime: 1747779150 +commit: 3f52c46d440ac17b2924c633826ce8fd30a5e16d ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/grype/vendor.tar.gz /work/SRC/openSUSE:Factory/.grype.new.2732/vendor.tar.gz differ: char 14, line 1