5a3151eefc
With this PR, we introduce the concept of image handlers. They support walking a tree of image resource descriptors for doing various tasks related to processing them. Handlers can be dispatched sequentially or in parallel and can be stacked for various effects. The main functionality we introduce here is parameterized fetch without coupling format resolution to the process itself. Two important handlers, `remotes.FetchHandler` and `image.ChildrenHandler` can be composed to implement recursive fetch with full status reporting. The approach can also be modified to filter based on platform or other constraints, unlocking a lot of possibilities. This also includes some light refactoring in the fetch command, in preparation for submission of end to end pull. Signed-off-by: Stephen J Day <stephen.day@docker.com>
161 lines
4.5 KiB
Go
161 lines
4.5 KiB
Go
package image
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
|
|
"github.com/docker/containerd/content"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
const (
|
|
MediaTypeDockerSchema2Layer = "application/vnd.docker.image.rootfs.diff.tar"
|
|
MediaTypeDockerSchema2LayerGzip = "application/vnd.docker.image.rootfs.diff.tar.gzip"
|
|
MediaTypeDockerSchema2Config = "application/vnd.docker.container.image.v1+json"
|
|
MediaTypeDockerSchema2Manifest = "application/vnd.docker.distribution.manifest.v2+json"
|
|
MediaTypeDockerSchema2ManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
|
|
)
|
|
|
|
var SkipDesc = fmt.Errorf("skip descriptor")
|
|
|
|
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.
|
|
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 {
|
|
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) {
|
|
switch desc.MediaType {
|
|
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
|
case MediaTypeDockerSchema2Layer, MediaTypeDockerSchema2LayerGzip,
|
|
MediaTypeDockerSchema2Config:
|
|
return nil, nil
|
|
default:
|
|
return nil, fmt.Errorf("%v not yet supported", desc.MediaType)
|
|
}
|
|
|
|
rc, err := provider.Reader(ctx, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rc.Close()
|
|
|
|
p, err := ioutil.ReadAll(rc)
|
|
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
|
|
}
|
|
|
|
var descs []ocispec.Descriptor
|
|
|
|
descs = append(descs, manifest.Config)
|
|
for _, desc := range manifest.Layers {
|
|
descs = append(descs, desc)
|
|
}
|
|
|
|
return descs, nil
|
|
}
|
|
}
|