diff --git a/README.md b/README.md index bb40dbad..49a39e10 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ It is currently in active development in the Kubernetes community through the [d | [kpod-pull(1)](/docs/kpod-pull.1.md) | Pull an image from a registry | | [kpod-push(1)](/docs/kpod-push.1.md) | Push an image to a specified destination | | [kpod-rmi(1)](/docs/kpod-rmi.1.md) | Removes one or more images | +| [kpod-save(1)](/docs/kpod-save.1.md) | Saves an image to an archive | | [kpod-tag(1)](/docs/kpod-tag.1.md) | Add an additional name to a local image | | [kpod-version(1)](/docs/kpod-version.1.md) | Display the Kpod Version Information | diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index b6e2adf6..47aeeb0a 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -31,6 +31,7 @@ func main() { rmiCommand, tagCommand, versionCommand, + saveCommand, } app.Flags = []cli.Flag{ cli.StringFlag{ diff --git a/cmd/kpod/save.go b/cmd/kpod/save.go new file mode 100644 index 00000000..056a563d --- /dev/null +++ b/cmd/kpod/save.go @@ -0,0 +1,96 @@ +package main + +import ( + "os" + + "github.com/Sirupsen/logrus" + "github.com/containers/storage" + libkpodimage "github.com/kubernetes-incubator/cri-o/libkpod/image" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +const ( + dockerArchive = "docker-archive:" +) + +type saveOptions struct { + output string + quiet bool + images []string +} + +var ( + saveFlags = []cli.Flag{ + cli.StringFlag{ + Name: "output, o", + Usage: "Write to a file, default is STDOUT", + Value: "/dev/stdout", + }, + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Suppress the output", + }, + } + saveDescription = "Save an image to docker-archive on the local machine" + saveCommand = cli.Command{ + Name: "save", + Usage: "Save image to an archive", + Description: saveDescription, + Flags: saveFlags, + Action: saveCmd, + ArgsUsage: "", + } +) + +// saveCmd saves the image to either docker-archive or oci +func saveCmd(c *cli.Context) error { + args := c.Args() + if len(args) == 0 { + return errors.Errorf("need at least 1 argument") + } + + store, err := getStore(c) + if err != nil { + return err + } + + output := c.String("output") + quiet := c.Bool("quiet") + + if output == "/dev/stdout" { + fi := os.Stdout + if logrus.IsTerminal(fi) { + return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") + } + } + + opts := saveOptions{ + output: output, + quiet: quiet, + images: args, + } + + return saveImage(store, opts) +} + +// saveImage pushes the image to docker-archive or oci by +// calling pushImageForSave +func saveImage(store storage.Store, opts saveOptions) error { + dst := dockerArchive + opts.output + + pushOpts := libkpodimage.CopyOptions{ + SignaturePolicyPath: "", + Store: store, + } + + // only one image is supported for now + // future pull requests will fix this + for _, image := range opts.images { + dest := dst + ":" + image + if err := libkpodimage.PushImage(image, dest, pushOpts); err != nil { + return errors.Wrapf(err, "unable to save %q", image) + } + } + return nil +} diff --git a/completions/bash/kpod b/completions/bash/kpod index 0d5a728b..ffc54f8c 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -130,6 +130,16 @@ _kpod_version() { _complete_ "$options_with_args" "$boolean_options" } +_kpod_save() { + local options_with_args=" + --output -o + " + local boolean_options=" + --quiet -q + " + _complete_ "$options_with_args" "$boolean_options" +} + _complete_() { local options_with_args=$1 local boolean_options="$2 -h --help" diff --git a/docs/kpod-save.1.md b/docs/kpod-save.1.md new file mode 100644 index 00000000..a08e23aa --- /dev/null +++ b/docs/kpod-save.1.md @@ -0,0 +1,51 @@ +% kpod(1) kpod-save - Simple tool to save an image to an archive +% Urvashi Mohnani +# kpod-save "1" "July 2017" "kpod" + +## NAME +kpod-save - save an image to docker-archive or oci + +## SYNOPSIS +**kpod save** +**NAME[:TAG]** +[**--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 [GLOBAL OPTIONS]** + +**kpod save [GLOBAL OPTIONS]** + +**kpod save [OPTIONS] NAME[:TAG] [GLOBAL OPTIONS]** + +## OPTIONS + +**--output, -o** +Write to a file, default is STDOUT + +**--quiet, -q** +Suppress the output + +## GLOBAL OPTIONS + +**--help, -h** + Print usage statement + +## EXAMPLES + +``` +# kpod save --quiet -o alpine.tar alpine:2.6 +``` + +``` +# kpod save > alpine-all.tar alpine +``` + +## SEE ALSO +kpod(1), kpod-load(1), crio(8), crio.conf(5) + +## HISTORY +July 2017, Originally compiled by Urvashi Mohnani diff --git a/test/kpod.bats b/test/kpod.bats index 8fb19a11..8ca52c27 100644 --- a/test/kpod.bats +++ b/test/kpod.bats @@ -115,6 +115,7 @@ function teardown() { @test "kpod history json flag" { run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE + [ "$status" -eq 0 ] run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} history --json $IMAGE | python -m json.tool" echo "$output" [ "$status" -eq 0 ] diff --git a/test/kpod_save.bats b/test/kpod_save.bats new file mode 100644 index 00000000..cfa3bad9 --- /dev/null +++ b/test/kpod_save.bats @@ -0,0 +1,54 @@ +#!/usr/bin/env bats + +load helpers + +IMAGE="alpine:latest" +ROOT="$TESTDIR/crio" +RUNROOT="$TESTDIR/crio-run" +KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT --storage-driver vfs" + +function teardown() { + cleanup_test +} + +@test "kpod save output flag" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alpine.tar $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 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save > alpine.tar $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 quiet flag" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -q -o alpine.tar $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 non-existent image" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alpine.tar $IMAGE + echo "$output" + [ "$status" -ne 0 ] +}