Skip to content

Commit d5bfb09

Browse files
committed
allow for concurrent layer reads
Signed-off-by: Alex Goodman <[email protected]>
1 parent e6d086e commit d5bfb09

File tree

10 files changed

+49
-33
lines changed

10 files changed

+49
-33
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,6 @@ require (
121121
gopkg.in/yaml.v3 v3.0.1 // indirect
122122
)
123123

124+
require github.com/anchore/go-sync v0.0.0-20240306205607-3ee6b614d624
125+
124126
replace github.com/gabriel-vasile/mimetype v1.4.4 => github.com/anchore/mimetype v0.0.0-20240710165720-f966690755a5

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5
2323
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
2424
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 h1:imgMA0gN0TZx7PSa/pdWqXadBvrz8WsN6zySzCe4XX0=
2525
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8/go.mod h1:+gPap4jha079qzRTUaehv+UZ6sSdaNwkH0D3b6zhTuk=
26+
github.com/anchore/go-sync v0.0.0-20240306205607-3ee6b614d624 h1:uKEb2vI/rlEGhgLs9BzRwCQDUVXyB8ia9vULpm/Q9Rc=
27+
github.com/anchore/go-sync v0.0.0-20240306205607-3ee6b614d624/go.mod h1:4gU9pKhRjTOiU34grx5w5IM7YxZfLx+9TrrultzHThU=
2628
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
2729
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
2830
github.com/anchore/mimetype v0.0.0-20240710165720-f966690755a5 h1:MGFRxHpfVyCoRRanIz6JJHZD2L4HLKifBGhGZReOLls=

pkg/image/docker/tarball_provider.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (p *tarballImageProvider) Name() string {
3838
}
3939

