mirror of
https://github.com/anchore/syft.git
synced 2026-11-03 16:06:36 +01:00
Signed-off-by: Rez Moss <hi@rezmoss.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
"iter"
|
"iter"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -301,6 +302,9 @@ func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest
|
|||||||
if version == "" {
|
if version == "" {
|
||||||
version = selectVersion(manifest, j.fileInfo)
|
version = selectVersion(manifest, j.fileInfo)
|
||||||
}
|
}
|
||||||
|
if version == "" {
|
||||||
|
version = j.versionFromPropertiesFile(ctx, manifest)
|
||||||
|
}
|
||||||
|
|
||||||
if len(lics) == 0 {
|
if len(lics) == 0 {
|
||||||
fileLicenses := j.getLicenseFromFileInArchive(ctx)
|
fileLicenses := j.getLicenseFromFileInArchive(ctx)
|
||||||
@@ -609,6 +613,44 @@ func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) []pkg.L
|
|||||||
return identified
|
return identified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optional "v" + leading digit + version-ish chars; rejects placeholders like "${revision}"
|
||||||
|
var releaseVersionPattern = regexp.MustCompile(`^v?(\d[0-9A-Za-z.\-_+]*)$`)
|
||||||
|
|
||||||
|
// versionFromPropertiesFile: last-resort ver from root version.properties for uber-jars built
|
||||||
|
// outside maven/gradle e.g. metabase, "tag=v0.63.5", shading can land a deps file at t same
|
||||||
|
// path, so be conservative => Main-Class jars only, "version"/"tag" keys only, version shaped values only
|
||||||
|
func (j *archiveParser) versionFromPropertiesFile(ctx context.Context, manifest *pkg.JavaManifest) string {
|
||||||
|
if manifest == nil || manifest.Main.MustGet("Main-Class") == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := j.fileManifest.GlobMatch(true, "/version.properties")
|
||||||
|
if len(matches) != 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err := intFile.ContentsFromZip(ctx, j.archivePath, matches...)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("unable to extract version.properties (%s): %v", j.location, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
props := map[string]string{}
|
||||||
|
for line := range strings.Lines(contents[matches[0]]) {
|
||||||
|
if k, v, ok := strings.Cut(line, "="); ok {
|
||||||
|
props[strings.ToLower(strings.TrimSpace(k))] = strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range []string{"version", "tag"} {
|
||||||
|
if m := releaseVersionPattern.FindStringSubmatch(props[key]); m != nil {
|
||||||
|
return m[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (j *archiveParser) discoverPkgsFromNestedArchives(ctx context.Context, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
func (j *archiveParser) discoverPkgsFromNestedArchives(ctx context.Context, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
// we know that all java archives are zip formatted files, so we can use the shared zip helper
|
// we know that all java archives are zip formatted files, so we can use the shared zip helper
|
||||||
return discoverPkgsFromZip(ctx, j.location, j.archivePath, j.contentPath, j.fileManifest, parentPkg, j.cfg)
|
return discoverPkgsFromZip(ctx, j.location, j.archivePath, j.contentPath, j.fileManifest, parentPkg, j.cfg)
|
||||||
|
|||||||
+16
-1
@@ -15,6 +15,9 @@ MULTIPLE_MATCHING = multiple-matching-2.11.5
|
|||||||
ORG_MULTIPLE_THENAME = org.multiple-thename
|
ORG_MULTIPLE_THENAME = org.multiple-thename
|
||||||
MICRONAUT_AOP = micronaut-aop-4.9.11
|
MICRONAUT_AOP = micronaut-aop-4.9.11
|
||||||
COMMONS_LANG3 = commons-lang3-3.12.0
|
COMMONS_LANG3 = commons-lang3-3.12.0
|
||||||
|
UBER_APP_VERSION_PROPERTIES = uber-app-version-properties
|
||||||
|
UBER_APP_VERSION_PLACEHOLDER = uber-app-version-placeholder
|
||||||
|
LIB_WITH_VERSION_PROPERTIES = lib-with-version-properties
|
||||||
|
|
||||||
.DEFAULT_GOAL := fixtures
|
.DEFAULT_GOAL := fixtures
|
||||||
|
|
||||||
@@ -24,7 +27,7 @@ fixtures: $(CACHE_DIR)
|
|||||||
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||||
fingerprint: $(FINGERPRINT_FILE)
|
fingerprint: $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
$(CACHE_DIR): $(CACHE_DIR)/$(JACKSON_CORE).jar $(CACHE_DIR)/$(SBT_JACKSON_CORE).jar $(CACHE_DIR)/$(OPENSAML_CORE).jar $(CACHE_DIR)/$(API_ALL_SOURCES).jar $(CACHE_DIR)/$(SPRING_INSTRUMENTATION).jar $(CACHE_DIR)/$(MULTIPLE_MATCHING).jar $(CACHE_DIR)/$(MICRONAUT_AOP).jar $(CACHE_DIR)/$(COMMONS_LANG3).jar
|
$(CACHE_DIR): $(CACHE_DIR)/$(JACKSON_CORE).jar $(CACHE_DIR)/$(SBT_JACKSON_CORE).jar $(CACHE_DIR)/$(OPENSAML_CORE).jar $(CACHE_DIR)/$(API_ALL_SOURCES).jar $(CACHE_DIR)/$(SPRING_INSTRUMENTATION).jar $(CACHE_DIR)/$(MULTIPLE_MATCHING).jar $(CACHE_DIR)/$(MICRONAUT_AOP).jar $(CACHE_DIR)/$(COMMONS_LANG3).jar $(CACHE_DIR)/$(UBER_APP_VERSION_PROPERTIES).jar $(CACHE_DIR)/$(UBER_APP_VERSION_PLACEHOLDER).jar $(CACHE_DIR)/$(LIB_WITH_VERSION_PROPERTIES).jar
|
||||||
|
|
||||||
$(CACHE_DIR)/$(JACKSON_CORE).jar:
|
$(CACHE_DIR)/$(JACKSON_CORE).jar:
|
||||||
mkdir -p $(CACHE_DIR)
|
mkdir -p $(CACHE_DIR)
|
||||||
@@ -62,6 +65,18 @@ $(CACHE_DIR)/$(COMMONS_LANG3).jar:
|
|||||||
mkdir -p $(CACHE_DIR)
|
mkdir -p $(CACHE_DIR)
|
||||||
cd $(COMMONS_LANG3) && zip -r $(CACHE_PATH)/$(COMMONS_LANG3).jar .
|
cd $(COMMONS_LANG3) && zip -r $(CACHE_PATH)/$(COMMONS_LANG3).jar .
|
||||||
|
|
||||||
|
$(CACHE_DIR)/$(UBER_APP_VERSION_PROPERTIES).jar:
|
||||||
|
mkdir -p $(CACHE_DIR)
|
||||||
|
cd $(UBER_APP_VERSION_PROPERTIES) && zip -r $(CACHE_PATH)/$(UBER_APP_VERSION_PROPERTIES).jar .
|
||||||
|
|
||||||
|
$(CACHE_DIR)/$(UBER_APP_VERSION_PLACEHOLDER).jar:
|
||||||
|
mkdir -p $(CACHE_DIR)
|
||||||
|
cd $(UBER_APP_VERSION_PLACEHOLDER) && zip -r $(CACHE_PATH)/$(UBER_APP_VERSION_PLACEHOLDER).jar .
|
||||||
|
|
||||||
|
$(CACHE_DIR)/$(LIB_WITH_VERSION_PROPERTIES).jar:
|
||||||
|
mkdir -p $(CACHE_DIR)
|
||||||
|
cd $(LIB_WITH_VERSION_PROPERTIES) && zip -r $(CACHE_PATH)/$(LIB_WITH_VERSION_PROPERTIES).jar .
|
||||||
|
|
||||||
# Jenkins plugins typically do not have the version included in the archive name,
|
# Jenkins plugins typically do not have the version included in the archive name,
|
||||||
# so it is important to not include it in the generated test fixture
|
# so it is important to not include it in the generated test fixture
|
||||||
$(CACHE_DIR)/gradle.hpi:
|
$(CACHE_DIR)/gradle.hpi:
|
||||||
|
|||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
Created-By: Apache Maven
|
||||||
|
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
version=1.2.3
|
||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
Created-By: build.clj
|
||||||
|
Main-Class: example.core.bootstrap
|
||||||
|
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
version=${revision}
|
||||||
|
build.date=${build_timestamp}
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
Multi-Release: true
|
||||||
|
Created-By: build.clj
|
||||||
|
Build-Jdk-Spec: 25
|
||||||
|
Main-Class: example.core.bootstrap
|
||||||
|
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
tag=v0.63.5
|
||||||
|
hash=1dca717
|
||||||
|
date=2026-08-05
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package java
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
intFile "github.com/anchore/syft/internal/file"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readManifest(t *testing.T, ctx context.Context, parser *archiveParser) *pkg.JavaManifest {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
matches := parser.fileManifest.GlobMatch(false, manifestGlob)
|
||||||
|
require.Len(t, matches, 1)
|
||||||
|
|
||||||
|
contents, err := intFile.ContentsFromZip(ctx, parser.archivePath, matches...)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
manifest, err := parseJavaManifest(parser.archivePath, strings.NewReader(contents[matches[0]]))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_archiveParser_versionFromPropertiesFile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fixture string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// "tag=v0.63.5"
|
||||||
|
name: "release tag from root version.properties",
|
||||||
|
fixture: "uber-app-version-properties",
|
||||||
|
want: "0.63.5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// clickhouse-jdbc ships "version=${revision}" must not become a version
|
||||||
|
name: "unresolved build placeholder is rejected",
|
||||||
|
fixture: "uber-app-version-placeholder",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// no Main-Class -> cant tell app uberjar from bundled dep
|
||||||
|
name: "library without Main-Class is rejected",
|
||||||
|
fixture: "lib-with-version-properties",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx := pkgtest.Context(t)
|
||||||
|
|
||||||
|
fixturePath := generateJavaMetadataJarFixture(t, tt.fixture, "jar")
|
||||||
|
fixture, err := os.Open(fixturePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
parser, cleanupFn, err := newJavaArchiveParser(ctx,
|
||||||
|
file.LocationReadCloser{
|
||||||
|
Location: file.NewLocation(fixture.Name()),
|
||||||
|
ReadCloser: fixture,
|
||||||
|
}, false, DefaultArchiveCatalogerConfig())
|
||||||
|
defer cleanupFn()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, parser.versionFromPropertiesFile(ctx, readManifest(t, ctx, parser)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_archiveParser_versionFromPropertiesFile_noManifest(t *testing.T) {
|
||||||
|
ctx := pkgtest.Context(t)
|
||||||
|
|
||||||
|
fixturePath := generateJavaMetadataJarFixture(t, "uber-app-version-properties", "jar")
|
||||||
|
fixture, err := os.Open(fixturePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
parser, cleanupFn, err := newJavaArchiveParser(ctx,
|
||||||
|
file.LocationReadCloser{
|
||||||
|
Location: file.NewLocation(fixture.Name()),
|
||||||
|
ReadCloser: fixture,
|
||||||
|
}, false, DefaultArchiveCatalogerConfig())
|
||||||
|
defer cleanupFn()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Empty(t, parser.versionFromPropertiesFile(ctx, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_releaseVersionPattern(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
value string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"v0.63.5", "0.63.5"},
|
||||||
|
{"0.63.5", "0.63.5"},
|
||||||
|
{"2.5.1", "2.5.1"},
|
||||||
|
{"1.0.0-SNAPSHOT", "1.0.0-SNAPSHOT"},
|
||||||
|
{"3", "3"},
|
||||||
|
{"${revision}", ""},
|
||||||
|
{"trunk-nightly build", ""},
|
||||||
|
{"valhalla", ""},
|
||||||
|
{"", ""},
|
||||||
|
{"v", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.value, func(t *testing.T) {
|
||||||
|
var got string
|
||||||
|
if m := releaseVersionPattern.FindStringSubmatch(tt.value); m != nil {
|
||||||
|
got = m[1]
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user