162 lines
4.5 KiB
Go
162 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
|
||
|
}
|
||
|
}
|