4040
// Provide an image object that represents the docker image tar at the configured location on disk.
41-
func (p *tarballImageProvider) Provide(_ context.Context) (*image.Image, error) {
41+
func (p *tarballImageProvider) Provide(ctx context.Context) (*image.Image, error) {
4242
img, err := tarball.ImageFromPath(p.path, nil)
4343
if err != nil {
4444
// raise a more controlled error for when there are multiple images within the given tar (from https://github.com/anchore/grype/issues/215)
@@ -92,7 +92,7 @@ func (p *tarballImageProvider) Provide(_ context.Context) (*image.Image, error)
9292
}
9393

9494
out := image.New(img, p.tmpDirGen, contentTempDir, metadata...)
95-
err = out.Read()
95+
err = out.Read(ctx)
9696
if err != nil {
9797
return nil, err
9898
}

pkg/image/image.go

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package image
22

33
import (
4+
"context"
45
"crypto/sha256"
56
"fmt"
67
"io"
@@ -14,6 +15,7 @@ import (
1415
"github.com/wagoodman/go-partybus"
1516
"github.com/wagoodman/go-progress"
1617

18+
"github.com/anchore/go-sync"
1719
"github.com/anchore/stereoscope/internal/bus"
1820
"github.com/anchore/stereoscope/internal/log"
1921
"github.com/anchore/stereoscope/pkg/event"
@@ -192,10 +194,14 @@ func (i *Image) applyOverrideMetadata() error {
192194
return nil
193195
}
194196

197+
type layerReadResult struct {
198+
layer *Layer
199+
err error
200+
}
201+
195202
// Read parses information from the underlying image tar into this struct. This includes image metadata, layer
196203
// metadata, layer file trees, and layer squash trees (which implies the image squash tree).
197-
func (i *Image) Read() error {
198-
var layers = make([]*Layer, 0)
204+
func (i *Image) Read(ctx context.Context) error {
199205
var err error
200206
i.Metadata, err = readImageMetadata(i.image)
201207
if err != nil {
@@ -207,10 +213,7 @@ func (i *Image) Read() error {
207213
return err
208214
}
209215

210-
log.Debugf("image metadata: digest=%+v mediaType=%+v tags=%+v",
211-
i.Metadata.ID,
212-
i.Metadata.MediaType,
213-
i.Metadata.Tags)
216+
log.WithFields("digest", i.Metadata.ID, "mediaType", i.Metadata.MediaType, "tags", i.Metadata.Tags).Info("reading image")
214217

215218
v1Layers, err := i.image.Layers()
216219
if err != nil {
@@ -222,19 +225,23 @@ func (i *Image) Read() error {
222225

223226
fileCatalog := NewFileCatalog()
224227

225-
for idx, v1Layer := range v1Layers {
226-
layer := NewLayer(v1Layer)
227-
err := layer.Read(fileCatalog, idx, i.contentCacheDir)
228-
if err != nil {
229-
return err
230-
}
231-
i.Metadata.Size += layer.Metadata.Size
232-
layers = append(layers, layer)
228+
exec, _ := sync.FromContext(ctx)
233229

234-
readProg.Increment()
230+
collector := sync.NewCollector[layerReadResult](exec)
231+
232+
for idx, v1Layer := range v1Layers {
233+
collector.Provide(readLayer(fileCatalog, idx, v1Layer, i.contentCacheDir, readProg))
235234
}
236235

237-
i.Layers = layers
236+
layerReadResults := collector.Collect()
237+
238+
for _, result := range layerReadResults {
239+
if result.err != nil {
240+
return result.err
241+
}
242+
i.Metadata.Size += result.layer.Metadata.Size
243+
i.Layers = append(i.Layers, result.layer)
244+
}
238245

239246
// in order to resolve symlinks all squashed trees must be available
240247
err = i.squash(readProg)
@@ -245,6 +252,17 @@ func (i *Image) Read() error {
245252
return err
246253
}
247254

255+
func readLayer(fileCatalog *FileCatalog, idx int, v1Layer v1.Layer, contentDir string, prog *progress.Manual) sync.ProviderFunc[layerReadResult] {
256+
return func() layerReadResult {
257+
defer prog.Increment()
258+
l := NewLayer(v1Layer)
259+
return layerReadResult{
260+
layer: l,
261+
err: l.Read(fileCatalog, idx, contentDir),
262+
}
263+
}
264+
}
265+
248266
// squash generates a squash tree for each layer in the image. For instance, layer 2 squash =
249267
// squash(layer 0, layer 1, layer 2), layer 3 squash = squash(layer 0, layer 1, layer 2, layer 3), and so on.
250268
func (i *Image) squash(prog *progress.Manual) error {

pkg/image/layer.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,7 @@ func (l *Layer) readStandardImageLayer(idx int, uncompressedLayersCacheDir strin
124124
return err
125125
}
126126

127-
log.Debugf("layer metadata: index=%+v digest=%+v mediaType=%+v",
128-
l.Metadata.Index,
129-
l.Metadata.Digest,
130-
l.Metadata.MediaType)
127+
log.WithFields("index", l.Metadata.Index, "digest", l.Metadata.Digest, "mediaType", l.Metadata.MediaType).Debug("reading layer")
131128

132129
tarFilePath, err := l.uncompressedCache(uncompressedLayersCacheDir)
133130
if err != nil {
@@ -153,10 +150,7 @@ func (l *Layer) readSingularityImageLayer(idx int, uncompressedLayersCacheDir st
153150
return err
154151
}
155152

156-
log.Debugf("layer metadata: index=%+v digest=%+v mediaType=%+v",
157-
l.Metadata.Index,
158-
l.Metadata.Digest,
159-
l.Metadata.MediaType)
153+
log.WithFields("index", l.Metadata.Index, "digest", l.Metadata.Digest, "mediaType", l.Metadata.MediaType).Debug("reading layer")
160154

161155
monitor := trackReadProgress(l.Metadata)
162156
sqfsFilePath, err := l.uncompressedCache(uncompressedLayersCacheDir)

pkg/image/oci/directory_provider.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (p *directoryImageProvider) Name() string {
3232
}
3333

3434
// Provide an image object that represents the OCI image as a directory.
35-
func (p *directoryImageProvider) Provide(_ context.Context) (*image.Image, error) {
35+
func (p *directoryImageProvider) Provide(ctx context.Context) (*image.Image, error) {
3636
pathObj, err := layout.FromPath(p.path)
3737
if err != nil {
3838
return nil, fmt.Errorf("unable to read image from OCI directory path %q: %w", p.path, err)
@@ -81,7 +81,7 @@ func (p *directoryImageProvider) Provide(_ context.Context) (*image.Image, error
8181
}
8282

8383
out := image.New(img, p.tmpDirGen, contentTempDir, metadata...)
84-
err = out.Read()
84+
err = out.Read(ctx)
8585
if err != nil {
8686
return nil, err
8787
}

pkg/image/oci/registry_provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func (p *registryImageProvider) Provide(ctx context.Context) (*image.Image, erro
9090
}
9191

9292
out := image.New(img, p.tmpDirGen, imageTempDir, metadata...)
93-
err = out.Read()
93+
err = out.Read(ctx)
9494
if err != nil {
9595
return nil, err
9696
}

pkg/image/sif/archive_provider.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (p *singularityImageProvider) Name() string {
3131
}
3232

3333
// Provide returns an Image that represents a Singularity Image Format (SIF) image.
34-
func (p *singularityImageProvider) Provide(_ context.Context) (*image.Image, error) {
34+
func (p *singularityImageProvider) Provide(ctx context.Context) (*image.Image, error) {
3535
// We need to map the SIF to a GGCR v1.Image. Start with an implementation of the GGCR
3636
// partial.UncompressedImageCore interface.
3737
si, err := newSIFImage(p.path)
@@ -58,7 +58,7 @@ func (p *singularityImageProvider) Provide(_ context.Context) (*image.Image, err
5858
}
5959

6060
out := image.New(ui, p.tmpDirGen, contentCacheDir, metadata...)
61-
err = out.Read()
61+
err = out.Read(ctx)
6262
if err != nil {
6363
return nil, err
6464
}

pkg/image/sif/archive_provider_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestSingularityImageProvider_Provide(t *testing.T) {
4343
}
4444

4545
if err == nil {
46-
if err := i.Read(); err != nil {
46+
if err := i.Read(context.Background()); err != nil {
4747
t.Fatal(err)
4848
}
4949
}

test/integration/oci_registry_source_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func TestOciRegistrySourceMetadata(t *testing.T) {
3838
require.NoError(t, img.Cleanup())
3939
})
4040

41-
require.NoError(t, img.Read())
41+
require.NoError(t, img.Read(context.Background()))
4242

4343
assert.Len(t, img.Metadata.RepoDigests, 1)
4444
assert.Equal(t, "index.docker.io/"+ref, img.Metadata.RepoDigests[0])

0 commit comments

Comments
 (0)