From 8698886bf743ac3157fa3315a26500081cf05468 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 29 Oct 2026 22:40:50 -0400 Subject: [PATCH] revert: drop the squash-only image read and bump stereoscope stereoscope is replacing `image.WithImageSquashOnly` with lazy layer squashes, where every layer keeps a usable squash. That removes the need for `GetSourceConfig.WithImageSquashOnly` and the scope guard in `FileResolver`, so both go. - bumps stereoscope to `feat/lazy-layer-squashes` (anchore/stereoscope `43c8f7e`), pending that PR merging Signed-off-by: Alex Goodman --- cmd/syft/internal/commands/scan.go | 4 +- go.mod | 5 +- go.sum | 10 ++- syft/get_source_config.go | 8 --- .../sourceproviders/source_provider_config.go | 9 --- .../sourceproviders/source_providers.go | 5 +- syft/source/stereoscopesource/image_source.go | 15 ----- .../image_source_provider.go | 6 -- .../image_source_provider_test.go | 64 ------------------- 9 files changed, 9 insertions(+), 117 deletions(-) delete mode 100644 syft/source/stereoscopesource/image_source_provider_test.go diff --git a/cmd/syft/internal/commands/scan.go b/cmd/syft/internal/commands/scan.go index 168568b86..7f92491ca 100644 --- a/cmd/syft/internal/commands/scan.go +++ b/cmd/syft/internal/commands/scan.go @@ -239,9 +239,7 @@ func getSource(ctx context.Context, opts *options.Catalog, userInput string, sou }). WithBasePath(opts.Source.BasePath). WithSources(sources...). - WithDefaultImagePullSource(opts.Source.Image.DefaultPullSource). - // only the squashed scope reads from the image squash alone, every other scope needs each layer's squash - WithImageSquashOnly(source.ParseScope(opts.Scope) == source.SquashedScope) + WithDefaultImagePullSource(opts.Source.Image.DefaultPullSource) var err error var platform *image.Platform diff --git a/go.mod b/go.mod index f9125521f..61863c2ba 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/anchore/go-sync v0.1.2 github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b github.com/anchore/packageurl-go v0.2.0 - github.com/anchore/stereoscope v0.3.5-0.20261029161941-a11ca27a9368 + github.com/anchore/stereoscope v0.3.5-0.20261030023738-43c8f7e4fc46 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be github.com/aquasecurity/go-pep440-version v0.0.1 github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef @@ -156,7 +156,6 @@ require ( github.com/aws/smithy-go v1.28.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect @@ -205,7 +204,7 @@ require ( github.com/gabriel-vasile/mimetype v1.4.15 // indirect github.com/gkampitakis/ciinfo v0.3.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-jose/go-jose/v4 v4.1.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.6 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect diff --git a/go.sum b/go.sum index 39056e96a..46d056b3c 100644 --- a/go.sum +++ b/go.sum @@ -150,8 +150,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk= github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE= -github.com/anchore/stereoscope v0.3.5-0.20261029161941-a11ca27a9368 h1:PBceoE+ilruFEpJAKabIv8DNjz0RPOBTsGdAfD+xaX8= -github.com/anchore/stereoscope v0.3.5-0.20261029161941-a11ca27a9368/go.mod h1:hXmgO16FulOp3wnzsE6IoMyB+aUjJDIT6CgpCTWsQRw= +github.com/anchore/stereoscope v0.3.5-0.20261030023738-43c8f7e4fc46 h1:0ZL8d1vjsJKNUu480/F8x41O701vbQvYgZFmoiKho7o= +github.com/anchore/stereoscope v0.3.5-0.20261030023738-43c8f7e4fc46/go.mod h1:sfJpowN3ZfLVtx0QTc+3/kdM7ub37Hpi9aBVmdANqz4= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= @@ -217,8 +217,6 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= -github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -443,8 +441,8 @@ github.com/go-git/go-git/v5 v5.19.3/go.mod h1:Ye4C8dVigkqrv9UsB4NMdSLV4yAIkLiIhW github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= -github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.6 h1:sNRkftxFJxwsILiQukqp5z83QBSmntVwmgRY7AqBDO4= +github.com/go-jose/go-jose/v4 v4.1.6/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= diff --git a/syft/get_source_config.go b/syft/get_source_config.go index f7528951f..735dba516 100644 --- a/syft/get_source_config.go +++ b/syft/get_source_config.go @@ -57,14 +57,6 @@ func (c *GetSourceConfig) WithBasePath(basePath string) *GetSourceConfig { return c } -// WithImageSquashOnly reads container images keeping only the image squash, which cuts memory sharply on -// images with many layers. Only use it when the source will be resolved with source.SquashedScope: -// any other scope returns an error from FileResolver. -func (c *GetSourceConfig) WithImageSquashOnly(squashOnly bool) *GetSourceConfig { - c.SourceProviderConfig = c.SourceProviderConfig.WithImageSquashOnly(squashOnly) - return c -} - func (c *GetSourceConfig) WithSources(sources ...string) *GetSourceConfig { c.Sources = sources return c diff --git a/syft/source/sourceproviders/source_provider_config.go b/syft/source/sourceproviders/source_provider_config.go index b1bac097e..dbc48bc0b 100644 --- a/syft/source/sourceproviders/source_provider_config.go +++ b/syft/source/sourceproviders/source_provider_config.go @@ -15,10 +15,6 @@ type Config struct { Exclude source.ExcludeConfig DigestAlgorithms []crypto.Hash BasePath string - // ImageSquashOnly reads container images keeping only the image squash, dropping the squash every - // lower layer would otherwise hold. This cuts memory sharply on images with many layers, but the - // resulting source only supports the squashed scope. - ImageSquashOnly bool } func (c *Config) WithAlias(alias source.Alias) *Config { @@ -51,11 +47,6 @@ func (c *Config) WithBasePath(basePath string) *Config { return c } -func (c *Config) WithImageSquashOnly(squashOnly bool) *Config { - c.ImageSquashOnly = squashOnly - return c -} - func DefaultConfig() *Config { return &Config{ DigestAlgorithms: []crypto.Hash{ diff --git a/syft/source/sourceproviders/source_providers.go b/syft/source/sourceproviders/source_providers.go index f6eeca0ea..fcaf7e697 100644 --- a/syft/source/sourceproviders/source_providers.go +++ b/syft/source/sourceproviders/source_providers.go @@ -67,9 +67,8 @@ func stereoscopeSourceProviders(userInput string, cfg *Config) collections.Tagge Platform: cfg.Platform, Registry: registry, }, - Alias: cfg.Alias, - Exclude: cfg.Exclude, - ImageSquashOnly: cfg.ImageSquashOnly, + Alias: cfg.Alias, + Exclude: cfg.Exclude, }) return stereoscopeProviders } diff --git a/syft/source/stereoscopesource/image_source.go b/syft/source/stereoscopesource/image_source.go index 27f4b4924..c6dc29390 100644 --- a/syft/source/stereoscopesource/image_source.go +++ b/syft/source/stereoscopesource/image_source.go @@ -99,10 +99,6 @@ func (s stereoscopeImageSource) FileResolver(scope source.Scope) (file.Resolver, var res file.Resolver var err error - if scope != source.SquashedScope && !hasLayerSquashes(s.image) { - return nil, fmt.Errorf("the %q scope needs every layer's squashed tree, but the image was read keeping only the image squash", scope) - } - switch scope { case source.SquashedScope: res, err = fileresolver.NewFromContainerImageSquash(s.image) @@ -126,17 +122,6 @@ func (s stereoscopeImageSource) FileResolver(scope source.Scope) (file.Resolver, return res, nil } -// hasLayerSquashes reports whether every layer kept its squashed tree, which is not the case for an image -// read with image.WithImageSquashOnly (only the top layer keeps one). -func hasLayerSquashes(img *image.Image) bool { - for _, l := range img.Layers { - if l.SquashedTree == nil { - return false - } - } - return true -} - func (s stereoscopeImageSource) Close() error { if s.image == nil { return nil diff --git a/syft/source/stereoscopesource/image_source_provider.go b/syft/source/stereoscopesource/image_source_provider.go index 0cee24694..1b1f78e4e 100644 --- a/syft/source/stereoscopesource/image_source_provider.go +++ b/syft/source/stereoscopesource/image_source_provider.go @@ -15,8 +15,6 @@ type ProviderConfig struct { StereoscopeImageProviderConfig stereoscope.ImageProviderConfig Exclude source.ExcludeConfig Alias source.Alias - // ImageSquashOnly reads the image keeping only the image squash, see image.WithImageSquashOnly - ImageSquashOnly bool } type stereoscopeImageSourceProvider struct { @@ -31,10 +29,6 @@ func (l stereoscopeImageSourceProvider) Name() string { } func (l stereoscopeImageSourceProvider) Provide(ctx context.Context) (source.Source, error) { - if l.cfg.ImageSquashOnly { - // scoped to this read only, the caller's ctx may be shared with reads that need every layer squash - ctx = image.WithImageSquashOnly(ctx) - } img, err := l.stereoscopeProvider.Provide(ctx) if err != nil { return nil, err diff --git a/syft/source/stereoscopesource/image_source_provider_test.go b/syft/source/stereoscopesource/image_source_provider_test.go deleted file mode 100644 index b1943850d..000000000 --- a/syft/source/stereoscopesource/image_source_provider_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package stereoscopesource - -import ( - "context" - "path/filepath" - "testing" - - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/random" - "github.com/google/go-containerregistry/pkg/v1/tarball" - "github.com/stretchr/testify/require" - - "github.com/anchore/stereoscope" - "github.com/anchore/stereoscope/pkg/image" - "github.com/anchore/syft/syft/source" -) - -func Test_stereoscopeImageSourceProvider_imageSquashOnly(t *testing.T) { - img, err := random.Image(1024, 3) - require.NoError(t, err) - tag, err := name.NewTag("localhost/squash-only:latest") - require.NoError(t, err) - archive := filepath.Join(t.TempDir(), "image.tar") - require.NoError(t, tarball.WriteToFile(archive, tag, img)) - - provide := func(t *testing.T, squashOnly bool) source.Source { - providers := Providers(ProviderConfig{ - StereoscopeImageProviderConfig: stereoscope.ImageProviderConfig{UserInput: archive}, - ImageSquashOnly: squashOnly, - }) - for _, p := range providers { - if p.Value.Name() != image.DockerTarballSource { - continue - } - src, err := p.Value.Provide(context.Background()) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, src.Close()) }) - return src - } - t.Fatalf("no %s provider", image.DockerTarballSource) - return nil - } - - tests := []struct { - name string - squashOnly bool - scope source.Scope - wantErr require.ErrorAssertionFunc - }{ - {name: "full read, squashed", scope: source.SquashedScope, wantErr: require.NoError}, - {name: "full read, all layers", scope: source.AllLayersScope, wantErr: require.NoError}, - {name: "full read, deep squashed", scope: source.DeepSquashedScope, wantErr: require.NoError}, - {name: "squash only, squashed", squashOnly: true, scope: source.SquashedScope, wantErr: require.NoError}, - // the per-layer resolvers would dereference the dropped layer squashes, so refuse up front - {name: "squash only, all layers", squashOnly: true, scope: source.AllLayersScope, wantErr: require.Error}, - {name: "squash only, deep squashed", squashOnly: true, scope: source.DeepSquashedScope, wantErr: require.Error}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := provide(t, tt.squashOnly).FileResolver(tt.scope) - tt.wantErr(t, err) - }) - } -}