c8d/exporter: Use WithSkipMissing

Save the unmodified manifest list to keep the image ID of the
multi-platform images when not all platforms are present.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski 2023-12-21 17:12:33 +01:00
parent a42726f866
commit 2a02403a94
No known key found for this signature in database
GPG key ID: B85EFCFE26DEF92A

View file

@ -22,7 +22,6 @@ import (
dockerarchive "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/platforms"
"github.com/docker/docker/pkg/streamformatter"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
@ -66,9 +65,9 @@ func (i *ImageService) ExportImage(ctx context.Context, names []string, outStrea
// Note: This is only applicable if importing this archive into non-containerd Docker.
// Importing the same archive into containerd, will not restrict the platforms.
archive.WithPlatform(platform),
archive.WithSkipMissing(i.content),
}
contentStore := i.client.ContentStore()
leasesManager := i.client.LeasesService()
lease, err := leasesManager.Create(ctx, leases.WithRandomID())
if err != nil {
@ -81,7 +80,7 @@ func (i *ImageService) ExportImage(ctx context.Context, names []string, outStrea
}()
addLease := func(ctx context.Context, target ocispec.Descriptor) error {
return leaseContent(ctx, contentStore, leasesManager, lease, target)
return leaseContent(ctx, i.content, leasesManager, lease, target)
}
exportImage := func(ctx context.Context, target ocispec.Descriptor, ref reference.Named) error {
@ -89,17 +88,6 @@ func (i *ImageService) ExportImage(ctx context.Context, names []string, outStrea
return err
}
// We may not have locally all the platforms that are specified in the index.
// Export only those manifests that we have.
// TODO(vvoland): Reconsider this when `--platform` is added.
if containerdimages.IsIndexType(target.MediaType) {
desc, err := i.getBestDescriptorForExport(ctx, target)
if err != nil {
return err
}
target = desc
}
if ref != nil {
opts = append(opts, archive.WithManifest(target, ref.String()))
@ -250,6 +238,8 @@ func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outSt
// TODO(vvoland): Allow user to pass platform
containerd.WithImportPlatform(cplatforms.All),
containerd.WithSkipMissing(),
// Create an additional image with dangling name for imported images...
containerd.WithDigestRef(danglingImageName),
// ... but only if they don't have a name or it's invalid.
@ -322,81 +312,3 @@ func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outSt
return nil
}
// getBestDescriptorForExport returns a descriptor which only references content available locally.
// The returned descriptor can be:
// - The same index descriptor - if all content is available
// - Platform specific manifest - if only one manifest from the whole index is available
// - Reduced index descriptor - if not all, but more than one manifest is available
//
// The reduced index descriptor is stored in the content store and may be garbage collected.
// It's advised to pass a context with a lease that's long enough to cover usage of the blob.
func (i *ImageService) getBestDescriptorForExport(ctx context.Context, indexDesc ocispec.Descriptor) (ocispec.Descriptor, error) {
none := ocispec.Descriptor{}
if !containerdimages.IsIndexType(indexDesc.MediaType) {
err := fmt.Errorf("index/manifest-list descriptor expected, got: %s", indexDesc.MediaType)
return none, errdefs.InvalidParameter(err)
}
store := i.client.ContentStore()
children, err := containerdimages.Children(ctx, store, indexDesc)
if err != nil {
if cerrdefs.IsNotFound(err) {
return none, errdefs.NotFound(err)
}
return none, errdefs.System(err)
}
// Check which platform manifests have all their blobs available.
hasMissingManifests := false
var presentManifests []ocispec.Descriptor
for _, mfst := range children {
if containerdimages.IsManifestType(mfst.MediaType) {
available, _, _, missing, err := containerdimages.Check(ctx, store, mfst, nil)
if err != nil {
hasMissingManifests = true
log.G(ctx).WithField("manifest", mfst.Digest).Warn("failed to check manifest's blob availability, won't export")
continue
}
if available && len(missing) == 0 {
presentManifests = append(presentManifests, mfst)
log.G(ctx).WithField("manifest", mfst.Digest).Debug("manifest content present, will export")
} else {
hasMissingManifests = true
log.G(ctx).WithFields(log.Fields{
"manifest": mfst.Digest,
"missing": missing,
}).Debug("manifest is missing, won't export")
}
}
}
if !hasMissingManifests || len(children) == 0 {
// If we have the full image, or it has no manifests, just export the original index.
return indexDesc, nil
} else if len(presentManifests) == 1 {
// If only one platform is present, export that one manifest.
return presentManifests[0], nil
} else if len(presentManifests) == 0 {
// Return error when none of the image's manifest is present.
return none, errdefs.NotFound(fmt.Errorf("none of the manifests is fully present in the content store"))
}
// Create a new index which contains only the manifests we have in store.
index := ocispec.Index{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
MediaType: ocispec.MediaTypeImageIndex,
Manifests: presentManifests,
Annotations: indexDesc.Annotations,
}
reducedIndexDesc, err := storeJson(ctx, store, index.MediaType, index, nil)
if err != nil {
return none, err
}
return reducedIndexDesc, nil
}