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 } }