client: add Import() and Export() for importing/exporting image in OCI format

Export as a tar (Note: "-" can be used for stdout):

    $ ctr images export /tmp/oci-busybox.tar docker.io/library/busybox:latest

Import a tar (Note: "-" can be used for stdin):

    $ ctr images import foo/new:latest /tmp/oci-busybox.tar

Note: media types are not converted at the moment: e.g.
  application/vnd.docker.image.rootfs.diff.tar.gzip
  -> application/vnd.oci.image.layer.v1.tar+gzip

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
Akihiro Suda
2017-06-15 08:50:20 +00:00
parent 856b038437
commit b518f11dba
13 changed files with 904 additions and 284 deletions

119
client.go
View File

@@ -3,6 +3,7 @@ package containerd
import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
@@ -25,6 +26,7 @@ import (
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/reference"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker"
"github.com/containerd/containerd/remotes/docker/schema1"
@@ -552,3 +554,120 @@ func (c *Client) Version(ctx context.Context) (Version, error) {
Revision: response.Revision,
}, nil
}
type imageFormat string
const (
ociImageFormat imageFormat = "oci"
)
type importOpts struct {
format imageFormat
refObject string
}
type ImportOpt func(c *importOpts) error
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) {
var iopts importOpts
for _, o := range opts {
if err := o(&iopts); err != nil {
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
}
// 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...)
if err != nil {
return nil, err
}
switch iopts.format {
case ociImageFormat:
return c.importFromOCITar(ctx, ref, reader, iopts)
default:
return nil, errors.Errorf("unsupported format: %s", iopts.format)
}
}
type exportOpts struct {
format imageFormat
}
type ExportOpt func(c *exportOpts) error
func WithOCIExportFormat() ExportOpt {
return func(c *exportOpts) error {
if c.format != "" {
return errors.New("format already set")
}
c.format = ociImageFormat
return 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.
// OCI format is used by default.
// 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
}
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)
}
return pr, nil
}