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 }