mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
224 lines
6.0 KiB
Go
224 lines
6.0 KiB
Go
package cache
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
bolt "go.etcd.io/bbolt"
|
|
bberrors "go.etcd.io/bbolt/errors"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
|
)
|
|
|
|
const defaultFSCacheTimeout = 5 * time.Second
|
|
|
|
var _ Cache = &FSCache{}
|
|
|
|
type FSCache struct {
|
|
db *bolt.DB
|
|
directory string
|
|
}
|
|
|
|
func NewFSCache(cacheDir string) (FSCache, error) {
|
|
dir := filepath.Join(cacheDir, scanCacheDirName)
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
return FSCache{}, xerrors.Errorf("failed to create cache dir: %w", err)
|
|
}
|
|
|
|
db, err := bolt.Open(filepath.Join(dir, "fanal.db"), 0o600, &bolt.Options{
|
|
Timeout: defaultFSCacheTimeout,
|
|
})
|
|
if err != nil {
|
|
// Check if the error is due to timeout (database locked by another process)
|
|
if errors.Is(err, bberrors.ErrTimeout) {
|
|
return FSCache{}, xerrors.Errorf("cache may be in use by another process: %w", err)
|
|
}
|
|
return FSCache{}, xerrors.Errorf("unable to open cache DB: %w", err)
|
|
}
|
|
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
for _, bucket := range []string{
|
|
artifactBucket,
|
|
blobBucket,
|
|
} {
|
|
if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
|
|
return xerrors.Errorf("unable to create %s bucket: %w", bucket, err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return FSCache{}, xerrors.Errorf("DB error: %w", err)
|
|
}
|
|
|
|
return FSCache{
|
|
db: db,
|
|
directory: dir,
|
|
}, nil
|
|
}
|
|
|
|
// GetBlob gets blob information such as layer data from local cache
|
|
func (fs FSCache) GetBlob(blobID string) (types.BlobInfo, error) {
|
|
var blobInfo types.BlobInfo
|
|
err := fs.db.View(func(tx *bolt.Tx) error {
|
|
var err error
|
|
blobBucket := tx.Bucket([]byte(blobBucket))
|
|
blobInfo, err = fs.getBlob(blobBucket, blobID)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get blob from the cache: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return types.BlobInfo{}, xerrors.Errorf("DB error: %w", err)
|
|
}
|
|
return blobInfo, nil
|
|
}
|
|
|
|
func (fs FSCache) getBlob(blobBucket *bolt.Bucket, diffID string) (types.BlobInfo, error) {
|
|
b := blobBucket.Get([]byte(diffID))
|
|
|
|
var l types.BlobInfo
|
|
if err := json.Unmarshal(b, &l); err != nil {
|
|
return types.BlobInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err)
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
// PutBlob stores blob information such as layer information in local cache
|
|
func (fs FSCache) PutBlob(blobID string, blobInfo types.BlobInfo) error {
|
|
b, err := json.Marshal(blobInfo)
|
|
if err != nil {
|
|
return xerrors.Errorf("unable to marshal blob JSON (%s): %w", blobID, err)
|
|
}
|
|
err = fs.db.Update(func(tx *bolt.Tx) error {
|
|
blobBucket := tx.Bucket([]byte(blobBucket))
|
|
err = blobBucket.Put([]byte(blobID), b)
|
|
if err != nil {
|
|
return xerrors.Errorf("unable to store blob information in cache (%s): %w", blobID, err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("DB update error: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetArtifact gets artifact information such as image metadata from local cache
|
|
func (fs FSCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) {
|
|
var blob []byte
|
|
err := fs.db.View(func(tx *bolt.Tx) error {
|
|
artifactBucket := tx.Bucket([]byte(artifactBucket))
|
|
blob = artifactBucket.Get([]byte(artifactID))
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return types.ArtifactInfo{}, xerrors.Errorf("DB error: %w", err)
|
|
}
|
|
|
|
var info types.ArtifactInfo
|
|
if err := json.Unmarshal(blob, &info); err != nil {
|
|
return types.ArtifactInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err)
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
// DeleteBlobs removes blobs by IDs
|
|
func (fs FSCache) DeleteBlobs(blobIDs []string) error {
|
|
var errs error
|
|
err := fs.db.Update(func(tx *bolt.Tx) error {
|
|
bucket := tx.Bucket([]byte(blobBucket))
|
|
for _, blobID := range blobIDs {
|
|
if err := bucket.Delete([]byte(blobID)); err != nil {
|
|
errs = multierror.Append(errs, err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("DB delete error: %w", err)
|
|
}
|
|
return errs
|
|
}
|
|
|
|
// PutArtifact stores artifact information such as image metadata in local cache
|
|
func (fs FSCache) PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) (err error) {
|
|
b, err := json.Marshal(artifactInfo)
|
|
if err != nil {
|
|
return xerrors.Errorf("unable to marshal artifact JSON (%s): %w", artifactID, err)
|
|
}
|
|
|
|
err = fs.db.Update(func(tx *bolt.Tx) error {
|
|
artifactBucket := tx.Bucket([]byte(artifactBucket))
|
|
err = artifactBucket.Put([]byte(artifactID), b)
|
|
if err != nil {
|
|
return xerrors.Errorf("unable to store artifact information in cache (%s): %w", artifactID, err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("DB update error: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MissingBlobs returns missing blob IDs such as layer IDs
|
|
func (fs FSCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) {
|
|
var missingArtifact bool
|
|
var missingBlobIDs []string
|
|
err := fs.db.View(func(tx *bolt.Tx) error {
|
|
blobBucket := tx.Bucket([]byte(blobBucket))
|
|
for _, blobID := range blobIDs {
|
|
blobInfo, err := fs.getBlob(blobBucket, blobID)
|
|
if err != nil {
|
|
// error means cache missed blob info
|
|
missingBlobIDs = append(missingBlobIDs, blobID)
|
|
continue
|
|
}
|
|
if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion {
|
|
missingBlobIDs = append(missingBlobIDs, blobID)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return false, nil, xerrors.Errorf("DB error: %w", err)
|
|
}
|
|
|
|
// get artifact info
|
|
artifactInfo, err := fs.GetArtifact(artifactID)
|
|
if err != nil {
|
|
// error means cache missed artifact info
|
|
return true, missingBlobIDs, nil
|
|
}
|
|
if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion {
|
|
missingArtifact = true
|
|
}
|
|
return missingArtifact, missingBlobIDs, nil
|
|
}
|
|
|
|
// Close closes the database
|
|
func (fs FSCache) Close() error {
|
|
if err := fs.db.Close(); err != nil {
|
|
return xerrors.Errorf("unable to close DB: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Clear removes the database
|
|
func (fs FSCache) Clear() error {
|
|
if err := fs.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.RemoveAll(fs.directory); err != nil {
|
|
return xerrors.Errorf("failed to remove cache: %w", err)
|
|
}
|
|
return nil
|
|
}
|