github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/cache/s3.go (about) 1 package cache 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 9 "github.com/aws/aws-sdk-go-v2/aws" 10 "github.com/aws/aws-sdk-go-v2/feature/s3/manager" 11 "github.com/aws/aws-sdk-go-v2/service/s3" 12 "github.com/hashicorp/go-multierror" 13 "golang.org/x/xerrors" 14 15 "github.com/devseccon/trivy/pkg/fanal/types" 16 ) 17 18 var _ Cache = &S3Cache{} 19 20 type s3API interface { 21 HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) 22 PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) 23 DeleteBucket(ctx context.Context, params *s3.DeleteBucketInput, optFns ...func(*s3.Options)) (*s3.DeleteBucketOutput, error) 24 } 25 26 type S3Cache struct { 27 s3Client s3API 28 downloader *manager.Downloader 29 bucketName string 30 prefix string 31 } 32 33 func NewS3Cache(bucketName, prefix string, api s3API, downloaderAPI *manager.Downloader) S3Cache { 34 return S3Cache{ 35 s3Client: api, 36 downloader: downloaderAPI, 37 bucketName: bucketName, 38 prefix: prefix, 39 } 40 } 41 42 func (c S3Cache) PutArtifact(artifactID string, artifactConfig types.ArtifactInfo) (err error) { 43 key := fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID) 44 if err := c.put(key, artifactConfig); err != nil { 45 return xerrors.Errorf("unable to store artifact information in cache (%s): %w", artifactID, err) 46 } 47 return nil 48 } 49 50 func (c S3Cache) DeleteBlobs(blobIDs []string) error { 51 var errs error 52 for _, blobID := range blobIDs { 53 key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID) 54 input := &s3.DeleteBucketInput{Bucket: aws.String(key)} 55 if _, err := c.s3Client.DeleteBucket(context.TODO(), input); err != nil { 56 errs = multierror.Append(errs, err) 57 } 58 } 59 return errs 60 } 61 62 func (c S3Cache) PutBlob(blobID string, blobInfo types.BlobInfo) error { 63 key := fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID) 64 if err := c.put(key, blobInfo); err != nil { 65 return xerrors.Errorf("unable to store blob information in cache (%s): %w", blobID, err) 66 } 67 return nil 68 } 69 70 func (c S3Cache) put(key string, body interface{}) (err error) { 71 b, err := json.Marshal(body) 72 if err != nil { 73 return err 74 } 75 params := &s3.PutObjectInput{ 76 Bucket: aws.String(c.bucketName), 77 Key: aws.String(key), 78 Body: bytes.NewReader(b), 79 } 80 _, err = c.s3Client.PutObject(context.TODO(), params) 81 if err != nil { 82 return xerrors.Errorf("unable to put object: %w", err) 83 } 84 // Index file due S3 caveat read after write consistency 85 _, err = c.s3Client.PutObject(context.TODO(), &s3.PutObjectInput{ 86 Bucket: aws.String(c.bucketName), 87 Key: aws.String(fmt.Sprintf("%s.index", key)), 88 }) 89 if err != nil { 90 return xerrors.Errorf("unable to put index object: %w", err) 91 } 92 return nil 93 } 94 95 func (c S3Cache) GetBlob(blobID string) (types.BlobInfo, error) { 96 var blobInfo types.BlobInfo 97 buf := manager.NewWriteAtBuffer([]byte{}) 98 _, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{ 99 Bucket: aws.String(c.bucketName), 100 Key: aws.String(fmt.Sprintf("%s/%s/%s", blobBucket, c.prefix, blobID)), 101 }) 102 if err != nil { 103 return types.BlobInfo{}, xerrors.Errorf("failed to get blob from the cache: %w", err) 104 } 105 err = json.Unmarshal(buf.Bytes(), &blobInfo) 106 if err != nil { 107 return types.BlobInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) 108 } 109 return blobInfo, nil 110 } 111 112 func (c S3Cache) GetArtifact(artifactID string) (types.ArtifactInfo, error) { 113 var info types.ArtifactInfo 114 buf := manager.NewWriteAtBuffer([]byte{}) 115 _, err := c.downloader.Download(context.TODO(), buf, &s3.GetObjectInput{ 116 Bucket: aws.String(c.bucketName), 117 Key: aws.String(fmt.Sprintf("%s/%s/%s", artifactBucket, c.prefix, artifactID)), 118 }) 119 if err != nil { 120 return types.ArtifactInfo{}, xerrors.Errorf("failed to get artifact from the cache: %w", err) 121 } 122 err = json.Unmarshal(buf.Bytes(), &info) 123 if err != nil { 124 return types.ArtifactInfo{}, xerrors.Errorf("JSON unmarshal error: %w", err) 125 } 126 return info, nil 127 } 128 129 func (c S3Cache) getIndex(key, keyType string) error { 130 _, err := c.s3Client.HeadObject(context.TODO(), &s3.HeadObjectInput{ 131 Key: aws.String(fmt.Sprintf("%s/%s/%s.index", keyType, c.prefix, key)), 132 Bucket: &c.bucketName, 133 }) 134 if err != nil { 135 return xerrors.Errorf("failed to get index from the cache: %w", err) 136 } 137 return nil 138 } 139 140 func (c S3Cache) MissingBlobs(artifactID string, blobIDs []string) (bool, []string, error) { 141 var missingArtifact bool 142 var missingBlobIDs []string 143 for _, blobID := range blobIDs { 144 err := c.getIndex(blobID, blobBucket) 145 if err != nil { 146 // error means cache missed blob info 147 missingBlobIDs = append(missingBlobIDs, blobID) 148 continue 149 } 150 blobInfo, err := c.GetBlob(blobID) 151 if err != nil { 152 return true, missingBlobIDs, xerrors.Errorf("the blob object (%s) doesn't exist in S3 even though the index file exists: %w", blobID, err) 153 } 154 if blobInfo.SchemaVersion != types.BlobJSONSchemaVersion { 155 missingBlobIDs = append(missingBlobIDs, blobID) 156 } 157 } 158 // get artifact info 159 err := c.getIndex(artifactID, artifactBucket) 160 // error means cache missed artifact info 161 if err != nil { 162 return true, missingBlobIDs, nil 163 } 164 artifactInfo, err := c.GetArtifact(artifactID) 165 if err != nil { 166 return true, missingBlobIDs, xerrors.Errorf("the artifact object (%s) doesn't exist in S3 even though the index file exists: %w", artifactID, err) 167 } 168 if artifactInfo.SchemaVersion != types.ArtifactJSONSchemaVersion { 169 missingArtifact = true 170 } 171 return missingArtifact, missingBlobIDs, nil 172 } 173 174 func (c S3Cache) Close() error { 175 return nil 176 } 177 178 func (c S3Cache) Clear() error { 179 return nil 180 }