diff --git a/pkg/commands/artifact/inject.go b/pkg/commands/artifact/inject.go index bd4193c11a..244072beec 100644 --- a/pkg/commands/artifact/inject.go +++ b/pkg/commands/artifact/inject.go @@ -22,7 +22,7 @@ import ( // initializeDockerScanner is for container image scanning in standalone mode // e.g. dockerd, container registry, podman, etc. func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option) ( + localArtifactCache cache.Cache, dockerOpt types.DockerOption, artifactOption artifact.Option) ( scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneDockerSet) return scanner.Scanner{}, nil, nil @@ -31,26 +31,28 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach // initializeArchiveScanner is for container image archive scanning in standalone mode // e.g. docker save -o alpine.tar alpine:3.15 func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { + localArtifactCache cache.Cache, artifactOption artifact.Option) (scanner.Scanner, error) { wire.Build(scanner.StandaloneArchiveSet) return scanner.Scanner{}, nil } // initializeFilesystemScanner is for filesystem scanning in standalone mode func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + localArtifactCache cache.Cache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneFilesystemSet) return scanner.Scanner{}, nil, nil } func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + localArtifactCache cache.Cache, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneRepositorySet) return scanner.Scanner{}, nil, nil } func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, - localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + localArtifactCache cache.Cache, artifactOption artifact.Option) ( + scanner.Scanner, func(), error) { wire.Build(scanner.StandaloneSBOMSet) return scanner.Scanner{}, nil, nil } diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index d27bb6222f..aaa9a2707c 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -58,7 +58,7 @@ type ScannerConfig struct { // Cache ArtifactCache cache.ArtifactCache - LocalArtifactCache cache.LocalArtifactCache + LocalArtifactCache cache.Cache // Client/Server options RemoteOption client.ScannerOption diff --git a/pkg/commands/artifact/scanner.go b/pkg/commands/artifact/scanner.go index 4d69f96de5..180305dfa1 100644 --- a/pkg/commands/artifact/scanner.go +++ b/pkg/commands/artifact/scanner.go @@ -27,7 +27,8 @@ func imageStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Sc // archiveStandaloneScanner initializes an image archive scanner in standalone mode // $ trivy image --input alpine.tar func archiveStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + s, err := initializeArchiveScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, + conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize the archive scanner: %w", err) } @@ -65,7 +66,8 @@ func archiveRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scan // filesystemStandaloneScanner initializes a filesystem scanner in standalone mode func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, + conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err) } @@ -83,7 +85,8 @@ func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.S // filesystemStandaloneScanner initializes a repository scanner in standalone mode func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, + conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err) } @@ -92,7 +95,8 @@ func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scann // sbomStandaloneScanner initializes a SBOM scanner in standalone mode func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) { - s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption) + s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, + conf.ArtifactOption) if err != nil { return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err) } diff --git a/pkg/commands/artifact/wire_gen.go b/pkg/commands/artifact/wire_gen.go index 16cfc1d33b..01388f187b 100644 --- a/pkg/commands/artifact/wire_gen.go +++ b/pkg/commands/artifact/wire_gen.go @@ -29,14 +29,15 @@ import ( // initializeDockerScanner is for container image scanning in standalone mode // e.g. dockerd, container registry, podman, etc. -func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, dockerOpt types.DockerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, localArtifactCache cache.Cache, dockerOpt types.DockerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + applierApplier := applier.NewApplier(localArtifactCache, v...) detector := ospkg.Detector{} config := db.Config{} client := vulnerability.NewClient(config) localScanner := local.NewScanner(applierApplier, detector, client) - v := _wireValue - typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, dockerOpt, v...) + v2 := _wireValue2 + typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, dockerOpt, v2...) if err != nil { return scanner.Scanner{}, nil, err } @@ -52,13 +53,15 @@ func initializeDockerScanner(ctx context.Context, imageName string, artifactCach } var ( - _wireValue = []image.Option(nil) + _wireValue = []applier.Option(nil) + _wireValue2 = []image.Option(nil) ) // initializeArchiveScanner is for container image archive scanning in standalone mode // e.g. docker save -o alpine.tar alpine:3.15 -func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.Cache, artifactOption artifact.Option) (scanner.Scanner, error) { + v := _wireValue + applierApplier := applier.NewApplier(localArtifactCache, v...) detector := ospkg.Detector{} config := db.Config{} client := vulnerability.NewClient(config) @@ -76,8 +79,9 @@ func initializeArchiveScanner(ctx context.Context, filePath string, artifactCach } // initializeFilesystemScanner is for filesystem scanning in standalone mode -func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, localArtifactCache cache.Cache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + applierApplier := applier.NewApplier(localArtifactCache, v...) detector := ospkg.Detector{} config := db.Config{} client := vulnerability.NewClient(config) @@ -91,8 +95,9 @@ func initializeFilesystemScanner(ctx context.Context, path string, artifactCache }, nil } -func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeRepositoryScanner(ctx context.Context, url string, artifactCache cache.ArtifactCache, localArtifactCache cache.Cache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + applierApplier := applier.NewApplier(localArtifactCache, v...) detector := ospkg.Detector{} config := db.Config{} client := vulnerability.NewClient(config) @@ -107,8 +112,9 @@ func initializeRepositoryScanner(ctx context.Context, url string, artifactCache }, nil } -func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.LocalArtifactCache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, localArtifactCache cache.Cache, artifactOption artifact.Option) (scanner.Scanner, func(), error) { + v := _wireValue + applierApplier := applier.NewApplier(localArtifactCache, v...) detector := ospkg.Detector{} config := db.Config{} client := vulnerability.NewClient(config) @@ -125,9 +131,9 @@ func initializeSBOMScanner(ctx context.Context, filePath string, artifactCache c // initializeRemoteDockerScanner is for container image scanning in client/server mode // e.g. dockerd, container registry, podman, etc. func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, dockerOpt types.DockerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - v := _wireValue2 + v := _wireValue3 clientScanner := client.NewScanner(remoteScanOptions, v...) - v2 := _wireValue3 + v2 := _wireValue4 typesImage, cleanup, err := image.NewContainerImage(ctx, imageName, dockerOpt, v2...) if err != nil { return scanner.Scanner{}, nil, err @@ -144,14 +150,14 @@ func initializeRemoteDockerScanner(ctx context.Context, imageName string, artifa } var ( - _wireValue2 = []client.Option(nil) - _wireValue3 = []image.Option(nil) + _wireValue3 = []client.Option(nil) + _wireValue4 = []image.Option(nil) ) // initializeRemoteArchiveScanner is for container image archive scanning in client/server mode // e.g. docker save -o alpine.tar alpine:3.15 func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, error) { - v := _wireValue2 + v := _wireValue3 clientScanner := client.NewScanner(remoteScanOptions, v...) typesImage, err := image.NewArchiveImage(filePath) if err != nil { @@ -167,7 +173,7 @@ func initializeRemoteArchiveScanner(ctx context.Context, filePath string, artifa // initializeRemoteFilesystemScanner is for filesystem scanning in client/server mode func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - v := _wireValue2 + v := _wireValue3 clientScanner := client.NewScanner(remoteScanOptions, v...) artifactArtifact, err := local2.NewArtifact(path, artifactCache, artifactOption) if err != nil { @@ -180,7 +186,7 @@ func initializeRemoteFilesystemScanner(ctx context.Context, path string, artifac // initializeRemoteSBOMScanner is for sbom scanning in client/server mode func initializeRemoteSBOMScanner(ctx context.Context, path string, artifactCache cache.ArtifactCache, remoteScanOptions client.ScannerOption, artifactOption artifact.Option) (scanner.Scanner, func(), error) { - v := _wireValue2 + v := _wireValue3 clientScanner := client.NewScanner(remoteScanOptions, v...) artifactArtifact, err := sbom.NewArtifact(path, artifactCache, artifactOption) if err != nil { diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index a1ae16db3b..2056e6779d 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -24,7 +24,7 @@ import ( // SuperSet binds cache dependencies var SuperSet = wire.NewSet( cache.NewFSCache, - wire.Bind(new(cache.LocalArtifactCache), new(cache.FSCache)), + wire.Bind(new(cache.Cache), new(cache.FSCache)), NewCache, ) diff --git a/pkg/fanal/applier/applier.go b/pkg/fanal/applier/applier.go index 0d6b422855..aade1df418 100644 --- a/pkg/fanal/applier/applier.go +++ b/pkg/fanal/applier/applier.go @@ -1,22 +1,60 @@ package applier import ( + "crypto/sha256" + "encoding/json" + + "github.com/opencontainers/go-digest" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/cache" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" ) type Applier struct { - cache cache.LocalArtifactCache + cache cache.Cache + + // cacheMergedLayer is a flag to determine if the merged layer is cached or not. + // This flag is for tools importing Trivy as a library, not used in Trivy itself. + cacheMergedLayer bool } -func NewApplier(c cache.LocalArtifactCache) Applier { - return Applier{cache: c} +type Option func(*Applier) + +func WithCacheMergedLayer(b bool) Option { + return func(a *Applier) { + a.cacheMergedLayer = b + } +} + +func NewApplier(c cache.Cache, opts ...Option) Applier { + a := &Applier{ + cache: c, + cacheMergedLayer: false, + } + for _, opt := range opts { + opt(a) + } + return *a } func (a Applier) ApplyLayers(imageID string, layerKeys []string) (types.ArtifactDetail, error) { + var mergedKey string + + // Try to restore the merged layer if the feature is enabled + if a.cacheMergedLayer { + var err error + mergedKey, err = calcMergedKey(layerKeys) + if err != nil { + return types.ArtifactDetail{}, xerrors.Errorf("failed to calculate a merged key: %w", err) + } + if b, err := a.cache.GetBlob(mergedKey); err == nil { + return b.ToArtifactDetail(), nil + } + } + var layers []types.BlobInfo for _, key := range layerKeys { blob, _ := a.cache.GetBlob(key) // nolint @@ -36,5 +74,26 @@ func (a Applier) ApplyLayers(imageID string, layerKeys []string) (types.Artifact imageInfo, _ := a.cache.GetArtifact(imageID) // nolint mergedLayer.HistoryPackages = imageInfo.HistoryPackages + // Store the merged layer if the feature is enabled + if a.cacheMergedLayer { + if err := a.cache.PutBlob(mergedKey, mergedLayer.ToBlobInfo()); err != nil { + log.Logger.Error("Unable to cache the merged layer: %s", err) + } + } + return mergedLayer, nil } + +func calcMergedKey(layerKeys []string) (string, error) { + if len(layerKeys) == 1 { + return layerKeys[0], nil + } + + h := sha256.New() + if err := json.NewEncoder(h).Encode(layerKeys); err != nil { + return "", xerrors.Errorf("json error: %w", err) + } + + d := digest.NewDigest(digest.SHA256, h) + return d.String(), nil +} diff --git a/pkg/fanal/applier/applier_test.go b/pkg/fanal/applier/applier_test.go index 641de97ab3..c69e88d810 100644 --- a/pkg/fanal/applier/applier_test.go +++ b/pkg/fanal/applier/applier_test.go @@ -24,8 +24,8 @@ func TestApplier_ApplyLayers(t *testing.T) { tests := []struct { name string args args - getLayerExpectations []cache.LocalArtifactCacheGetBlobExpectation - getImageExpectations []cache.LocalArtifactCacheGetArtifactExpectation + getLayerExpectations []cache.CacheGetBlobExpectation + getImageExpectations []cache.CacheGetArtifactExpectation want types.ArtifactDetail wantErr string }{ @@ -39,12 +39,12 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, }, - getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + getLayerExpectations: []cache.CacheGetBlobExpectation{ { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", @@ -70,10 +70,10 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", @@ -98,10 +98,10 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", @@ -126,12 +126,12 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, }, - getImageExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{ + getImageExpectations: []cache.CacheGetArtifactExpectation{ { - Args: cache.LocalArtifactCacheGetArtifactArgs{ + Args: cache.CacheGetArtifactArgs{ ArtifactID: "sha256:4791503518dff090d6a82f7a5c1fd71c41146920e2562fb64308e17ab6834b7e", }, - Returns: cache.LocalArtifactCacheGetArtifactReturns{ + Returns: cache.CacheGetArtifactReturns{ ArtifactInfo: types.ArtifactInfo{ SchemaVersion: 1, }, @@ -193,12 +193,12 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", }, }, - getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + getLayerExpectations: []cache.CacheGetBlobExpectation{ { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72", @@ -223,12 +223,12 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, }, - getImageExpectations: []cache.LocalArtifactCacheGetArtifactExpectation{ + getImageExpectations: []cache.CacheGetArtifactExpectation{ { - Args: cache.LocalArtifactCacheGetArtifactArgs{ + Args: cache.CacheGetArtifactArgs{ ArtifactID: "sha256:3bb70bd5fb37e05b8ecaaace5d6a6b5ec7834037c07ecb5907355c23ab70352d", }, - Returns: cache.LocalArtifactCacheGetArtifactReturns{ + Returns: cache.CacheGetArtifactReturns{ ArtifactInfo: types.ArtifactInfo{ SchemaVersion: 1, HistoryPackages: []types.Package{ @@ -311,12 +311,12 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, }, - getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + getLayerExpectations: []cache.CacheGetBlobExpectation{ { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{BlobInfo: types.BlobInfo{}}, + Returns: cache.CacheGetBlobReturns{BlobInfo: types.BlobInfo{}}, }, }, wantErr: "layer cache missing", @@ -328,12 +328,12 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, }, - getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + getLayerExpectations: []cache.CacheGetBlobExpectation{ { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{BlobInfo: types.BlobInfo{}}, + Returns: cache.CacheGetBlobReturns{BlobInfo: types.BlobInfo{}}, }, }, wantErr: "layer cache missing", @@ -348,12 +348,12 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, }, - getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + getLayerExpectations: []cache.CacheGetBlobExpectation{ { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", @@ -375,10 +375,10 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", @@ -403,10 +403,10 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:beee9f30bc1f711043e78d4a2be0668955d4b761d587d6f60c2c8dc081efb203", @@ -482,12 +482,12 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, }, - getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + getLayerExpectations: []cache.CacheGetBlobExpectation{ { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, OS: &types.OS{ @@ -515,12 +515,12 @@ func TestApplier_ApplyLayers(t *testing.T) { "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", }, }, - getLayerExpectations: []cache.LocalArtifactCacheGetBlobExpectation{ + getLayerExpectations: []cache.CacheGetBlobExpectation{ { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", @@ -558,10 +558,10 @@ func TestApplier_ApplyLayers(t *testing.T) { }, }, { - Args: cache.LocalArtifactCacheGetBlobArgs{ + Args: cache.CacheGetBlobArgs{ BlobID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", }, - Returns: cache.LocalArtifactCacheGetBlobReturns{ + Returns: cache.CacheGetBlobReturns{ BlobInfo: types.BlobInfo{ SchemaVersion: 1, Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", @@ -677,7 +677,7 @@ func TestApplier_ApplyLayers(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := new(cache.MockLocalArtifactCache) + c := new(cache.MockCache) c.ApplyGetBlobExpectations(tt.getLayerExpectations) c.ApplyGetArtifactExpectations(tt.getImageExpectations) diff --git a/pkg/fanal/cache/mock_cache.go b/pkg/fanal/cache/mock_cache.go index bb7623b0e6..23611d4d65 100644 --- a/pkg/fanal/cache/mock_cache.go +++ b/pkg/fanal/cache/mock_cache.go @@ -3,9 +3,8 @@ package cache import ( - mock "github.com/stretchr/testify/mock" - types "github.com/aquasecurity/trivy/pkg/fanal/types" + mock "github.com/stretchr/testify/mock" ) // MockCache is an autogenerated mock type for the Cache type @@ -79,43 +78,43 @@ func (_m *MockCache) Close() error { return r0 } -type CacheDeleteBlobArgs struct { - BlobID string - BlobIDAnything bool +type CacheDeleteBlobsArgs struct { + BlobIDs []string + BlobIDsAnything bool } -type CacheDeleteBlobReturns struct { +type CacheDeleteBlobsReturns struct { _a0 error } -type CacheDeleteBlobExpectation struct { - Args CacheDeleteBlobArgs - Returns CacheDeleteBlobReturns +type CacheDeleteBlobsExpectation struct { + Args CacheDeleteBlobsArgs + Returns CacheDeleteBlobsReturns } -func (_m *MockCache) ApplyDeleteBlobExpectation(e CacheDeleteBlobExpectation) { +func (_m *MockCache) ApplyDeleteBlobsExpectation(e CacheDeleteBlobsExpectation) { var args []interface{} - if e.Args.BlobIDAnything { + if e.Args.BlobIDsAnything { args = append(args, mock.Anything) } else { - args = append(args, e.Args.BlobID) + args = append(args, e.Args.BlobIDs) } - _m.On("DeleteBlob", args...).Return(e.Returns._a0) + _m.On("DeleteBlobs", args...).Return(e.Returns._a0) } -func (_m *MockCache) ApplyDeleteBlobExpectations(expectations []CacheDeleteBlobExpectation) { +func (_m *MockCache) ApplyDeleteBlobsExpectations(expectations []CacheDeleteBlobsExpectation) { for _, e := range expectations { - _m.ApplyDeleteBlobExpectation(e) + _m.ApplyDeleteBlobsExpectation(e) } } -// DeleteBlob provides a mock function with given fields: blobID -func (_m *MockCache) DeleteBlob(blobID string) error { - ret := _m.Called(blobID) +// DeleteBlobs provides a mock function with given fields: blobIDs +func (_m *MockCache) DeleteBlobs(blobIDs []string) error { + ret := _m.Called(blobIDs) var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(blobID) + if rf, ok := ret.Get(0).(func([]string) error); ok { + r0 = rf(blobIDs) } else { r0 = ret.Error(0) } diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index c2699a014d..e1e6aea896 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -155,6 +155,25 @@ type BlobInfo struct { CustomResources []CustomResource `json:",omitempty"` } +// ToArtifactDetail is used to restore the merged layer from cache. +func (b *BlobInfo) ToArtifactDetail() ArtifactDetail { + var pkgs []Package + // The size must be 1 as this BlobInfo contains the merged layer. + if len(b.PackageInfos) == 1 { + pkgs = b.PackageInfos[0].Packages + } + return ArtifactDetail{ + OS: b.OS, + Repository: b.Repository, + Packages: pkgs, + Applications: b.Applications, + Misconfigurations: b.Misconfigurations, + Secrets: b.Secrets, + Licenses: b.Licenses, + CustomResources: b.CustomResources, + } +} + // ArtifactDetail is generated by applying blobs type ArtifactDetail struct { OS *OS `json:",omitempty"` @@ -173,6 +192,25 @@ type ArtifactDetail struct { CustomResources []CustomResource `json:",omitempty"` } +// ToBlobInfo is used to store a merged layer in cache. +func (a *ArtifactDetail) ToBlobInfo() BlobInfo { + return BlobInfo{ + OS: a.OS, + Repository: a.Repository, + PackageInfos: []PackageInfo{ + { + FilePath: "merged", // Set a dummy file path + Packages: a.Packages, + }, + }, + Applications: a.Applications, + Misconfigurations: a.Misconfigurations, + Secrets: a.Secrets, + Licenses: a.Licenses, + CustomResources: a.CustomResources, + } +} + // CustomResource holds the analysis result from a custom analyzer. // It is for extensibility and not used in OSS. type CustomResource struct { diff --git a/pkg/rpc/server/inject.go b/pkg/rpc/server/inject.go index 253a01a819..33741a248c 100644 --- a/pkg/rpc/server/inject.go +++ b/pkg/rpc/server/inject.go @@ -8,7 +8,7 @@ import ( "github.com/google/wire" ) -func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServer { +func initializeScanServer(localArtifactCache cache.Cache) *ScanServer { wire.Build(ScanSuperSet) return &ScanServer{} } diff --git a/pkg/rpc/server/wire_gen.go b/pkg/rpc/server/wire_gen.go index 5b83089357..e44546395c 100644 --- a/pkg/rpc/server/wire_gen.go +++ b/pkg/rpc/server/wire_gen.go @@ -17,8 +17,9 @@ import ( // Injectors from inject.go: -func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServer { - applierApplier := applier.NewApplier(localArtifactCache) +func initializeScanServer(localArtifactCache cache.Cache) *ScanServer { + v := _wireValue + applierApplier := applier.NewApplier(localArtifactCache, v...) detector := ospkg.Detector{} config := db.Config{} client := vulnerability.NewClient(config) @@ -26,3 +27,7 @@ func initializeScanServer(localArtifactCache cache.LocalArtifactCache) *ScanServ scanServer := NewScanServer(scanner) return scanServer } + +var ( + _wireValue = []applier.Option(nil) +) diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index c40f13c515..819c1493c8 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -40,6 +40,7 @@ var ( // SuperSet binds dependencies for Local scan var SuperSet = wire.NewSet( vulnerability.SuperSet, + wire.Value([]applier.Option(nil)), // functional options applier.NewApplier, wire.Bind(new(Applier), new(applier.Applier)), wire.Struct(new(ospkgDetector.Detector)),