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