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>
		
	
		
			
				
	
	
		
			120 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			120 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package containerd
 | 
						|
 | 
						|
import (
 | 
						|
	"archive/tar"
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/containerd/containerd/content"
 | 
						|
	"github.com/containerd/containerd/errdefs"
 | 
						|
	"github.com/containerd/containerd/images"
 | 
						|
	"github.com/containerd/containerd/reference"
 | 
						|
	digest "github.com/opencontainers/go-digest"
 | 
						|
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
)
 | 
						|
 | 
						|
func resolveOCIIndex(idx ocispec.Index, refObject string) (*ocispec.Descriptor, error) {
 | 
						|
	tag, dgst := reference.SplitObject(refObject)
 | 
						|
	if tag == "" && dgst == "" {
 | 
						|
		return nil, errors.Errorf("unexpected object: %q", refObject)
 | 
						|
	}
 | 
						|
	for _, m := range idx.Manifests {
 | 
						|
		if m.Digest == dgst {
 | 
						|
			return &m, nil
 | 
						|
		}
 | 
						|
		annot, ok := m.Annotations[ocispec.AnnotationRefName]
 | 
						|
		if ok && annot == tag && tag != "" {
 | 
						|
			return &m, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, errors.Errorf("not found: %q", refObject)
 | 
						|
}
 | 
						|
 | 
						|
func (c *Client) importFromOCITar(ctx context.Context, ref string, reader io.Reader, iopts importOpts) (Image, error) {
 | 
						|
	tr := tar.NewReader(reader)
 | 
						|
	store := c.ContentStore()
 | 
						|
	var desc *ocispec.Descriptor
 | 
						|
	for {
 | 
						|
		hdr, err := tr.Next()
 | 
						|
		if err == io.EOF {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if hdr.Name == "index.json" {
 | 
						|
			desc, err = onUntarIndexJSON(tr, iopts.refObject)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if strings.HasPrefix(hdr.Name, "blobs/") {
 | 
						|
			if err := onUntarBlob(ctx, tr, store, hdr.Name, hdr.Size); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if desc == nil {
 | 
						|
		return nil, errors.Errorf("no descriptor found for reference object %q", iopts.refObject)
 | 
						|
	}
 | 
						|
	imgrec := images.Image{
 | 
						|
		Name:   ref,
 | 
						|
		Target: *desc,
 | 
						|
	}
 | 
						|
	is := c.ImageService()
 | 
						|
	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
 | 
						|
	}
 | 
						|
 | 
						|
	img := &image{
 | 
						|
		client: c,
 | 
						|
		i:      imgrec,
 | 
						|
	}
 | 
						|
	return img, nil
 | 
						|
}
 | 
						|
 | 
						|
func onUntarIndexJSON(r io.Reader, refObject string) (*ocispec.Descriptor, error) {
 | 
						|
	b, err := ioutil.ReadAll(r)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	var idx ocispec.Index
 | 
						|
	if err := json.Unmarshal(b, &idx); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return resolveOCIIndex(idx, refObject)
 | 
						|
}
 | 
						|
 | 
						|
func onUntarBlob(ctx context.Context, r io.Reader, store content.Store, name string, size int64) error {
 | 
						|
	// name is like "blobs/sha256/deadbeef"
 | 
						|
	split := strings.Split(name, "/")
 | 
						|
	if len(split) != 3 {
 | 
						|
		return errors.Errorf("unexpected name: %q", name)
 | 
						|
	}
 | 
						|
	algo := digest.Algorithm(split[1])
 | 
						|
	if !algo.Available() {
 | 
						|
		return errors.Errorf("unsupported algorithm: %s", algo)
 | 
						|
	}
 | 
						|
	dgst := digest.NewDigestFromHex(algo.String(), split[2])
 | 
						|
	return content.WriteBlob(ctx, store, "unknown-"+dgst.String(), r, size, dgst)
 | 
						|
}
 |