|
@@ -7,6 +7,7 @@ import (
|
|
"net/http"
|
|
"net/http"
|
|
"runtime"
|
|
"runtime"
|
|
"strconv"
|
|
"strconv"
|
|
|
|
+ "strings"
|
|
"sync"
|
|
"sync"
|
|
"time"
|
|
"time"
|
|
|
|
|
|
@@ -29,7 +30,6 @@ import (
|
|
"github.com/containerd/containerd/namespaces"
|
|
"github.com/containerd/containerd/namespaces"
|
|
"github.com/containerd/containerd/platforms"
|
|
"github.com/containerd/containerd/platforms"
|
|
"github.com/containerd/containerd/plugin"
|
|
"github.com/containerd/containerd/plugin"
|
|
- "github.com/containerd/containerd/reference"
|
|
|
|
"github.com/containerd/containerd/remotes"
|
|
"github.com/containerd/containerd/remotes"
|
|
"github.com/containerd/containerd/remotes/docker"
|
|
"github.com/containerd/containerd/remotes/docker"
|
|
"github.com/containerd/containerd/remotes/docker/schema1"
|
|
"github.com/containerd/containerd/remotes/docker/schema1"
|
|
@@ -334,6 +334,14 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor,
|
|
for i := len(manifestStack) - 1; i >= 0; i-- {
|
|
for i := len(manifestStack) - 1; i >= 0; i-- {
|
|
_, err := pushHandler(ctx, manifestStack[i])
|
|
_, err := pushHandler(ctx, manifestStack[i])
|
|
if err != nil {
|
|
if err != nil {
|
|
|
|
+ // TODO(estesp): until we have a more complete method for index push, we need to report
|
|
|
|
+ // missing dependencies in an index/manifest list by sensing the "400 Bad Request"
|
|
|
|
+ // as a marker for this problem
|
|
|
|
+ if (manifestStack[i].MediaType == ocispec.MediaTypeImageIndex ||
|
|
|
|
+ manifestStack[i].MediaType == images.MediaTypeDockerSchema2ManifestList) &&
|
|
|
|
+ errors.Cause(err) != nil && strings.Contains(errors.Cause(err).Error(), "400 Bad Request") {
|
|
|
|
+ return errors.Wrap(err, "manifest list/index references to blobs and/or manifests are missing in your target registry")
|
|
|
|
+ }
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -494,95 +502,27 @@ func (c *Client) Version(ctx context.Context) (Version, error) {
|
|
}, nil
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
|
|
-type imageFormat string
|
|
|
|
-
|
|
|
|
-const (
|
|
|
|
- ociImageFormat imageFormat = "oci"
|
|
|
|
-)
|
|
|
|
-
|
|
|
|
type importOpts struct {
|
|
type importOpts struct {
|
|
- format imageFormat
|
|
|
|
- refObject string
|
|
|
|
- labels map[string]string
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// ImportOpt allows the caller to specify import specific options
|
|
// ImportOpt allows the caller to specify import specific options
|
|
type ImportOpt func(c *importOpts) error
|
|
type ImportOpt func(c *importOpts) error
|
|
|
|
|
|
-// WithImportLabel sets a label to be associated with an imported image
|
|
|
|
-func WithImportLabel(key, value string) ImportOpt {
|
|
|
|
- return func(opts *importOpts) error {
|
|
|
|
- if opts.labels == nil {
|
|
|
|
- opts.labels = make(map[string]string)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- opts.labels[key] = value
|
|
|
|
- return nil
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// WithImportLabels associates a set of labels to an imported image
|
|
|
|
-func WithImportLabels(labels map[string]string) ImportOpt {
|
|
|
|
- return func(opts *importOpts) error {
|
|
|
|
- if opts.labels == nil {
|
|
|
|
- opts.labels = make(map[string]string)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- for k, v := range labels {
|
|
|
|
- opts.labels[k] = v
|
|
|
|
- }
|
|
|
|
- return nil
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// WithOCIImportFormat sets the import format for an OCI image format
|
|
|
|
-func WithOCIImportFormat() ImportOpt {
|
|
|
|
- return func(c *importOpts) error {
|
|
|
|
- if c.format != "" {
|
|
|
|
- return errors.New("format already set")
|
|
|
|
- }
|
|
|
|
- c.format = ociImageFormat
|
|
|
|
- return nil
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// WithRefObject specifies the ref object to import.
|
|
|
|
-// If refObject is empty, it is copied from the ref argument of Import().
|
|
|
|
-func WithRefObject(refObject string) ImportOpt {
|
|
|
|
- return func(c *importOpts) error {
|
|
|
|
- c.refObject = refObject
|
|
|
|
- return nil
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func resolveImportOpt(ref string, opts ...ImportOpt) (importOpts, error) {
|
|
|
|
|
|
+func resolveImportOpt(opts ...ImportOpt) (importOpts, error) {
|
|
var iopts importOpts
|
|
var iopts importOpts
|
|
for _, o := range opts {
|
|
for _, o := range opts {
|
|
if err := o(&iopts); err != nil {
|
|
if err := o(&iopts); err != nil {
|
|
return iopts, err
|
|
return iopts, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- // use OCI as the default format
|
|
|
|
- if iopts.format == "" {
|
|
|
|
- iopts.format = ociImageFormat
|
|
|
|
- }
|
|
|
|
- // if refObject is not explicitly specified, use the one specified in ref
|
|
|
|
- if iopts.refObject == "" {
|
|
|
|
- refSpec, err := reference.Parse(ref)
|
|
|
|
- if err != nil {
|
|
|
|
- return iopts, err
|
|
|
|
- }
|
|
|
|
- iopts.refObject = refSpec.Object
|
|
|
|
- }
|
|
|
|
return iopts, nil
|
|
return iopts, nil
|
|
}
|
|
}
|
|
|
|
|
|
// Import imports an image from a Tar stream using reader.
|
|
// Import imports an image from a Tar stream using reader.
|
|
-// OCI format is assumed by default.
|
|
|
|
-//
|
|
|
|
-// Note that unreferenced blobs are imported to the content store as well.
|
|
|
|
-func (c *Client) Import(ctx context.Context, ref string, reader io.Reader, opts ...ImportOpt) (Image, error) {
|
|
|
|
- iopts, err := resolveImportOpt(ref, opts...)
|
|
|
|
|
|
+// Caller needs to specify importer. Future version may use oci.v1 as the default.
|
|
|
|
+// Note that unreferrenced blobs may be imported to the content store as well.
|
|
|
|
+func (c *Client) Import(ctx context.Context, importer images.Importer, reader io.Reader, opts ...ImportOpt) ([]Image, error) {
|
|
|
|
+ _, err := resolveImportOpt(opts...) // unused now
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
@@ -593,58 +533,66 @@ func (c *Client) Import(ctx context.Context, ref string, reader io.Reader, opts
|
|
}
|
|
}
|
|
defer done()
|
|
defer done()
|
|
|
|
|
|
- switch iopts.format {
|
|
|
|
- case ociImageFormat:
|
|
|
|
- return c.importFromOCITar(ctx, ref, reader, iopts)
|
|
|
|
- default:
|
|
|
|
- return nil, errors.Errorf("unsupported format: %s", iopts.format)
|
|
|
|
|
|
+ imgrecs, err := importer.Import(ctx, c.ContentStore(), reader)
|
|
|
|
+ if err != nil {
|
|
|
|
+ // is.Update() is not called on error
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ is := c.ImageService()
|
|
|
|
+ var images []Image
|
|
|
|
+ for _, imgrec := range imgrecs {
|
|
|
|
+ if updated, err := is.Update(ctx, imgrec, "target"); err != nil {
|
|
|
|
+ if !errdefs.IsNotFound(err) {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ created, err := is.Create(ctx, imgrec)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ imgrec = created
|
|
|
|
+ } else {
|
|
|
|
+ imgrec = updated
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ images = append(images, &image{
|
|
|
|
+ client: c,
|
|
|
|
+ i: imgrec,
|
|
|
|
+ })
|
|
}
|
|
}
|
|
|
|
+ return images, nil
|
|
}
|
|
}
|
|
|
|
|
|
type exportOpts struct {
|
|
type exportOpts struct {
|
|
- format imageFormat
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-// ExportOpt allows callers to set export options
|
|
|
|
|
|
+// ExportOpt allows the caller to specify export-specific options
|
|
type ExportOpt func(c *exportOpts) error
|
|
type ExportOpt func(c *exportOpts) error
|
|
|
|
|
|
-// WithOCIExportFormat sets the OCI image format as the export target
|
|
|
|
-func WithOCIExportFormat() ExportOpt {
|
|
|
|
- return func(c *exportOpts) error {
|
|
|
|
- if c.format != "" {
|
|
|
|
- return errors.New("format already set")
|
|
|
|
|
|
+func resolveExportOpt(opts ...ExportOpt) (exportOpts, error) {
|
|
|
|
+ var eopts exportOpts
|
|
|
|
+ for _, o := range opts {
|
|
|
|
+ if err := o(&eopts); err != nil {
|
|
|
|
+ return eopts, err
|
|
}
|
|
}
|
|
- c.format = ociImageFormat
|
|
|
|
- return nil
|
|
|
|
}
|
|
}
|
|
|
|
+ return eopts, nil
|
|
}
|
|
}
|
|
|
|
|
|
-// TODO: add WithMediaTypeTranslation that transforms media types according to the format.
|
|
|
|
-// e.g. application/vnd.docker.image.rootfs.diff.tar.gzip
|
|
|
|
-// -> application/vnd.oci.image.layer.v1.tar+gzip
|
|
|
|
-
|
|
|
|
// Export exports an image to a Tar stream.
|
|
// Export exports an image to a Tar stream.
|
|
// OCI format is used by default.
|
|
// OCI format is used by default.
|
|
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
|
// It is up to caller to put "org.opencontainers.image.ref.name" annotation to desc.
|
|
-func (c *Client) Export(ctx context.Context, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
|
|
|
|
- var eopts exportOpts
|
|
|
|
- for _, o := range opts {
|
|
|
|
- if err := o(&eopts); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- // use OCI as the default format
|
|
|
|
- if eopts.format == "" {
|
|
|
|
- eopts.format = ociImageFormat
|
|
|
|
|
|
+// TODO(AkihiroSuda): support exporting multiple descriptors at once to a single archive stream.
|
|
|
|
+func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocispec.Descriptor, opts ...ExportOpt) (io.ReadCloser, error) {
|
|
|
|
+ _, err := resolveExportOpt(opts...) // unused now
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
}
|
|
}
|
|
pr, pw := io.Pipe()
|
|
pr, pw := io.Pipe()
|
|
- switch eopts.format {
|
|
|
|
- case ociImageFormat:
|
|
|
|
- go func() {
|
|
|
|
- pw.CloseWithError(c.exportToOCITar(ctx, desc, pw, eopts))
|
|
|
|
- }()
|
|
|
|
- default:
|
|
|
|
- return nil, errors.Errorf("unsupported format: %s", eopts.format)
|
|
|
|
- }
|
|
|
|
|
|
+ go func() {
|
|
|
|
+ pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
|
|
|
|
+ }()
|
|
return pr, nil
|
|
return pr, nil
|
|
}
|
|
}
|