| package images |
| |
| import ( |
| "context" |
| "fmt" |
| |
| "github.com/containerd/containerd/content" |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| "github.com/pkg/errors" |
| "golang.org/x/sync/errgroup" |
| ) |
| |
| var ( |
| // ErrSkipDesc is used to skip processing of a descriptor and |
| // its descendants. |
| ErrSkipDesc = fmt.Errorf("skip descriptor") |
| |
| // ErrStopHandler 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. |
| ErrStopHandler = fmt.Errorf("stop handler") |
| ) |
| |
| // Handler handles image manifests |
| type Handler interface { |
| Handle(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) |
| } |
| |
| // HandlerFunc function implementing the Handler interface |
| type HandlerFunc func(ctx context.Context, desc ocispec.Descriptor) (subdescs []ocispec.Descriptor, err error) |
| |
| // Handle image manifests |
| 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 `ErrStopHandler` 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) == ErrStopHandler { |
| 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) == ErrSkipDesc { |
| continue // 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 `ErrSkipDesc` 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) == ErrSkipDesc { |
| 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 manifest 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, platform string) HandlerFunc { |
| return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { |
| return Children(ctx, provider, desc, platform) |
| } |
| } |