Skip to content

Commit

Permalink
Merge pull request #46302 from thaJeztah/24.0_backport_c8d-legacybuil…
Browse files Browse the repository at this point in the history
…der-fix-from-scratch

[24.0 backport] c8d: Fix building Dockerfiles that have `FROM scratch`
  • Loading branch information
rumpl committed Aug 23, 2023
2 parents e0661af + 1d10e86 commit aade22d
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 61 deletions.
89 changes: 57 additions & 32 deletions daemon/containerd/image_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/containerd/containerd/snapshots"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/archive"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/identity"
"github.com/opencontainers/image-spec/specs-go"
Expand All @@ -38,28 +39,27 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
container := i.containers.Get(cc.ContainerID)
cs := i.client.ContentStore()

imageManifest, err := getContainerImageManifest(container)
if err != nil {
return "", err
}
var parentManifest ocispec.Manifest
var parentImage ocispec.Image

imageManifestBytes, err := content.ReadBlob(ctx, cs, imageManifest)
if err != nil {
return "", err
}
// ImageManifest can be nil when committing an image with base FROM scratch
if container.ImageManifest != nil {
imageManifestBytes, err := content.ReadBlob(ctx, cs, *container.ImageManifest)
if err != nil {
return "", err
}

var manifest ocispec.Manifest
if err := json.Unmarshal(imageManifestBytes, &manifest); err != nil {
return "", err
}
if err := json.Unmarshal(imageManifestBytes, &parentManifest); err != nil {
return "", err
}

imageConfigBytes, err := content.ReadBlob(ctx, cs, manifest.Config)
if err != nil {
return "", err
}
var ociimage ocispec.Image
if err := json.Unmarshal(imageConfigBytes, &ociimage); err != nil {
return "", err
imageConfigBytes, err := content.ReadBlob(ctx, cs, parentManifest.Config)
if err != nil {
return "", err
}
if err := json.Unmarshal(imageConfigBytes, &parentImage); err != nil {
return "", err
}
}

var (
Expand All @@ -78,15 +78,19 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
if err != nil {
return "", fmt.Errorf("failed to export layer: %w", err)
}
imageConfig := generateCommitImageConfig(parentImage, diffID, cc)

layers := parentManifest.Layers
if diffLayerDesc != nil {
rootfsID := identity.ChainID(imageConfig.RootFS.DiffIDs).String()

imageConfig := generateCommitImageConfig(ociimage, diffID, cc)
if err := applyDiffLayer(ctx, rootfsID, parentImage, sn, differ, *diffLayerDesc); err != nil {
return "", fmt.Errorf("failed to apply diff: %w", err)
}

rootfsID := identity.ChainID(imageConfig.RootFS.DiffIDs).String()
if err := applyDiffLayer(ctx, rootfsID, ociimage, sn, differ, diffLayerDesc); err != nil {
return "", fmt.Errorf("failed to apply diff: %w", err)
layers = append(layers, *diffLayerDesc)
}

layers := append(manifest.Layers, diffLayerDesc)
commitManifestDesc, err := writeContentsForImage(ctx, container.Driver, cs, imageConfig, layers)
if err != nil {
return "", err
Expand Down Expand Up @@ -130,6 +134,12 @@ func generateCommitImageConfig(baseConfig ocispec.Image, diffID digest.Digest, o
logrus.Warnf("assuming os=%q", os)
}
logrus.Debugf("generateCommitImageConfig(): arch=%q, os=%q", arch, os)

diffIds := baseConfig.RootFS.DiffIDs
if diffID != "" {
diffIds = append(diffIds, diffID)
}

return ocispec.Image{
Platform: ocispec.Platform{
Architecture: arch,
Expand All @@ -140,7 +150,7 @@ func generateCommitImageConfig(baseConfig ocispec.Image, diffID digest.Digest, o
Config: containerConfigToOciImageConfig(opts.Config),
RootFS: ocispec.RootFS{
Type: "layers",
DiffIDs: append(baseConfig.RootFS.DiffIDs, diffID),
DiffIDs: diffIds,
},
History: append(baseConfig.History, ocispec.History{
Created: &createdTime,
Expand Down Expand Up @@ -217,28 +227,43 @@ func writeContentsForImage(ctx context.Context, snName string, cs content.Store,
}

// createDiff creates a layer diff into containerd's content store.
func createDiff(ctx context.Context, name string, sn snapshots.Snapshotter, cs content.Store, comparer diff.Comparer) (ocispec.Descriptor, digest.Digest, error) {
// If the diff is empty it returns nil empty digest and no error.
func createDiff(ctx context.Context, name string, sn snapshots.Snapshotter, cs content.Store, comparer diff.Comparer) (*ocispec.Descriptor, digest.Digest, error) {
newDesc, err := rootfs.CreateDiff(ctx, name, sn, comparer)
if err != nil {
return ocispec.Descriptor{}, "", err
return nil, "", err
}

ra, err := cs.ReaderAt(ctx, newDesc)
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 {
return nil, "", nil
}

info, err := cs.Info(ctx, newDesc.Digest)
if err != nil {
return ocispec.Descriptor{}, "", err
return nil, "", fmt.Errorf("failed to get content info: %w", err)
}

diffIDStr, ok := info.Labels["containerd.io/uncompressed"]
if !ok {
return ocispec.Descriptor{}, "", fmt.Errorf("invalid differ response with no diffID")
return nil, "", fmt.Errorf("invalid differ response with no diffID")
}

diffID, err := digest.Parse(diffIDStr)
if err != nil {
return ocispec.Descriptor{}, "", err
return nil, "", err
}

return ocispec.Descriptor{
return &ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageLayerGzip,
Digest: newDesc.Digest,
Size: info.Size,
Expand All @@ -254,7 +279,7 @@ func applyDiffLayer(ctx context.Context, name string, baseImg ocispec.Image, sn

mount, err := sn.Prepare(ctx, key, parent)
if err != nil {
return err
return fmt.Errorf("failed to prepare snapshot: %w", err)
}

defer func() {
Expand Down
57 changes: 30 additions & 27 deletions daemon/containerd/image_snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,45 @@ import (

// PrepareSnapshot prepares a snapshot from a parent image for a container
func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *ocispec.Platform) error {
img, err := i.resolveImage(ctx, parentImage)
if err != nil {
return err
}
var parentSnapshot string
if parentImage != "" {
img, err := i.resolveImage(ctx, parentImage)
if err != nil {
return err
}

cs := i.client.ContentStore()
cs := i.client.ContentStore()

matcher := platforms.Default()
if platform != nil {
matcher = platforms.Only(*platform)
}
matcher := platforms.Default()
if platform != nil {
matcher = platforms.Only(*platform)
}

platformImg := containerd.NewImageWithPlatform(i.client, img, matcher)
unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
if err != nil {
return err
}
platformImg := containerd.NewImageWithPlatform(i.client, img, matcher)
unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
if err != nil {
return err
}

if !unpacked {
if err := platformImg.Unpack(ctx, i.snapshotter); err != nil {
if !unpacked {
if err := platformImg.Unpack(ctx, i.snapshotter); err != nil {
return err
}
}

desc, err := containerdimages.Config(ctx, cs, img.Target, matcher)
if err != nil {
return err
}
}

desc, err := containerdimages.Config(ctx, cs, img.Target, matcher)
if err != nil {
return err
}
diffIDs, err := containerdimages.RootFS(ctx, cs, desc)
if err != nil {
return err
}

diffIDs, err := containerdimages.RootFS(ctx, cs, desc)
if err != nil {
return err
parentSnapshot = identity.ChainID(diffIDs).String()
}

parent := identity.ChainID(diffIDs).String()

// Add a lease so that containerd doesn't garbage collect our snapshot
ls := i.client.LeasesService()
lease, err := ls.Create(ctx, leases.WithID(id))
Expand All @@ -69,7 +72,7 @@ func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentIma
}

s := i.client.SnapshotService(i.StorageDriver())
_, err = s.Prepare(ctx, id, parent)
_, err = s.Prepare(ctx, id, parentSnapshot)
return err
}

Expand Down
7 changes: 5 additions & 2 deletions daemon/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

cerrdefs "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/leases"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
Expand Down Expand Up @@ -144,8 +145,10 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, config ty
ID: container.ID,
}
if err := ls.Delete(context.Background(), lease, leases.SynchronousDelete); err != nil {
container.SetRemovalError(err)
return err
if !cerrdefs.IsNotFound(err) {
container.SetRemovalError(err)
return err
}
}
}
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/archive/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,25 @@ func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (
return applyLayerHandler(dest, layer, options, false)
}

// IsEmpty checks if the tar archive is empty (doesn't contain any entries).
func IsEmpty(rd io.Reader) (bool, error) {
decompRd, err := DecompressStream(rd)
if err != nil {
return true, fmt.Errorf("failed to decompress archive: %v", err)
}
defer decompRd.Close()

tarReader := tar.NewReader(decompRd)
if _, err := tarReader.Next(); err != nil {
if err == io.EOF {
return true, nil
}
return false, fmt.Errorf("failed to read next archive header: %v", err)
}

return false, nil
}

// do the bulk load of ApplyLayer, but allow for not calling DecompressStream
func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) {
dest = filepath.Clean(dest)
Expand Down

0 comments on commit aade22d

Please sign in to comment.