containerd/oci/tar.go
Akihiro Suda b518f11dba 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>
2017-07-28 04:47:53 +00:00

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()
}