Files
trivy/pkg/javadb/client.go
Matthieu MOREL 6562082e28 fix: unused-parameter rule from revive (#8794)
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2025-04-30 09:17:24 +00:00

224 lines
5.7 KiB
Go

package javadb
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"sync"
"time"
"github.com/google/go-containerregistry/pkg/name"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy-java-db/pkg/db"
"github.com/aquasecurity/trivy-java-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/oci"
)
const (
SchemaVersion = db.SchemaVersion
mediaType = "application/vnd.aquasec.trivy.javadb.layer.v1.tar+gzip"
)
var (
// GitHub Container Registry
DefaultGHCRRepository = fmt.Sprintf("%s:%d", "ghcr.io/aquasecurity/trivy-java-db", SchemaVersion)
// GCR mirrors
DefaultGCRRepository = fmt.Sprintf("%s:%d", "mirror.gcr.io/aquasec/trivy-java-db", SchemaVersion)
)
var updater *Updater
type Updater struct {
repos []name.Reference
dbDir string
skip bool
quiet bool
registryOption ftypes.RegistryOptions
once sync.Once // we need to update java-db once per run
}
func (u *Updater) Update() error {
ctx := log.WithContextPrefix(context.Background(), log.PrefixJavaDB)
metac := db.NewMetadata(u.dbDir)
meta, err := metac.Get()
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return xerrors.Errorf("Java DB metadata error: %w", err)
} else if u.skip {
log.ErrorContext(ctx, "The first run cannot skip downloading Java DB")
return xerrors.New("'--skip-java-db-update' cannot be specified on the first run")
}
}
if (meta.Version != SchemaVersion || !u.isNewDB(ctx, meta)) && !u.skip {
// Download DB
// TODO: support remote options
if err := u.downloadDB(ctx); err != nil {
return xerrors.Errorf("OCI artifact error: %w", err)
}
// Parse the newly downloaded metadata.json
meta, err = metac.Get()
if err != nil {
return xerrors.Errorf("Java DB metadata error: %w", err)
}
// Update DownloadedAt
meta.DownloadedAt = time.Now().UTC()
if err = metac.Update(meta); err != nil {
return xerrors.Errorf("Java DB metadata update error: %w", err)
}
log.InfoContext(ctx, "Java DB is cached for 3 days. If you want to update the database more frequently, "+
`"trivy clean --java-db" command clears the DB cache.`)
}
return nil
}
func (u *Updater) isNewDB(ctx context.Context, meta db.Metadata) bool {
now := time.Now().UTC()
if now.Before(meta.NextUpdate) {
log.DebugContext(ctx, "Java DB update was skipped because the local Java DB is the latest")
return true
}
if now.Before(meta.DownloadedAt.Add(time.Hour * 24)) { // 1 day
log.DebugContext(ctx, "Java DB update was skipped because the local Java DB was downloaded during the last day")
return true
}
return false
}
func (u *Updater) downloadDB(ctx context.Context) error {
log.InfoContext(ctx, "Downloading Java DB...")
artifacts := oci.NewArtifacts(u.repos, u.registryOption)
downloadOpt := oci.DownloadOption{
MediaType: mediaType,
Quiet: u.quiet,
}
if err := artifacts.Download(ctx, u.dbDir, downloadOpt); err != nil {
return xerrors.Errorf("failed to download Java DB: %w", err)
}
return nil
}
func Init(cacheDir string, javaDBRepositories []name.Reference, skip, quiet bool, registryOption ftypes.RegistryOptions) {
updater = &Updater{
repos: javaDBRepositories,
dbDir: dbDir(cacheDir),
skip: skip,
quiet: quiet,
registryOption: registryOption,
}
}
func Update() error {
if updater == nil {
return xerrors.New("Java DB client not initialized")
}
var err error
updater.once.Do(func() {
err = updater.Update()
})
return err
}
func Clear(_ context.Context, cacheDir string) error {
return os.RemoveAll(dbDir(cacheDir))
}
func dbDir(cacheDir string) string {
return filepath.Join(cacheDir, "java-db")
}
type DB struct {
driver db.DB
}
func NewClient() (*DB, error) {
if err := Update(); err != nil {
return nil, xerrors.Errorf("Java DB update failed: %s", err)
}
dbc, err := db.New(updater.dbDir)
if err != nil {
return nil, xerrors.Errorf("Java DB open error: %w", err)
}
return &DB{driver: dbc}, nil
}
func (d *DB) Exists(groupID, artifactID string) (bool, error) {
index, err := d.driver.SelectIndexByArtifactIDAndGroupID(artifactID, groupID)
if err != nil {
return false, err
}
return index.ArtifactID != "", nil
}
func (d *DB) SearchBySHA1(sha1 string) (jar.Properties, error) {
index, err := d.driver.SelectIndexBySha1(sha1)
if err != nil {
return jar.Properties{}, xerrors.Errorf("select error: %w", err)
} else if index.ArtifactID == "" {
return jar.Properties{}, xerrors.Errorf("digest %s: %w", sha1, jar.ArtifactNotFoundErr)
}
return jar.Properties{
GroupID: index.GroupID,
ArtifactID: index.ArtifactID,
Version: index.Version,
}, nil
}
func (d *DB) SearchByArtifactID(artifactID, version string) (string, error) {
indexes, err := d.driver.SelectIndexesByArtifactIDAndFileType(artifactID, version, types.JarType)
if err != nil {
return "", xerrors.Errorf("select error: %w", err)
} else if len(indexes) == 0 {
return "", xerrors.Errorf("artifactID %s: %w", artifactID, jar.ArtifactNotFoundErr)
}
sort.Slice(indexes, func(i, j int) bool {
return indexes[i].GroupID < indexes[j].GroupID
})
// Some artifacts might have the same artifactId.
// e.g. "javax.servlet:jstl" and "jstl:jstl"
groupIDs := make(map[string]int)
for _, index := range indexes {
if i, ok := groupIDs[index.GroupID]; ok {
groupIDs[index.GroupID] = i + 1
continue
}
groupIDs[index.GroupID] = 1
}
maxCount := 0
var groupID string
for k, v := range groupIDs {
if v > maxCount {
maxCount = v
groupID = k
}
}
return groupID, nil
}
func (d *DB) Close() error {
if d == nil {
return nil
}
return d.driver.Close()
}