Skip to content

Commit

Permalink
Merge pull request #2633 from dmcgowan/import-docker
Browse files Browse the repository at this point in the history
Support importing docker images
  • Loading branch information
estesp committed Sep 19, 2018
2 parents 3c2668d + da6d290 commit 1ac5ac6
Show file tree
Hide file tree
Showing 12 changed files with 510 additions and 323 deletions.
72 changes: 45 additions & 27 deletions cmd/ctr/commands/images/import.go
Expand Up @@ -20,10 +20,11 @@ import (
"fmt"
"io"
"os"
"time"

"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/images"
oci "github.com/containerd/containerd/images/oci"
"github.com/containerd/containerd/images/archive"
"github.com/containerd/containerd/log"
"github.com/urfave/cli"
)
Expand All @@ -34,45 +35,58 @@ var importCommand = cli.Command{
ArgsUsage: "[flags] <in>",
Description: `Import images from a tar stream.
Implemented formats:
- oci.v1 (default)
- oci.v1
- docker.v1.1
- docker.v1.2
For oci.v1 format, you need to specify --oci-name because an OCI archive contains image refs (tags)
but does not contain the base image name.
For OCI v1, you may need to specify --base-name because an OCI archive may
contain only partial image references (tags without the base image name).
If no base image name is provided, a name will be generated as "import-%{yyyy-MM-dd}".
e.g.
$ ctr images import --format oci.v1 --oci-name foo/bar foobar.tar
$ ctr images import --base-name foo/bar foobar.tar
If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadbeef", the command will create
"foo/bar:latest" and "foo/bar@sha256:deadbeef" images in the containerd store.
`,
Flags: append([]cli.Flag{
cli.StringFlag{
Name: "format",
Value: "oci.v1",
Usage: "image format. See DESCRIPTION.",
Name: "base-name",
Value: "",
Usage: "base image name for added images, when provided only images with this name prefix are imported",
},
cli.BoolFlag{
Name: "digests",
Usage: "whether to create digest images (default: false)",
},
cli.StringFlag{
Name: "oci-name",
Value: "unknown/unknown",
Usage: "prefix added to either oci.v1 ref annotation or digest",
Name: "index-name",
Usage: "image name to keep index as, by default index is discarded",
},
// TODO(AkihiroSuda): support commands.LabelFlag (for all children objects)
}, commands.SnapshotterFlags...),

Action: func(context *cli.Context) error {
var (
in = context.Args().First()
imageImporter images.Importer
in = context.Args().First()
opts []containerd.ImportOpt
)

switch format := context.String("format"); format {
case "oci.v1":
imageImporter = &oci.V1Importer{
ImageName: context.String("oci-name"),
}
default:
return fmt.Errorf("unknown format %s", format)
prefix := context.String("base-name")
if prefix == "" {
prefix = fmt.Sprintf("import-%s", time.Now().Format("2006-01-02"))
opts = append(opts, containerd.WithImageRefTranslator(archive.AddRefPrefix(prefix)))
} else {
// When provided, filter out references which do not match
opts = append(opts, containerd.WithImageRefTranslator(archive.FilterRefPrefix(prefix)))
}

if context.Bool("digests") {
opts = append(opts, containerd.WithDigestRef(archive.DigestTranslator(prefix)))
}

if idxName := context.String("index-name"); idxName != "" {
opts = append(opts, containerd.WithIndexName(idxName))
}

client, ctx, cancel, err := commands.NewClient(context)
Expand All @@ -90,20 +104,24 @@ If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadb
return err
}
}
imgs, err := client.Import(ctx, imageImporter, r)
imgs, err := client.Import(ctx, r, opts...)
closeErr := r.Close()
if err != nil {
return err
}
if err = r.Close(); err != nil {
return err
if closeErr != nil {
return closeErr
}

log.G(ctx).Debugf("unpacking %d images", len(imgs))

for _, img := range imgs {
// TODO: Allow configuration of the platform
image := containerd.NewImage(client, img)

// TODO: Show unpack status
fmt.Printf("unpacking %s (%s)...", img.Name(), img.Target().Digest)
err = img.Unpack(ctx, context.String("snapshotter"))
fmt.Printf("unpacking %s (%s)...", img.Name, img.Target.Digest)
err = image.Unpack(ctx, context.String("snapshotter"))
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions content/helpers.go
Expand Up @@ -70,7 +70,7 @@ func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, desc o
cw, err := OpenWriter(ctx, cs, WithRef(ref), WithDescriptor(desc))
if err != nil {
if !errdefs.IsAlreadyExists(err) {
return err
return errors.Wrap(err, "failed to open writer")
}

return nil // all ready present
Expand Down Expand Up @@ -127,7 +127,7 @@ func OpenWriter(ctx context.Context, cs Ingester, opts ...WriterOpt) (Writer, er
func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected digest.Digest, opts ...Opt) error {
ws, err := cw.Status()
if err != nil {
return err
return errors.Wrap(err, "failed to get status")
}

if ws.Offset > 0 {
Expand All @@ -138,7 +138,7 @@ func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected dige
}

if _, err := copyWithBuffer(cw, r); err != nil {
return err
return errors.Wrap(err, "failed to copy")
}

if err := cw.Commit(ctx, size, expected, opts...); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions content/proxy/content_writer.go
Expand Up @@ -57,7 +57,7 @@ func (rw *remoteWriter) Status() (content.Status, error) {
Action: contentapi.WriteActionStat,
})
if err != nil {
return content.Status{}, errors.Wrap(err, "error getting writer status")
return content.Status{}, errors.Wrap(errdefs.FromGRPC(err), "error getting writer status")
}

return content.Status{
Expand All @@ -82,7 +82,7 @@ func (rw *remoteWriter) Write(p []byte) (n int, err error) {
Data: p,
})
if err != nil {
return 0, err
return 0, errors.Wrap(errdefs.FromGRPC(err), "failed to send write")
}

n = int(resp.Offset - offset)
Expand Down Expand Up @@ -112,7 +112,7 @@ func (rw *remoteWriter) Commit(ctx context.Context, size int64, expected digest.
Labels: base.Labels,
})
if err != nil {
return errdefs.FromGRPC(err)
return errors.Wrap(errdefs.FromGRPC(err), "commit failed")
}

if size != 0 && resp.Offset != size {
Expand Down
3 changes: 2 additions & 1 deletion export.go
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/containerd/containerd/images"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)

type exportOpts struct {
Expand Down Expand Up @@ -51,7 +52,7 @@ func (c *Client) Export(ctx context.Context, exporter images.Exporter, desc ocis
}
pr, pw := io.Pipe()
go func() {
pw.CloseWithError(exporter.Export(ctx, c.ContentStore(), desc, pw))
pw.CloseWithError(errors.Wrap(exporter.Export(ctx, c.ContentStore(), desc, pw), "export failed"))
}()
return pr, nil
}

0 comments on commit 1ac5ac6

Please sign in to comment.