8000 [WIP] store: extract artifact layers automatically by sohankunkerkar · Pull Request #9150 · cri-o/cri-o · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[WIP] store: extract artifact layers automatically #915 8000 0

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

Draft
wants to merge 4 commits 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: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
github.com/intel/goresctrl v0.8.0
github.com/json-iterator/go v1.1.12
github.com/kata-containers/kata-containers/src/runtime v0.0.0-20240208092920-b99f57452225
github.com/klauspost/compress v1.18.0
github.com/moby/sys/mountinfo v0.7.2
github.com/moby/sys/user v0.3.0
github.com/moby/sys/userns v0.1.0
Expand Down Expand Up @@ -162,7 +163,6 @@ require (
github.com/jinzhu/copier v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knqyf263/go-plugin v0.8.1-0.20241122101054-d8d42059d8f1 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
Expand Down
17 changes: 17 additions & 0 deletions internal/oci/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
runtimePath string // runtime path for a given platform
execPIDs map[int]bool
runtimeUser *types.ContainerUser
cleanups []func()
cleanupOnce *sync.Once
}

func (c *Container) CRIAttributes() *types.ContainerAttributes {
Expand Down Expand Up @@ -877,3 +879,18 @@
func (c *Container) RuntimeUser() *types.ContainerUser {
return c.runtimeUser
}

func (c *Container) AddCleanup(cleanup func()) {
c.cleanups = append(c.cleanups, cleanup)

Check warning on line 884 in internal/oci/container.go

View check run for this annotation

Codecov / codecov/patch

internal/oci/container.go#L883-L884

Added lines #L883 - L884 were not covered by tests
}

func (c *Container) Cleanup() {
if c.cleanupOnce == nil {
c.cleanupOnce = &sync.Once{}
}
c.cleanupOnce.Do(func() {

Check failure on line 891 in internal/oci/container.go

View workflow job for this annotation

GitHub Actions / lint

expressions should not be cuddled with blocks (wsl)
for _, cleanup := range c.cleanups {
cleanup()
}

Check warning on line 894 in internal/oci/container.go

View check run for this annotation

Codecov / codecov/patch

internal/oci/container.go#L893-L894

Added lines #L893 - L894 were not covered by tests
})
}
1 change: 1 addition & 0 deletions internal/oci/runtime_oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,7 @@ func (r *runtimeOCI) StopLoopForContainer(c *Container, bm kwait.BackoffManager)
c.state.Finished = time.Now()
c.opLock.Unlock()
c.SetAsDoneStopping()
c.Cleanup()
}()

if c.state.Status == ContainerStatePaused {
Expand Down
1 change: 1 addition & 0 deletions internal/oci/runtime_vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@
return err
}

c.Cleanup()

Check warning on line 692 in internal/oci/runtime_vm.go

View check run for this annotation

Codecov / codecov/patch

internal/oci/runtime_vm.go#L692

Added line #L692 was not covered by tests
c.state.Finished = time.Now()

return nil
Expand Down
136 changes: 123 additions & 13 deletions internal/ociartifact/store.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package ociartifact

import (
"archive/tar"
"compress/gzip"
"context"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"slices"
"strings"
"sync"

modelSpec "github.com/CloudNativeAI/model-spec/specs-go/v1"
"github.com/containers/common/libimage"
Expand All @@ -17,6 +21,7 @@
"github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/pkg/blobinfocache"
"github.com/containers/image/v5/types"
"github.com/klauspost/compress/zstd"
"github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"

Expand Down Expand Up @@ -469,7 +474,16 @@
// Source path of the blob, i.e. full path in the blob dir.
SourcePath string
// Name of the file in the artifact.
Name string
Name string
cleanupFunc func()
cleanupOnce *sync.Once
}

// Cleanup triggers the deletion of the extracted directory.
func (b *BlobMountPath) Cleanup() {
if b.cleanupFunc != nil && b.cleanupOnce != nil {
b.cleanupOnce.Do(b.cleanupFunc)
}

Check warning on line 486 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L483-L486

Added lines #L483 - L486 were not covered by tests
}

// BlobMountPaths retrieves the local file paths for all blobs in the provided artifact and returns them as BlobMountPath slices.
Expand All @@ -479,35 +493,131 @@
return nil, fmt.Errorf("failed to get an image reference: %w", err)
}

