github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/cache/fs.go (about)

     1  package cache
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"github.com/hashicorp/go-multierror"
     9  	bolt "go.etcd.io/bbolt"
    10  	"golang.org/x/xerrors"
    11  
    12  	"github.com/devseccon/trivy/pkg/fanal/types"
    13  )
    14  
    15  var _ Cache = &FSCache{}
    16  
    17  type FSCache struct {
    18  	db        *bolt.DB
    19  	directory string
    20  }
    21  
    22  func NewFSCache(cacheDir string) (FSCache, error) {
    23  	dir := filepath.Join(cacheDir, cacheDirName)
    24  	if err := os.MkdirAll(dir, 0700); err != nil {
    25  		return FSCache{}, xerrors.Errorf("failed to create cache dir: %w", err)
    26  	}
    27  
    28  	db, err := bolt.Open(filepath.Join(dir, "fanal.db"), 0600, nil)
    29  	if err != nil {
    30  		return FSCache{}, xerrors.Errorf("unable to open DB: %w", err)
    31  	}
    32  
    33  	err = db.Update(func(tx *bolt.Tx) error {
    34  		for _, bucket := range []string{artifactBucket, blobBucket} {
    35  			if _, err := tx.CreateBucketIfNotExists([]byte(bucket)); err != nil {
    36  				return xerrors.Errorf("unable to create %s bucket: %w", bucket, err)
    37  			}
    38  		}
    39  		return nil
    40  	})
    41  	if err != nil {
    42  		return FSCache{}, xerrors.Errorf("DB error: %w", err)
    43  	}
    44  
    45  	return FSCache{
    46  		db:        db,
    47  		directory: dir,
    48  	}, nil
    49  }
    50  
    51  // GetBlob gets blob information such as layer data from local cache
    52  func (fs FSCache) GetBlob(blobID string) (types.BlobInfo, error) {
    53  	var blobInfo types.BlobInfo
    54  	err := fs.db.View(func(tx *bolt.Tx) error {
    55  		var err error
    56  		blobBucket := tx.Bucket([]byte(blobBucket))
    57  		blobInfo, err = fs.getBlob(blobBucket, blobID)
    58  		if err != nil {
    59  			return xerrors.Errorf("failed to get blob from the cache: %w", err)
    60  		}
    61  		return nil
    62  	})
    63  	if err != nil {
    64  		return types.BlobInfo{}, xerrors.Errorf("DB error: %w", err)
    65  	}
    66  	return blobInfo, nil
    67  }
    68  
    69  func (fs FSCache) getBlob(blobBucket *bolt.Bucket, diffID string) (types.BlobInfo, error) {
    70  	b := blobBucket.Get([]byte(diffID))
    71  
    72  	var l types.BlobInfo
    73  	if err := json.Unmarshal(b, &l); err != nil {
    74  		return types.BlobInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err)
    75  	}
    76  	return l, nil
    77  }
    78  
    79  // PutBlob stores blob information such as layer information in local cache
    80  func (fs FSCache) PutBlob(blobID string, blobInfo types.BlobInfo) error {
    81  	b, err := json.Marshal(blobInfo)
    82  	if err != nil {
    83  		return xerrors.Errorf("unable to marshal blob JSON (%s): %w", blobID, err)
    84  	}
    85  	err = fs.db.Update(func(tx *bolt.Tx) error {
    86  		blobBucket := tx.Bucket([]byte(blobBucket))
    87  		err = blobBucket.Put([]byte(blobID), b)
    88  		if err != nil {
    89  			return xerrors.Errorf("unable to store blob information in cache (%s): %w", blobID, err)
    90  		}
    91  		return nil
    92  	})
    93  	if err != nil {
    94  		return xerrors.Errorf("DB update error: %w", err)
    95  	}
    96  	return nil
    97  }
    98  
    99  // GetArtifact gets artifact information such as image metadata from local cache
   100  func (fs FSCache) GetArtifact(artifactID string) (types.ArtifactInfo, error) {
   101  	var blob []byte
   102  	err := fs.db.View(func(tx *bolt.Tx) error {
   103  		artifactBucket := tx.Bucket([]byte(artifactBucket))
   104  		blob = artifactBucket.Get([]byte(artifactID))
   105  		return nil
   106  	})
   107  	if err != nil {
   108  		return types.ArtifactInfo{}, xerrors.Errorf("DB error: %w", err)
   109  	}
   110  
   111  	var info types.ArtifactInfo
   112  	if err := json.Unmarshal(blob, &info); err != nil {
   113  		return types.ArtifactInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err)
   114  	}
   115  	return info, nil
   116  }
   117  
   118  // DeleteBlobs removes blobs by IDs
   119  func (fs FSCache) DeleteBlobs(blobIDs []string) error {
   120  	var errs error
   121  	err := fs.db.Update(func(tx *bolt.Tx) error {
   122  		bucket := tx.Bucket([]byte(blobBucket))
   123  		for _, blobID := range blobIDs {
   124  			if err := bucket.Delete([]byte(blobID)); err != nil {
   125  				errs = multierror.Append(errs, err)
   126  			}
   127  		}
   128  		return nil
   129  	})
   130  	if err != nil {
   131  		return xerrors.Errorf("DB delete error: %w", err)
   132  	}
   133  	return errs
   134  }
   135  
   136  // PutArtifact stores artifact information such as image metadata in local cache
   137  func (fs FSCache) PutArtifact(artifactID string, artifactInfo types.ArtifactInfo) (err error) {
   138  	b, err := json.Marshal(artifactInfo)
   139  	if err != nil {
   140  		return xerrors.Errorf("unable to marshal artifact JSON (%s): %w", artifactID, err)
   141  	}
   142  
   143  	err = fs.db.Update(func(tx *bolt.Tx) error {
   144  		artifactBucket := tx.Bucket([]byte(artifactBucket))
   145  		err = artifactBucket.Put([]byte(artifactID), b)
   146  		if err != nil {
   147  			return xerrors.Errorf("unable to store artifact information in cache (%s): %w", artifactID, err)
   148  		}
   149  		return nil
   150  	})
   151  	if err != nil {
   152  		return xerrors.Errorf("DB update error: %w", err)
   153  	}
   154  	return nil
   155  }
   156  
   157  // MissingBlobs returns missing blob IDs such as layer IDs
   158  func (fs FSCache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) {
   159  	var missingArtifact bool
   160  	var missingBlobIDs []string
   161  	err := fs.db.View(func(tx *bolt.Tx) error {
   162  		blobBucket := tx.Bucket([]byte(blobBucket))
   163  		for _, blobID := range blobIDs {
   164  			blobInfo, err := fs.getBlob(blobBucket, blobID)
   165  			if err != nil {
   166  				// error means cache missed blob info
   167  				missingBlobIDs = append(missingBlobIDs, blobID)
   168  				continue
   169  			}
   170  			if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion {
   171  				missingBlobIDs = append(missingBlobIDs, blobID)
   172  			}
   173  		}
   174  		return nil
   175  	})
   176  	if err != nil {
   177  		return false, nil, xerrors.Errorf("DB error: %w", err)
   178  	}
   179  
   180  	// get artifact info
   181  	artifactInfo, err := fs.GetArtifact(artifactID)
   182  	if err != nil {
   183  		// error means cache missed artifact info
   184  		return true, missingBlobIDs, nil
   185  	}
   186  	if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion {
   187  		missingArtifact = true
   188  	}
   189  	return missingArtifact, missingBlobIDs, nil
   190  }
   191  
   192  // Close closes the database
   193  func (fs FSCache) Close() error {
   194  	if err := fs.db.Close(); err != nil {
   195  		return xerrors.Errorf("unable to close DB: %w", err)
   196  	}
   197  	return nil
   198  }
   199  
   200  // Clear removes the database
   201  func (fs FSCache) Clear() error {
   202  	if err := fs.Close(); err != nil {
   203  		return err
   204  	}
   205  	if err := os.RemoveAll(fs.directory); err != nil {
   206  		return xerrors.Errorf("failed to remove cache: %w", err)
   207  	}
   208  	return nil
   209  }