From 79c09d4343e597343af3706eabca4b972acbf81b Mon Sep 17 00:00:00 2001 From: umohnani8 Date: Wed, 2 Aug 2017 16:32:44 -0400 Subject: [PATCH] Update kpod load and save for oci-archive Signed-off-by: umohnani8 --- cmd/kpod/load.go | 32 ++++++++++++++++++++++++-------- cmd/kpod/save.go | 36 +++++++++++++++++++++++++----------- completions/bash/kpod | 1 + docs/kpod-load.1.md | 4 ++-- docs/kpod-save.1.md | 19 +++++++++++++++---- libpod/images/copy.go | 39 +++++++++++++++++++++++++++++++-------- test/kpod_load.bats | 16 ++++++++++++++++ test/kpod_save.bats | 12 ++++++++++++ 8 files changed, 126 insertions(+), 33 deletions(-) diff --git a/cmd/kpod/load.go b/cmd/kpod/load.go index f4b75941..6061a16e 100644 --- a/cmd/kpod/load.go +++ b/cmd/kpod/load.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "github.com/containers/storage" - "github.com/kubernetes-incubator/cri-o/libpod/common" "github.com/kubernetes-incubator/cri-o/libpod/images" "github.com/pkg/errors" "github.com/urfave/cli" @@ -16,6 +15,7 @@ import ( type loadOptions struct { input string quiet bool + image string } var ( @@ -54,12 +54,15 @@ func loadCmd(c *cli.Context) error { } args := c.Args() - if len(args) > 0 { + var image string + if len(args) == 1 { + image = args[0] + } + if len(args) > 1 { return errors.New("too many arguments. Requires exactly 1") } input := c.String("input") - quiet := c.Bool("quiet") if input == "/dev/stdin" { fi, err := os.Stdin.Stat() @@ -92,7 +95,8 @@ func loadCmd(c *cli.Context) error { opts := loadOptions{ input: input, - quiet: quiet, + quiet: c.Bool("quiet"), + image: image, } return loadImage(store, opts) @@ -101,9 +105,21 @@ func loadCmd(c *cli.Context) error { // loadImage loads the image from docker-archive or oci to containers-storage // using the pullImage function func loadImage(store storage.Store, opts loadOptions) error { - systemContext := common.GetSystemContext("") + loadOpts := images.CopyOptions{ + Quiet: opts.quiet, + Store: store, + } - src := dockerArchive + opts.input - - return images.PullImage(store, src, false, opts.quiet, systemContext) + src := images.DockerArchive + ":" + opts.input + if err := images.PullImage(src, false, loadOpts); err != nil { + src = images.OCIArchive + ":" + opts.input + // generate full src name with specified image:tag + if opts.image != "" { + src = src + ":" + opts.image + } + if err := images.PullImage(src, false, loadOpts); err != nil { + return errors.Wrapf(err, "error pulling from %q", opts.input) + } + } + return nil } diff --git a/cmd/kpod/save.go b/cmd/kpod/save.go index 97d42f70..54680a2e 100644 --- a/cmd/kpod/save.go +++ b/cmd/kpod/save.go @@ -10,13 +10,10 @@ import ( "github.com/urfave/cli" ) -const ( - dockerArchive = "docker-archive:" -) - type saveOptions struct { output string quiet bool + format string images []string } @@ -31,9 +28,16 @@ var ( Name: "quiet, q", Usage: "Suppress the output", }, + cli.StringFlag{ + Name: "format", + Usage: "Save image to oci-archive", + }, } - saveDescription = "Save an image to docker-archive on the local machine" - saveCommand = cli.Command{ + saveDescription = ` + Save an image to docker-archive or oci-archive on the local machine. + Default is docker-archive` + + saveCommand = cli.Command{ Name: "save", Usage: "Save image to an archive", Description: saveDescription, @@ -60,7 +64,6 @@ func saveCmd(c *cli.Context) error { } output := c.String("output") - quiet := c.Bool("quiet") if output == "/dev/stdout" { fi := os.Stdout @@ -71,7 +74,8 @@ func saveCmd(c *cli.Context) error { opts := saveOptions{ output: output, - quiet: quiet, + quiet: c.Bool("quiet"), + format: c.String("format"), images: args, } @@ -81,9 +85,19 @@ func saveCmd(c *cli.Context) error { // saveImage pushes the image to docker-archive or oci by // calling pushImage func saveImage(store storage.Store, opts saveOptions) error { - dst := dockerArchive + opts.output + var dst string + switch opts.format { + case images.OCIArchive: + dst = images.OCIArchive + ":" + opts.output + case images.DockerArchive: + fallthrough + case "": + dst = images.DockerArchive + ":" + opts.output + default: + return errors.Errorf("unknown format option %q", opts.format) + } - pushOpts := images.CopyOptions{ + saveOpts := images.CopyOptions{ SignaturePolicyPath: "", Store: store, } @@ -92,7 +106,7 @@ func saveImage(store storage.Store, opts saveOptions) error { // future pull requests will fix this for _, image := range opts.images { dest := dst + ":" + image - if err := images.PushImage(image, dest, pushOpts); err != nil { + if err := images.PushImage(image, dest, saveOpts); err != nil { return errors.Wrapf(err, "unable to save %q", image) } } diff --git a/completions/bash/kpod b/completions/bash/kpod index e68f9814..1ebf6c3e 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -332,6 +332,7 @@ _kpod_version() { _kpod_save() { local options_with_args=" --output -o + --format " local boolean_options=" --quiet -q diff --git a/docs/kpod-load.1.md b/docs/kpod-load.1.md index 43201c8f..e1a3257e 100644 --- a/docs/kpod-load.1.md +++ b/docs/kpod-load.1.md @@ -11,8 +11,8 @@ kpod-load - Load an image from docker archive [**--help**|**-h**] ## DESCRIPTION -**kpod load** copies an image from **docker-archive** stored on the local machine. -**kpod load** reads from stdin by default or a file if the **input** flag is set. +**kpod load** copies an image from either **docker-archive** or **oci-archive** stored +on the local machine. **kpod load** reads from stdin by default or a file if the **input** flag is set. The **quiet** flag suppresses the output when set. **kpod [GLOBAL OPTIONS]** diff --git a/docs/kpod-save.1.md b/docs/kpod-save.1.md index 454a18f0..4d4fb12f 100644 --- a/docs/kpod-save.1.md +++ b/docs/kpod-save.1.md @@ -3,7 +3,7 @@ # kpod-save "1" "July 2017" "kpod" ## NAME -kpod-save - Save an image to docker-archive or oci +kpod-save - Save an image to docker-archive or oci-archive ## SYNOPSIS **kpod save** @@ -11,9 +11,10 @@ kpod-save - Save an image to docker-archive or oci [**--help**|**-h**] ## DESCRIPTION -**kpod save** saves an image to either **docker-archive** on the loacl machine. -**kpod save** writes to STDOUT by default and can be redirected to a file -using the **output** flag. The **quiet** flag suppresses the output when set. +**kpod save** saves an image to either **docker-archive** or **oci-archive** +on the local machine, default is **docker-archive**. +**kpod save** writes to STDOUT by default and can be redirected to a file using the **output** flag. +The **quiet** flag suppresses the output when set. **kpod [GLOBAL OPTIONS]** @@ -26,6 +27,12 @@ using the **output** flag. The **quiet** flag suppresses the output when set. **--output, -o** Write to a file, default is STDOUT +**--format** +Save image to **oci-archive** +``` +--format oci-archive +``` + **--quiet, -q** Suppress the output @@ -44,6 +51,10 @@ Suppress the output # kpod save > alpine-all.tar alpine ``` +``` +# kpod save -o oci-alpine.tar --format oci-archive alpine +``` + ## SEE ALSO kpod(1), kpod-load(1), crio(8), crio.conf(5) diff --git a/libpod/images/copy.go b/libpod/images/copy.go index 8a9688e0..255c1000 100644 --- a/libpod/images/copy.go +++ b/libpod/images/copy.go @@ -7,12 +7,13 @@ import ( "syscall" cp "github.com/containers/image/copy" + dockerarchive "github.com/containers/image/docker/archive" "github.com/containers/image/docker/tarfile" "github.com/containers/image/manifest" + ociarchive "github.com/containers/image/oci/archive" "github.com/containers/image/signature" is "github.com/containers/image/storage" "github.com/containers/image/transports/alltransports" - "github.com/containers/image/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/kubernetes-incubator/cri-o/libpod/common" @@ -25,6 +26,15 @@ const ( DefaultRegistry = "docker://" ) +var ( + // DockerArchive is the transport we prepend to an image name + // when saving to docker-archive + DockerArchive = dockerarchive.Transport.Name() + // OCIArchive is the transport we prepend to an image name + // when saving to oci-archive + OCIArchive = ociarchive.Transport.Name() +) + // CopyOptions contains the options given when pushing or pulling images type CopyOptions struct { // Compression specifies the type of compression which is applied to @@ -50,6 +60,8 @@ type CopyOptions struct { // strip or add signatures to the image when pushing (uploading) the // image to a registry. common.SigningOptions + // Quiet suppresses the output when a push or pull happens + Quiet bool } // PushImage pushes the src image to the destination @@ -101,13 +113,15 @@ func PushImage(srcName, destName string, options CopyOptions) error { } // PullImage copies the image from the source to the destination -func PullImage(store storage.Store, imgName string, allTags, quiet bool, sc *types.SystemContext) error { +func PullImage(imgName string, allTags bool, options CopyOptions) error { var ( images []string output io.Writer ) + store := options.Store + sc := common.GetSystemContext(options.SignaturePolicyPath) - if quiet { + if options.Quiet { output = nil } else { output = os.Stdout @@ -124,10 +138,11 @@ func PullImage(store storage.Store, imgName string, allTags, quiet bool, sc *typ } splitArr := strings.Split(imgName, ":") + archFile := splitArr[len(splitArr)-1] // supports pulling from docker-archive, oci, and registries - if splitArr[0] == "docker-archive" { - tarSource := tarfile.NewSource(splitArr[len(splitArr)-1]) + if srcRef.Transport().Name() == DockerArchive { + tarSource := tarfile.NewSource(archFile) manifest, err := tarSource.LoadTarManifest() if err != nil { return errors.Errorf("error retrieving manifest.json: %v", err) @@ -152,9 +167,17 @@ func PullImage(store storage.Store, imgName string, allTags, quiet bool, sc *typ } } } - } else if splitArr[0] == "oci" { - // needs to be implemented in future - return errors.Errorf("oci not supported") + } else if srcRef.Transport().Name() == OCIArchive { + // retrieve the manifest from index.json to access the image name + manifest, err := ociarchive.LoadManifestDescriptor(srcRef) + if err != nil { + return errors.Wrapf(err, "error loading manifest for %q", srcRef) + } + + if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { + return errors.Errorf("error, archive doesn't have a name annotation. Cannot store image with no name") + } + images = append(images, manifest.Annotations["org.opencontainers.image.ref.name"]) } else { images = append(images, imgName) } diff --git a/test/kpod_load.bats b/test/kpod_load.bats index 8cf5cf1b..61f9a2b8 100644 --- a/test/kpod_load.bats +++ b/test/kpod_load.bats @@ -27,6 +27,22 @@ function teardown() { [ "$status" -eq 0 ] } +@test "kpod load oci-archive image" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alpine.tar --format oci-archive $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} $KPOD_OPTIONS rmi $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} load -i alpine.tar + echo "$output" + [ "$status" -eq 0 ] + rm -f alpine.tar + [ "$status" -eq 0 ] + run ${KPOD_BINARY} $KPOD_OPTIONS rmi $IMAGE + [ "$status" -eq 0 ] +} + @test "kpod load using quiet flag" { run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE [ "$status" -eq 0 ] diff --git a/test/kpod_save.bats b/test/kpod_save.bats index d77cd6b5..d961d69e 100644 --- a/test/kpod_save.bats +++ b/test/kpod_save.bats @@ -23,6 +23,18 @@ function teardown() { [ "$status" -eq 0 ] } +@test "kpod save oci flag" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alpine.tar --format oci-archive $IMAGE + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi $IMAGE + [ "$status" -eq 0 ] + rm -f alpine.tar + [ "$status" -eq 0 ] +} + @test "kpod save using stdout" { run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE [ "$status" -eq 0 ]