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:
156
oci/tar.go
Normal file
156
oci/tar.go
Normal file
@@ -0,0 +1,156 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user