Merge pull request #46383 from vvoland/c8d-legacybuilder-fix-layer-parent-snapshot

c8d/legacybuilder: Assorted fixes
This commit is contained in:
Sebastiaan van Stijn 2023-09-11 20:41:59 +02:00 committed by GitHub
commit 51d647122a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 91 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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.