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>
		
	
		
			
				
	
	
		
			157 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package oci
 | 
						|
 | 
						|
import (
 | 
						|
	"archive/tar"
 | 
						|
	"bytes"
 | 
						|
	"errors"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
 | 
						|
	"github.com/opencontainers/go-digest"
 | 
						|
)
 | 
						|
 | 
						|
// TarWriter is an interface that is implemented by archive/tar.Writer.
 | 
						|
// (Using an interface allows hooking)
 | 
						|
type TarWriter interface {
 | 
						|
	io.WriteCloser
 | 
						|
	Flush() error
 | 
						|
	WriteHeader(hdr *tar.Header) error
 | 
						|
}
 | 
						|
 | 
						|
// Tar is ImageDriver for TAR representation of an OCI image.
 | 
						|
func Tar(w TarWriter) ImageDriver {
 | 
						|
	return &tarDriver{
 | 
						|
		w: w,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type tarDriver struct {
 | 
						|
	w TarWriter
 | 
						|
}
 | 
						|
 | 
						|
func (d *tarDriver) Init() error {
 | 
						|
	headers := []tar.Header{
 | 
						|
		{
 | 
						|
			Name:     "blobs/",
 | 
						|
			Mode:     0755,
 | 
						|
			Typeflag: tar.TypeDir,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Name:     "blobs/" + string(digest.Canonical) + "/",
 | 
						|
			Mode:     0755,
 | 
						|
			Typeflag: tar.TypeDir,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, h := range headers {
 | 
						|
		if err := d.w.WriteHeader(&h); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *tarDriver) Remove(path string) error {
 | 
						|
	return errors.New("Tar does not support Remove")
 | 
						|
}
 | 
						|
 | 
						|
func (d *tarDriver) Reader(path string) (io.ReadCloser, error) {
 | 
						|
	// because tar does not support random access
 | 
						|
	return nil, errors.New("Tar does not support Reader")
 | 
						|
}
 | 
						|
 | 
						|
func (d *tarDriver) Writer(path string, perm os.FileMode) (io.WriteCloser, error) {
 | 
						|
	name := filepath.ToSlash(path)
 | 
						|
	return &tarDriverWriter{
 | 
						|
		w:    d.w,
 | 
						|
		name: name,
 | 
						|
		mode: int64(perm),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// tarDriverWriter is used for writing non-blob files
 | 
						|
// (e.g. oci-layout, index.json)
 | 
						|
type tarDriverWriter struct {
 | 
						|
	bytes.Buffer
 | 
						|
	w    TarWriter
 | 
						|
	name string
 | 
						|
	mode int64
 | 
						|
}
 | 
						|
 | 
						|
func (w *tarDriverWriter) Close() error {
 | 
						|
	if err := w.w.WriteHeader(&tar.Header{
 | 
						|
		Name:     w.name,
 | 
						|
		Mode:     w.mode,
 | 
						|
		Size:     int64(w.Len()),
 | 
						|
		Typeflag: tar.TypeReg,
 | 
						|
	}); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	n, err := io.Copy(w.w, w)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if n < int64(w.Len()) {
 | 
						|
		return io.ErrShortWrite
 | 
						|
	}
 | 
						|
	return w.w.Flush()
 | 
						|
}
 | 
						|
 | 
						|
func (d *tarDriver) BlobWriter(algo digest.Algorithm) (BlobWriter, error) {
 | 
						|
	return &tarBlobWriter{
 | 
						|
		w:        d.w,
 | 
						|
		digester: algo.Digester(),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// tarBlobWriter implements BlobWriter.
 | 
						|
type tarBlobWriter struct {
 | 
						|
	w        TarWriter
 | 
						|
	digester digest.Digester
 | 
						|
	buf      bytes.Buffer // TODO: use tmp file for large buffer?
 | 
						|
}
 | 
						|
 | 
						|
// Write implements io.Writer.
 | 
						|
func (bw *tarBlobWriter) Write(b []byte) (int, error) {
 | 
						|
	n, err := bw.buf.Write(b)
 | 
						|
	if err != nil {
 | 
						|
		return n, err
 | 
						|
	}
 | 
						|
	return bw.digester.Hash().Write(b)
 | 
						|
}
 | 
						|
 | 
						|
func (bw *tarBlobWriter) Commit(size int64, expected digest.Digest) error {
 | 
						|
	path := "blobs/" + bw.digester.Digest().Algorithm().String() + "/" + bw.digester.Digest().Hex()
 | 
						|
	if err := bw.w.WriteHeader(&tar.Header{
 | 
						|
		Name:     path,
 | 
						|
		Mode:     0444,
 | 
						|
		Size:     int64(bw.buf.Len()),
 | 
						|
		Typeflag: tar.TypeReg,
 | 
						|
	}); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	n, err := io.Copy(bw.w, &bw.buf)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if n < int64(bw.buf.Len()) {
 | 
						|
		return io.ErrShortWrite
 | 
						|
	}
 | 
						|
	if size > 0 && size != n {
 | 
						|
		return ErrUnexpectedSize{Expected: size, Actual: n}
 | 
						|
	}
 | 
						|
	if expected != "" && bw.digester.Digest() != expected {
 | 
						|
		return ErrUnexpectedDigest{Expected: expected, Actual: bw.digester.Digest()}
 | 
						|
	}
 | 
						|
	return bw.w.Flush()
 | 
						|
}
 | 
						|
 | 
						|
func (bw *tarBlobWriter) Close() error {
 | 
						|
	// we don't close bw.w (reused for writing another blob)
 | 
						|
	return bw.w.Flush()
 | 
						|
}
 | 
						|
 | 
						|
func (bw *tarBlobWriter) Digest() digest.Digest {
 | 
						|
	return bw.digester.Digest()
 | 
						|
}
 |