extractDir := filepath.Join(s.rootPath, "extracted", artifact.Digest().Encoded())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsure about this. is this a convention? is there a convention? seems odd to hardcode the path but I'm unsure about the alternative

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I tried changing that logic. Let me know how it feels.3c63062

if err := os.MkdirAll(extractDir, 0o755); err != nil {
return nil, fmt.Errorf("failed to create artifact dir: %w", err)
}

Check warning on line 499 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L496-L499

Added lines #L496 - L499 were not covered by tests

cleanupFunc := func() { os.RemoveAll(extractDir) }
cleanupOnce := &sync.Once{}

// Cleanup on function exit if we encounter an error
cleanup := true
defer func() {
if cleanup {
os.RemoveAll(extractDir)
}

Check warning on line 509 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L501-L509

Added lines #L501 - L509 were not covered by tests
}()

src, err := ref.NewImageSource(ctx, sys)
if err != nil {
return nil, fmt.Errorf("failed to get an image source: %w", err)
}

defer src.Close()

mountPaths := make([]BlobMountPath, 0, len(artifact.Manifest().Layers))
bic := blobinfocache.DefaultCache(s.systemContext)

Check warning on line 519 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L519

Added line #L519 was not covered by tests

for _, l := range artifact.Manifest().Layers {
path, err := layout.GetLocalBlobPath(ctx, src, l.Digest)
err = func() error {
blob, _, err := s.impl.GetBlob(ctx, src, types.BlobInfo{Digest: l.Digest}, bic)
if err != nil {
return fmt.Errorf("failed to get blob: %w", err)
}
defer blob.Close()

content, err := s.processLayerContent(l.MediaType, blob)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder which is better extracting when pulling or when mounting.
Images are extracted when it's pulled, aren't they? I think it'd be better to align with images.

if err != nil {
return fmt.Errorf("failed to process layer: %w", err)
}

Check warning on line 532 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L522-L532

Added lines #L522 - L532 were not covered by tests

name := artifactName(l.Annotations)
if name == "" {
log.Warnf(ctx, "Unable to find name for artifact layer which makes it not mountable")

return nil
}

Check warning on line 539 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L534-L539

Added lines #L534 - L539 were not covered by tests

filePath := filepath.Join(extractDir, name)
if err := os.WriteFile(filePath, content, 0o644); err != nil {
log.Errorf(ctx, "Failed to write file: %v", err)
os.Remove(filePath)
return err

Check failure on line 545 in internal/ociartifact/store.go

View workflow job for this annotation

GitHub Actions / lint

return with no blank line before (nlreturn)
}

Check warning on line 546 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L541-L546

Added lines #L541 - L546 were not covered by tests

mountPaths = append(mountPaths, BlobMountPath{
SourcePath: filePath,
Name: name,
cleanupFunc: cleanupFunc,
cleanupOnce: cleanupOnce,
})
return nil

Check failure on line 554 in internal/ociartifact/store.go

View workflow job for this annotation

GitHub Actions / lint

return with no blank line before (nlreturn)

Check warning on line 554 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L548-L554

Added lines #L548 - L554 were not covered by tests
}()
// If any layer extraction fails, abort the entire operation.
if err != nil {
return nil, fmt.Errorf("failed to extract artifact layer: %w", err)
}

Check warning on line 559 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L557-L559

Added lines #L557 - L559 were not covered by tests
}
// Disable cleanup on success.
// mountArtifact() will handle the cleanup.
cleanup = false
return mountPaths, nil

Check failure on line 564 in internal/ociartifact/store.go

View workflow job for this annotation

GitHub Actions / lint

return with no blank line before (nlreturn)

Check warning on line 564 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L563-L564

Added lines #L563 - L564 were not covered by tests
}

