Merge pull request #46383 from vvoland/c8d-legacybuilder-fix-layer-parent-snapshot
c8d/legacybuilder: Assorted fixes
This commit is contained in:
commit
51d647122a
3 changed files with 126 additions and 91 deletions
|
@ -2,6 +2,7 @@ package containerd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/content"
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/mount"
|
||||
|
@ -28,6 +30,7 @@ import (
|
|||
"github.com/docker/docker/errdefs"
|
||||
dimage "github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
|
@ -58,7 +61,6 @@ func (i *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID s
|
|||
c: i.client,
|
||||
snapshotter: i.snapshotter,
|
||||
diffID: "",
|
||||
root: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -184,7 +186,6 @@ func newROLayerForImage(ctx context.Context, imgDesc *ocispec.Descriptor, i *Ima
|
|||
platMatcher = platforms.Only(*platform)
|
||||
}
|
||||
|
||||
// this needs it's own context + lease so that it doesn't get cleaned before we're ready
|
||||
confDesc, err := containerdimages.Config(ctx, i.client.ContentStore(), *imgDesc, platMatcher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -194,46 +195,47 @@ func newROLayerForImage(ctx context.Context, imgDesc *ocispec.Descriptor, i *Ima
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parent := identity.ChainID(diffIDs).String()
|
||||
|
||||
s := i.client.SnapshotService(i.snapshotter)
|
||||
key := stringid.GenerateRandomID()
|
||||
ctx, _, err = i.client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lease for commit: %w", err)
|
||||
}
|
||||
mounts, err := s.View(ctx, key, parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(vvoland): Check if image is unpacked, and unpack it if it's not.
|
||||
imageSnapshotID := identity.ChainID(diffIDs).String()
|
||||
|
||||
tempMountLocation := os.TempDir()
|
||||
root, err := os.MkdirTemp(tempMountLocation, "rootfs-mount")
|
||||
snapshotter := i.StorageDriver()
|
||||
_, lease, err := createLease(ctx, i.client.LeasesService())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mount.All(mounts, root); err != nil {
|
||||
return nil, err
|
||||
return nil, errdefs.System(fmt.Errorf("failed to lease image snapshot %s: %w", imageSnapshotID, err))
|
||||
}
|
||||
|
||||
return &rolayer{
|
||||
key: key,
|
||||
key: imageSnapshotID,
|
||||
c: i.client,
|
||||
snapshotter: i.snapshotter,
|
||||
diffID: digest.Digest(parent),
|
||||
root: root,
|
||||
snapshotter: snapshotter,
|
||||
diffID: "", // Image RO layer doesn't have a diff.
|
||||
contentStoreDigest: "",
|
||||
lease: &lease,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createLease(ctx context.Context, lm leases.Manager) (context.Context, leases.Lease, error) {
|
||||
lease, err := lm.Create(ctx,
|
||||
leases.WithExpiration(time.Hour*24),
|
||||
leases.WithLabels(map[string]string{
|
||||
"org.mobyproject.lease.classicbuilder": "true",
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, leases.Lease{}, fmt.Errorf("failed to create a lease for snapshot: %w", err)
|
||||
}
|
||||
|
||||
return leases.WithLease(ctx, lease.ID), lease, nil
|
||||
}
|
||||
|
||||
type rolayer struct {
|
||||
key string
|
||||
c *containerd.Client
|
||||
snapshotter string
|
||||
diffID digest.Digest
|
||||
root string
|
||||
contentStoreDigest digest.Digest
|
||||
lease *leases.Lease
|
||||
}
|
||||
|
||||
func (rl *rolayer) ContentStoreDigest() digest.Digest {
|
||||
|
@ -248,41 +250,36 @@ func (rl *rolayer) DiffID() layer.DiffID {
|
|||
}
|
||||
|
||||
func (rl *rolayer) Release() error {
|
||||
snapshotter := rl.c.SnapshotService(rl.snapshotter)
|
||||
err := snapshotter.Remove(context.TODO(), rl.key)
|
||||
if err != nil && !cerrdefs.IsNotFound(err) {
|
||||
return err
|
||||
if rl.lease != nil {
|
||||
lm := rl.c.LeasesService()
|
||||
err := lm.Delete(context.TODO(), *rl.lease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rl.lease = nil
|
||||
}
|
||||
|
||||
if rl.root == "" { // nothing to release
|
||||
return nil
|
||||
}
|
||||
if err := mount.UnmountAll(rl.root, 0); err != nil {
|
||||
log.G(context.TODO()).WithError(err).WithField("root", rl.root).Error("failed to unmount ROLayer")
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(rl.root); err != nil {
|
||||
log.G(context.TODO()).WithError(err).WithField("dir", rl.root).Error("failed to remove mount temp dir")
|
||||
return err
|
||||
}
|
||||
rl.root = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewRWLayer creates a new read-write layer for the builder
|
||||
func (rl *rolayer) NewRWLayer() (builder.RWLayer, error) {
|
||||
func (rl *rolayer) NewRWLayer() (_ builder.RWLayer, outErr error) {
|
||||
snapshotter := rl.c.SnapshotService(rl.snapshotter)
|
||||
|
||||
// we need this here for the prepared snapshots or
|
||||
// we'll have racy behaviour where sometimes they
|
||||
// will get GC'd before we commit/use them
|
||||
ctx, _, err := rl.c.WithLease(context.TODO(), leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lease for commit: %w", err)
|
||||
}
|
||||
|
||||
key := stringid.GenerateRandomID()
|
||||
mounts, err := snapshotter.Prepare(ctx, key, rl.diffID.String())
|
||||
|
||||
ctx, lease, err := createLease(context.TODO(), rl.c.LeasesService())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if outErr != nil {
|
||||
if err := rl.c.LeasesService().Delete(ctx, lease); err != nil {
|
||||
log.G(ctx).WithError(err).Warn("failed to remove lease after NewRWLayer error")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mounts, err := snapshotter.Prepare(ctx, key, rl.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -301,6 +298,7 @@ func (rl *rolayer) NewRWLayer() (builder.RWLayer, error) {
|
|||
c: rl.c,
|
||||
snapshotter: rl.snapshotter,
|
||||
root: root,
|
||||
lease: &lease,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -310,23 +308,31 @@ type rwlayer struct {
|
|||
c *containerd.Client
|
||||
snapshotter string
|
||||
root string
|
||||
lease *leases.Lease
|
||||
}
|
||||
|
||||
func (rw *rwlayer) Root() string {
|
||||
return rw.root
|
||||
}
|
||||
|
||||
func (rw *rwlayer) Commit() (builder.ROLayer, error) {
|
||||
// we need this here for the prepared snapshots or
|
||||
// we'll have racy behaviour where sometimes they
|
||||
// will get GC'd before we commit/use them
|
||||
ctx, _, err := rw.c.WithLease(context.TODO(), leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create lease for commit: %w", err)
|
||||
}
|
||||
func (rw *rwlayer) Commit() (_ builder.ROLayer, outErr error) {
|
||||
snapshotter := rw.c.SnapshotService(rw.snapshotter)
|
||||
|
||||
key := stringid.GenerateRandomID()
|
||||
|
||||
lm := rw.c.LeasesService()
|
||||
ctx, lease, err := createLease(context.TODO(), lm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if outErr != nil {
|
||||
if err := lm.Delete(ctx, lease); err != nil {
|
||||
log.G(ctx).WithError(err).Warn("failed to remove lease after NewRWLayer error")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = snapshotter.Commit(ctx, key, rw.key)
|
||||
if err != nil && !cerrdefs.IsAlreadyExists(err) {
|
||||
return nil, err
|
||||
|
@ -355,30 +361,36 @@ func (rw *rwlayer) Commit() (builder.ROLayer, error) {
|
|||
c: rw.c,
|
||||
snapshotter: rw.snapshotter,
|
||||
diffID: diffID,
|
||||
root: "",
|
||||
contentStoreDigest: desc.Digest,
|
||||
lease: &lease,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rw *rwlayer) Release() error {
|
||||
snapshotter := rw.c.SnapshotService(rw.snapshotter)
|
||||
err := snapshotter.Remove(context.TODO(), rw.key)
|
||||
if err != nil && !cerrdefs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (rw *rwlayer) Release() (outErr error) {
|
||||
if rw.root == "" { // nothing to release
|
||||
return nil
|
||||
}
|
||||
if err := mount.UnmountAll(rw.root, 0); err != nil {
|
||||
|
||||
if err := mount.UnmountAll(rw.root, 0); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
log.G(context.TODO()).WithError(err).WithField("root", rw.root).Error("failed to unmount ROLayer")
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(rw.root); err != nil {
|
||||
if err := os.Remove(rw.root); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
log.G(context.TODO()).WithError(err).WithField("dir", rw.root).Error("failed to remove mount temp dir")
|
||||
return err
|
||||
}
|
||||
rw.root = ""
|
||||
|
||||
if rw.lease != nil {
|
||||
lm := rw.c.LeasesService()
|
||||
err := lm.Delete(context.TODO(), *rw.lease)
|
||||
if err != nil {
|
||||
log.G(context.TODO()).WithError(err).Warn("failed to delete lease when releasing RWLayer")
|
||||
} else {
|
||||
rw.lease = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -411,20 +423,30 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st
|
|||
parentDigest = parentDesc.Digest
|
||||
}
|
||||
|
||||
// get the info for the new layers
|
||||
info, err := i.client.ContentStore().Info(ctx, layerDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs := i.client.ContentStore()
|
||||
|
||||
// append the new layer descriptor
|
||||
layers = append(layers,
|
||||
ocispec.Descriptor{
|
||||
ra, err := cs.ReaderAt(ctx, ocispec.Descriptor{Digest: layerDigest})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read diff archive: %w", err)
|
||||
}
|
||||
defer ra.Close()
|
||||
|
||||
empty, err := archive.IsEmpty(content.NewReader(ra))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if archive is empty: %w", err)
|
||||
}
|
||||
if !empty {
|
||||
info, err := cs.Info(ctx, layerDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layers = append(layers, ocispec.Descriptor{
|
||||
MediaType: containerdimages.MediaTypeDockerSchema2LayerGzip,
|
||||
Digest: layerDigest,
|
||||
Size: info.Size,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// necessary to prevent the contents from being GC'd
|
||||
// between writing them here and creating an image
|
||||
|
@ -464,7 +486,7 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st
|
|||
}
|
||||
}
|
||||
|
||||
if err := i.unpackImage(ctx, createdImage, platforms.DefaultSpec()); err != nil {
|
||||
if err := i.unpackImage(ctx, i.StorageDriver(), img, commitManifestDesc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,17 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
|
|||
return "", fmt.Errorf("failed to create new image: %w", err)
|
||||
}
|
||||
}
|
||||
return image.ID(img.Target.Digest), nil
|
||||
id := image.ID(img.Target.Digest)
|
||||
|
||||
c8dImg, err := i.NewImageManifest(ctx, img, commitManifestDesc)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
if err := c8dImg.Unpack(ctx, container.Driver); err != nil && !cerrdefs.IsAlreadyExists(err) {
|
||||
return id, fmt.Errorf("failed to unpack image: %w", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// generateCommitImageConfig generates an OCI Image config based on the
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/content"
|
||||
cerrdefs "github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
|
@ -150,7 +150,8 @@ func (i *ImageService) ImportImage(ctx context.Context, ref reference.Named, pla
|
|||
logger.WithError(err).Debug("failed to save image")
|
||||
return "", err
|
||||
}
|
||||
err = i.unpackImage(ctx, img, *platform)
|
||||
|
||||
err = i.unpackImage(ctx, i.StorageDriver(), img, manifestDesc)
|
||||
if err != nil {
|
||||
logger.WithError(err).Debug("failed to unpack image")
|
||||
} else {
|
||||
|
@ -312,18 +313,20 @@ func (i *ImageService) saveImage(ctx context.Context, img images.Image) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// unpackImage unpacks the image into the snapshotter.
|
||||
func (i *ImageService) unpackImage(ctx context.Context, img images.Image, platform ocispec.Platform) error {
|
||||
c8dImg := containerd.NewImageWithPlatform(i.client, img, platforms.Only(platform))
|
||||
unpacked, err := c8dImg.IsUnpacked(ctx, i.snapshotter)
|
||||
// unpackImage unpacks the platform-specific manifest of a image into the snapshotter.
|
||||
func (i *ImageService) unpackImage(ctx context.Context, snapshotter string, img images.Image, manifestDesc ocispec.Descriptor) error {
|
||||
c8dImg, err := i.NewImageManifest(ctx, img, manifestDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !unpacked {
|
||||
err = c8dImg.Unpack(ctx, i.snapshotter)
|
||||
|
||||
if err := c8dImg.Unpack(ctx, snapshotter); err != nil {
|
||||
if !cerrdefs.IsAlreadyExists(err) {
|
||||
return errdefs.System(fmt.Errorf("failed to unpack image: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectCompression dectects the reader compression type.
|
||||
|
|
Loading…
Add table
Reference in a new issue