Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for concurrent layer reads #294

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,6 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require github.com/anchore/go-sync v0.0.0-20240306205607-3ee6b614d624

replace github.com/gabriel-vasile/mimetype v1.4.4 => github.com/anchore/mimetype v0.0.0-20240710165720-f966690755a5
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 h1:imgMA0gN0TZx7PSa/pdWqXadBvrz8WsN6zySzCe4XX0=
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8/go.mod h1:+gPap4jha079qzRTUaehv+UZ6sSdaNwkH0D3b6zhTuk=
github.com/anchore/go-sync v0.0.0-20240306205607-3ee6b614d624 h1:uKEb2vI/rlEGhgLs9BzRwCQDUVXyB8ia9vULpm/Q9Rc=
github.com/anchore/go-sync v0.0.0-20240306205607-3ee6b614d624/go.mod h1:4gU9pKhRjTOiU34grx5w5IM7YxZfLx+9TrrultzHThU=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
github.com/anchore/mimetype v0.0.0-20240710165720-f966690755a5 h1:MGFRxHpfVyCoRRanIz6JJHZD2L4HLKifBGhGZReOLls=
Expand Down
4 changes: 2 additions & 2 deletions pkg/image/docker/tarball_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (p *tarballImageProvider) Name() string {
}

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

out := image.New(img, p.tmpDirGen, contentTempDir, metadata...)
err = out.Read()
err = out.Read(ctx)
if err != nil {
return nil, err
}
Expand Down
50 changes: 34 additions & 16 deletions pkg/image/image.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package image

import (
"context"
"crypto/sha256"
"fmt"
"io"
Expand All @@ -14,6 +15,7 @@
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"

"github.com/anchore/go-sync"
"github.com/anchore/stereoscope/internal/bus"
"github.com/anchore/stereoscope/internal/log"
"github.com/anchore/stereoscope/pkg/event"
Expand Down Expand Up @@ -192,10 +194,14 @@
return nil
}

type layerReadResult struct {
layer *Layer
err error
}

// Read parses information from the underlying image tar into this struct. This includes image metadata, layer
// metadata, layer file trees, and layer squash trees (which implies the image squash tree).
func (i *Image) Read() error {
var layers = make([]*Layer, 0)
func (i *Image) Read(ctx context.Context) error {
var err error
i.Metadata, err = readImageMetadata(i.image)
if err != nil {
Expand All @@ -207,10 +213,7 @@
return err
}

log.Debugf("image metadata: digest=%+v mediaType=%+v tags=%+v",
i.Metadata.ID,
i.Metadata.MediaType,
i.Metadata.Tags)
log.WithFields("digest", i.Metadata.ID, "mediaType", i.Metadata.MediaType, "tags", i.Metadata.Tags).Info("reading image")

v1Layers, err := i.image.Layers()
if err != nil {
Expand All @@ -222,19 +225,23 @@

fileCatalog := NewFileCatalog()

for idx, v1Layer := range v1Layers {
layer := NewLayer(v1Layer)
err := layer.Read(fileCatalog, idx, i.contentCacheDir)
if err != nil {
return err
}
i.Metadata.Size += layer.Metadata.Size
layers = append(layers, layer)
exec, _ := sync.FromContext(ctx)

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Build snapshot artifacts

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Benchmark tests

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Benchmark tests

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Integration tests

undefined: sync.FromContext

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Static analysis

undefined: sync.FromContext (typecheck)

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Static analysis

undefined: sync.FromContext) (typecheck)

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Static analysis

undefined: sync.FromContext) (typecheck)

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Static analysis

undefined: sync.FromContext) (typecheck)

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Static analysis

undefined: sync.FromContext) (typecheck)

Check failure on line 228 in pkg/image/image.go

View workflow job for this annotation

GitHub Actions / Unit tests

undefined: sync.FromContext

readProg.Increment()
collector := sync.NewCollector[layerReadResult](exec)

for idx, v1Layer := range v1Layers {
collector.Provide(readLayer(fileCatalog, idx, v1Layer, i.contentCacheDir, readProg))
}

i.Layers = layers
layerReadResults := collector.Collect()

for _, result := range layerReadResults {
if result.err != nil {
return result.err
}
i.Metadata.Size += result.layer.Metadata.Size
i.Layers = append(i.Layers, result.layer)
}

// in order to resolve symlinks all squashed trees must be available
err = i.squash(readProg)
Expand All @@ -245,6 +252,17 @@
return err
}

func readLayer(fileCatalog *FileCatalog, idx int, v1Layer v1.Layer, contentDir string, prog *progress.Manual) sync.ProviderFunc[layerReadResult] {
return func() layerReadResult {
defer prog.Increment()
l := NewLayer(v1Layer)
return layerReadResult{
layer: l,
err: l.Read(fileCatalog, idx, contentDir),
}
}
}

// squash generates a squash tree for each layer in the image. For instance, layer 2 squash =
// squash(layer 0, layer 1, layer 2), layer 3 squash = squash(layer 0, layer 1, layer 2, layer 3), and so on.
func (i *Image) squash(prog *progress.Manual) error {
Expand Down
10 changes: 2 additions & 8 deletions pkg/image/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,7 @@ func (l *Layer) readStandardImageLayer(idx int, uncompressedLayersCacheDir strin
return err
}

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

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

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

monitor := trackReadProgress(l.Metadata)
sqfsFilePath, err := l.uncompressedCache(uncompressedLayersCacheDir)
Expand Down
4 changes: 2 additions & 2 deletions pkg/image/oci/directory_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"github.com/google/go-containerregistry/pkg/v1/layout"

"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/image"

Check failure on line 11 in pkg/image/oci/directory_provider.go

View workflow job for this annotation

GitHub Actions / Static analysis

could not import github.com/anchore/stereoscope/pkg/image (-: # github.com/anchore/stereoscope/pkg/image
)

const Directory image.Source = image.OciDirectorySource
Expand All @@ -32,7 +32,7 @@
}

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

out := image.New(img, p.tmpDirGen, contentTempDir, metadata...)
err = out.Read()
err = out.Read(ctx)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/image/oci/registry_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (p *registryImageProvider) Provide(ctx context.Context) (*image.Image, erro
}

out := image.New(img, p.tmpDirGen, imageTempDir, metadata...)
err = out.Read()
err = out.Read(ctx)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/image/sif/archive_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"github.com/google/go-containerregistry/pkg/v1/partial"

"github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/image"

Check failure on line 9 in pkg/image/sif/archive_provider.go

View workflow job for this annotation

GitHub Actions / Static analysis

could not import github.com/anchore/stereoscope/pkg/image (-: # github.com/anchore/stereoscope/pkg/image
)

const ProviderName = image.SingularitySource
Expand All @@ -31,7 +31,7 @@
}

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

out := image.New(ui, p.tmpDirGen, contentCacheDir, metadata...)
err = out.Read()
err = out.Read(ctx)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/image/sif/archive_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestSingularityImageProvider_Provide(t *testing.T) {
}

if err == nil {
if err := i.Read(); err != nil {
if err := i.Read(context.Background()); err != nil {
t.Fatal(err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/integration/oci_registry_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestOciRegistrySourceMetadata(t *testing.T) {
require.NoError(t, img.Cleanup())
})

require.NoError(t, img.Read())
require.NoError(t, img.Read(context.Background()))

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