c8d/commit: Don't produce an empty layer
If the diff is empty and don't produce an empty layer. Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
parent
a01bcf9767
commit
eb56493f4e
2 changed files with 76 additions and 32 deletions
|
@ -21,6 +21,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"
|
||||
|
@ -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 (
|
||||
|
@ -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)
|
||||
|
||||
imageConfig := generateCommitImageConfig(ociimage, diffID, cc)
|
||||
layers := parentManifest.Layers
|
||||
if diffLayerDesc != nil {
|
||||
rootfsID := identity.ChainID(imageConfig.RootFS.DiffIDs).String()
|
||||
|
||||
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)
|
||||
if err := applyDiffLayer(ctx, rootfsID, parentImage, 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
|
||||
|
@ -130,6 +134,12 @@ func generateCommitImageConfig(baseConfig ocispec.Image, diffID digest.Digest, o
|
|||
log.G(context.TODO()).Warnf("assuming os=%q", os)
|
||||
}
|
||||
log.G(context.TODO()).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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
|
|
@ -224,6 +224,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)
|
||||
|
|
Loading…
Add table
Reference in a new issue