
This PR ensures that we can pull images with manifest lists, aka OCI indexes. After this change, when pulling such an image, the resources will all be available for creating the image. Further support is required to do platform based selection for rootfs creation, so such images may not yet be runnable. This is mostly useful for checkpoint transfers, which use an OCI index for assembling the component set. Signed-off-by: Stephen J Day <stephen.day@docker.com>
172 lines
4.9 KiB
Go
172 lines
4.9 KiB
Go
package images
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/containerd/containerd/content"
|
|
"github.com/containerd/containerd/log"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
var (
|
|
// SkipDesc is used to skip processing of a descriptor and
|
|
// its descendants.
|
|
SkipDesc = fmt.Errorf("skip descriptor")
|
|
|
|
// StopHandler is used to signify that the descriptor
|
|
// has been handled and should not be handled further.
|
|
// This applies only to a single descriptor in a handler
|
|
// chain and does not apply to descendant descriptors.
|
|
StopHandler = fmt.Errorf("stop handler")
|
|
)
|
|
|
|
type Handler interface {
|
|
Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
|
|
}
|
|
|
|
type HandlerFunc func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error)
|
|
|
|
func (fn HandlerFunc) Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
|
|
return fn(ctx, desc)
|
|
}
|
|
|
|
// Handlers returns a handler that will run the handlers in sequence.
|
|
//
|
|
// A handler may return `StopHandler` to stop calling additional handlers
|
|
func Handlers(handlers ...Handler) HandlerFunc {
|
|
return func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) {
|
|
var children []ocispec.Descriptor
|
|
for _, handler := range handlers {
|
|
ch, err := handler.Handle(ctx, desc)
|
|
if err != nil {
|
|
if errors.Cause(err) == StopHandler {
|
|
break
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
children = append(children, ch...)
|
|
}
|
|
|
|
return children, nil
|
|
}
|
|
}
|
|
|
|
// Walk the resources of an image and call the handler for each. If the handler
|
|
// decodes the sub-resources for each image,
|
|
//
|
|
// This differs from dispatch in that each sibling resource is considered
|
|
// synchronously.
|
|
func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
|
|
for _, desc := range descs {
|
|
|
|
children, err := handler.Handle(ctx, desc)
|
|
if err != nil {
|
|
if errors.Cause(err) == SkipDesc {
|
|
return nil // don't traverse the children.
|
|
}
|
|
return err
|
|
}
|
|
|
|
if len(children) > 0 {
|
|
if err := Walk(ctx, handler, children...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Dispatch runs the provided handler for content specified by the descriptors.
|
|
// If the handler decode subresources, they will be visited, as well.
|
|
//
|
|
// Handlers for siblings are run in parallel on the provided descriptors. A
|
|
// handler may return `SkipDesc` to signal to the dispatcher to not traverse
|
|
// any children.
|
|
//
|
|
// Typically, this function will be used with `FetchHandler`, often composed
|
|
// with other handlers.
|
|
//
|
|
// If any handler returns an error, the dispatch session will be canceled.
|
|
func Dispatch(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) error {
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
for _, desc := range descs {
|
|
desc := desc
|
|
|
|
eg.Go(func() error {
|
|
desc := desc
|
|
|
|
children, err := handler.Handle(ctx, desc)
|
|
if err != nil {
|
|
if errors.Cause(err) == SkipDesc {
|
|
return nil // don't traverse the children.
|
|
}
|
|
return err
|
|
}
|
|
|
|
if len(children) > 0 {
|
|
return Dispatch(ctx, handler, children...)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
return eg.Wait()
|
|
}
|
|
|
|
// ChildrenHandler decodes well-known manifests types and returns their children.
|
|
//
|
|
// This is useful for supporting recursive fetch and other use cases where you
|
|
// want to do a full walk of resources.
|
|
//
|
|
// One can also replace this with another implementation to allow descending of
|
|
// arbitrary types.
|
|
func ChildrenHandler(provider content.Provider) HandlerFunc {
|
|
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
var descs []ocispec.Descriptor
|
|
switch desc.MediaType {
|
|
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(stevvooe): We just assume oci manifest, for now. There may be
|
|
// subtle differences from the docker version.
|
|
var manifest ocispec.Manifest
|
|
if err := json.Unmarshal(p, &manifest); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
descs = append(descs, manifest.Config)
|
|
descs = append(descs, manifest.Layers...)
|
|
case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var index ocispec.Index
|
|
if err := json.Unmarshal(p, &index); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
descs = append(descs, index.Manifests...)
|
|
case MediaTypeDockerSchema2Layer, MediaTypeDockerSchema2LayerGzip,
|
|
MediaTypeDockerSchema2Config, ocispec.MediaTypeImageLayer,
|
|
ocispec.MediaTypeImageLayerGzip: // childless data types.
|
|
return nil, nil
|
|
default:
|
|
log.G(ctx).Warnf("encounted unknown type %v; children may not be fetched", desc.MediaType)
|
|
}
|
|
|
|
return descs, nil
|
|
}
|
|
}
|