// processLayerContent decompresses and processes layer content based on media type.
// Supports gzip, zstd, and uncompressed tar formats. Returns raw bytes for non-tar content.
// Instead of relying on the exact media type like "application/vnd.oci.image.layer.v1.tar+zstd",
// it would be helpful to stick with more generic patterns like ".tar+gzip" or ".tar+zstd".
func (s *Store) processLayerContent(mediaType string, r io.Reader) ([]byte, error) {
switch {
case strings.HasSuffix(mediaType, ".tar+gzip"):
gr, err := gzip.NewReader(r)

Check warning on line 574 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L571-L574

Added lines #L571 - L574 were not covered by tests
if err != nil {
return nil, fmt.Errorf("failed to get a local blob path: %w", err)
return nil, fmt.Errorf("gzip decompress failed: %w", err)

Check warning on line 576 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L576

Added line #L576 was not covered by tests
}
defer gr.Close()

Check warning on line 578 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L578

Added line #L578 was not covered by tests

name := artifactName(l.Annotations)
if name == "" {
log.Warnf(ctx, "Unable to find name for artifact layer which makes it not mountable")
return s.extractSingleFileFromTar(gr)

Check warning on line 580 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L580

Added line #L580 was not covered by tests

continue
case strings.HasSuffix(mediaType, ".tar+zstd"):
zr, err := zstd.NewReader(r)
if err != nil {
return nil, fmt.Errorf("zstd decompress failed: %w", err)

Check warning on line 585 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L582-L585

Added lines #L582 - L585 were not covered by tests
}
defer zr.Close()

Check warning on line 587 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L587

Added line #L587 was not covered by tests

mountPaths = append(mountPaths, BlobMountPath{
SourcePath: path,
Name: name,
})
return s.extractSingleFileFromTar(zr)

Check warning on line 589 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch 10000

internal/ociartifact/store.go#L589

Added line #L589 was not covered by tests

case strings.HasSuffix(mediaType, ".tar"):
return s.extractSingleFileFromTar(r)

Check warning on line 592 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L591-L592

Added lines #L591 - L592 were not covered by tests

default:
data, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("reading blob content: %w", err)
}

Check warning on line 598 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L594-L598

Added lines #L594 - L598 were not covered by tests

return data, nil

Check warning on line 600 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L600

Added line #L600 was not covered by tests
}
}

return mountPaths, nil
func (s *Store) extractSingleFileFromTar(r io.Reader) ([]byte, error) {
tr := tar.NewReader(r)

for {
hdr, err := tr.Next()
if err == io.EOF {
return nil, errors.New("no files found in tar archive")
}

Check warning on line 611 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L604-L611

Added lines #L604 - L611 were not covered by tests

if err != nil {
return nil, fmt.Errorf("reading tar header: %w", err)
}

Check warning on line 615 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L613-L615

Added lines #L613 - L615 were not covered by tests

if hdr.Typeflag == tar.TypeReg {
return io.ReadAll(tr)
}

Check warning on line 619 in internal/ociartifact/store.go

View check run for this annotation

Codecov / codecov/patch

internal/ociartifact/store.go#L617-L619

Added lines #L617 - L619 were not covered by tests
}
}

func artifactName(annotations map[string]string) string {
Expand Down
6 changes: 5 additions & 1 deletion server/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@
}
}()

containerVolumes, ociMounts, safeMounts, err := s.addOCIBindMounts(ctx, ctr, mountLabel, s.config.BindMountPrefix, s.config.AbsentMountSourcesToReject, maybeRelabel, skipRelabel, cgroup2RW, idMapSupport, rroSupport, s.ContainerServer.Config().Root, containerInfo.RunDir)
containerVolumes, ociMounts, safeMounts, cleanups, err := s.addOCIBindMounts(ctx, ctr, mountLabel, s.config.BindMountPrefix, s.config.AbsentMountSourcesToReject, maybeRelabel, skipRelabel, cgroup2RW, idMapSupport, rroSupport, s.ContainerServer.Config().Root, containerInfo.RunDir)

Check failure on line 840 in server/container_create.go

View workflow job for this annotation

GitHub Actions / build-freebsd

assignment mismatch: 5 variables but s.addOCIBindMounts returns 4 values
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1380,6 +1380,10 @@
ociContainer.AddVolume(cv)
}

for _, cleanup := range cleanups {
ociContainer.AddCleanup(cleanup)
}

Check warning on line 1385 in server/container_create.go

View check run for this annotation

Codecov / codecov/patch

server/container_create.go#L1384-L1385

Added lines #L1384 - L1385 were not covered by tests

return ociContainer, nil
}

Expand Down
Loading
Loading
0