From 412b98be26caccda939c084bb9a26508e4e9cdd9 Mon Sep 17 00:00:00 2001 From: umohnani8 Date: Wed, 12 Jul 2017 13:37:16 -0400 Subject: [PATCH] Add 'kpod load' command Signed-off-by: umohnani8 --- README.md | 1 + cmd/kpod/load.go | 109 ++++++++++++++++++++++++++++++++++++++++++ cmd/kpod/main.go | 1 + cmd/kpod/pull.go | 7 +-- cmd/kpod/save.go | 2 +- completions/bash/kpod | 11 +++++ docs/kpod-load.1.md | 69 ++++++++++++++++++++++++++ docs/kpod-pull.1.md | 36 +++++++++++++- libkpod/image/copy.go | 70 +++++++++++++++++---------- test/kpod_load.bats | 50 +++++++++++++++++++ 10 files changed, 323 insertions(+), 33 deletions(-) create mode 100644 cmd/kpod/load.go create mode 100644 docs/kpod-load.1.md create mode 100644 test/kpod_load.bats diff --git a/README.md b/README.md index 49a39e10..a90fef5d 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ It is currently in active development in the Kubernetes community through the [d | [kpod-history(1)](/docs/kpod-history.1.md)] | Shows the history of an image | | [kpod-images(1)](/docs/kpod-images.1.md) | List images in local storage | | [kpod-inspect(1)](/docs/kpod-inspect.1.md) | Display the configuration of a container or image | +| [kpod-load(1)](/docs/kpod-load.1.md) | Load an image from docker archive or oci | | [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 | diff --git a/cmd/kpod/load.go b/cmd/kpod/load.go new file mode 100644 index 00000000..6c2eea53 --- /dev/null +++ b/cmd/kpod/load.go @@ -0,0 +1,109 @@ +package main + +import ( + "io" + "os" + + "io/ioutil" + + "github.com/containers/storage" + "github.com/kubernetes-incubator/cri-o/libkpod/common" + libkpodimage "github.com/kubernetes-incubator/cri-o/libkpod/image" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +type loadOptions struct { + input string + quiet bool +} + +var ( + loadFlags = []cli.Flag{ + cli.StringFlag{ + Name: "input, i", + Usage: "Read from archive file, default is STDIN", + Value: "/dev/stdin", + }, + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Suppress the output", + }, + } + loadDescription = "Loads the image from docker-archive stored on the local machine." + loadCommand = cli.Command{ + Name: "load", + Usage: "load an image from docker archive", + Description: loadDescription, + Flags: loadFlags, + Action: loadCmd, + ArgsUsage: "", + } +) + +// loadCmd gets the image/file to be loaded from the command line +// and calls loadImage to load the image to containers-storage +func loadCmd(c *cli.Context) error { + config, err := getConfig(c) + if err != nil { + return errors.Wrapf(err, "could not get config") + } + store, err := getStore(config) + if err != nil { + return err + } + + args := c.Args() + if len(args) > 0 { + 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() + if err != nil { + return err + } + // checking if loading from pipe + if !fi.Mode().IsRegular() { + outFile, err := ioutil.TempFile("/var/tmp", "kpod") + if err != nil { + return errors.Errorf("error creating file %v", err) + } + defer outFile.Close() + defer os.Remove(outFile.Name()) + + inFile, err := os.OpenFile(input, 0, 0666) + if err != nil { + return errors.Errorf("error reading file %v", err) + } + defer inFile.Close() + + _, err = io.Copy(outFile, inFile) + if err != nil { + return errors.Errorf("error copying file %v", err) + } + + input = outFile.Name() + } + } + + opts := loadOptions{ + input: input, + quiet: quiet, + } + + return loadImage(store, opts) +} + +// 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("") + + src := dockerArchive + opts.input + + return libkpodimage.PullImage(store, src, false, opts.quiet, systemContext) +} diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index 47aeeb0a..5741d684 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -32,6 +32,7 @@ func main() { tagCommand, versionCommand, saveCommand, + loadCommand, } app.Flags = []cli.Flag{ cli.StringFlag{ diff --git a/cmd/kpod/pull.go b/cmd/kpod/pull.go index 37aa933d..1ece94bd 100644 --- a/cmd/kpod/pull.go +++ b/cmd/kpod/pull.go @@ -54,14 +54,11 @@ func pullCmd(c *cli.Context) error { return err } - allTags := false - if c.IsSet("all-tags") { - allTags = c.Bool("all-tags") - } + allTags := c.Bool("all-tags") systemContext := common.GetSystemContext("") - err = libkpodimage.PullImage(store, image, allTags, systemContext) + err = libkpodimage.PullImage(store, image, allTags, false, systemContext) if err != nil { return errors.Errorf("error pulling image from %q: %v", image, err) } diff --git a/cmd/kpod/save.go b/cmd/kpod/save.go index 5caa7d20..ff11d084 100644 --- a/cmd/kpod/save.go +++ b/cmd/kpod/save.go @@ -79,7 +79,7 @@ func saveCmd(c *cli.Context) error { } // saveImage pushes the image to docker-archive or oci by -// calling pushImageForSave +// calling pushImage func saveImage(store storage.Store, opts saveOptions) error { dst := dockerArchive + opts.output diff --git a/completions/bash/kpod b/completions/bash/kpod index ffc54f8c..fc4f3720 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -157,6 +157,16 @@ _complete_() { esac } +_kpod_load() { + local options_with_args=" + --input -i + " + local boolean_options=" + --quiet -q + " + _complete_ "$options_with_args" "$boolean_options" +} + _kpod_kpod() { local options_with_args=" " @@ -173,6 +183,7 @@ _kpod_kpod() { version pull history + load " case "$prev" in diff --git a/docs/kpod-load.1.md b/docs/kpod-load.1.md new file mode 100644 index 00000000..6e9cf632 --- /dev/null +++ b/docs/kpod-load.1.md @@ -0,0 +1,69 @@ +% kpod(1) kpod-load - Simple tool to load an image from an archive to containers-storage +% Urvashi Mohnani +# kpod-load "1" "July 2017" "kpod" + +## NAME +kpod-load - load an image from docker archive + +## SYNOPSIS +**kpod load** +**NAME[:TAG|@DIGEST]** +[**--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. +The **quiet** flag suppresses the output when set. + +**kpod [GLOBAL OPTIONS]** + +**kpod load [GLOBAL OPTIONS]** + +**kpod load [OPTIONS] NAME[:TAG|@DIGEST] [GLOBAL OPTIONS]** + +## OPTIONS + +**--input, -i** +Read from archive file, default is STDIN + +**--quiet, -q** +Suppress the output + +## GLOBAL OPTIONS + +**--help, -h** + Print usage statement + +## EXAMPLES + +``` +# kpod load --quiet -i fedora.tar +``` + +``` +# kpod load < fedora.tar +Getting image source signatures +Copying blob sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0 + 0 B / 4.03 MB [---------------------------------------------------------------] +Copying config sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560 + 0 B / 1.48 KB [---------------------------------------------------------------] +Writing manifest to image destination +Storing signatures +``` + +``` +# cat fedora.tar | kpod load +Getting image source signatures +Copying blob sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0 + 0 B / 4.03 MB [---------------------------------------------------------------] +Copying config sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560 + 0 B / 1.48 KB [---------------------------------------------------------------] +Writing manifest to image destination +Storing signatures +``` + +## SEE ALSO +kpod(1), kpod-save(1), crio(8), crio.conf(5) + +## HISTORY +July 2017, Originally compiled by Urvashi Mohnani diff --git a/docs/kpod-pull.1.md b/docs/kpod-pull.1.md index 7d2f153f..5c5f717d 100644 --- a/docs/kpod-pull.1.md +++ b/docs/kpod-pull.1.md @@ -7,7 +7,7 @@ kpod-pull - Pull an image from a registry ## SYNOPSIS **kpod pull** -**NAME[:TAG|@DISGEST]** +**NAME[:TAG|@DIGEST]** [**--help**|**-h**] ## DESCRIPTION @@ -15,7 +15,39 @@ Copies an image from a registry onto the local machine. **kpod pull** pulls an image from Docker Hub if a registry is not specified in the command line argument. If an image tag is not specified, **kpod pull** defaults to the image with the **latest** tag (if it exists) and pulls it. **kpod pull** can also pull an image -using its digest **kpod pull [image]@[digest]**. +using its digest **kpod pull [image]@[digest]**. **kpod pull** can be used to pull +images from archives and local storage using different transports. + +## imageID +Image stored in local container/storage + +## DESTINATION + + The DESTINATION is a location to store container images + The Image "DESTINATION" uses a "transport":"details" format. + + Multiple transports are supported: + + **atomic:**_hostname_**/**_namespace_**/**_stream_**:**_tag_ + An image served by an OpenShift(Atomic) Registry server. The current OpenShift project and OpenShift Registry instance are by default read from `$HOME/.kube/config`, which is set e.g. using `(oc login)`. + + **dir:**_path_ + An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection. + + **docker://**_docker-reference_ + An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$HOME/.docker/config.json`, which is set e.g. using `(docker login)`. + + **docker-archive:**_path_[**:**_docker-reference_] + An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest. + + **docker-daemon:**_docker-reference_ + An image _docker-reference_ stored in the docker daemon internal storage. _docker-reference_ must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID). + + **oci:**_path_**:**_tag_ + An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_. + + **ostree:**_image_[**@**_/absolute/repo/path_] + An image in local OSTree repository. _/absolute/repo/path_ defaults to _/ostree/repo_. **kpod [GLOBAL OPTIONS]** diff --git a/libkpod/image/copy.go b/libkpod/image/copy.go index 64be749e..7b61dcb6 100644 --- a/libkpod/image/copy.go +++ b/libkpod/image/copy.go @@ -1,13 +1,13 @@ package image import ( - "fmt" "io" "os" + "strings" "syscall" cp "github.com/containers/image/copy" - "github.com/containers/image/docker/reference" + "github.com/containers/image/docker/tarfile" "github.com/containers/image/manifest" "github.com/containers/image/signature" is "github.com/containers/image/storage" @@ -101,34 +101,46 @@ 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 bool, sc *types.SystemContext) error { - defaultName := DefaultRegistry + imgName - var fromName string - var tag string +func PullImage(store storage.Store, imgName string, allTags, quiet bool, sc *types.SystemContext) error { + var ( + images []string + output io.Writer + ) - srcRef, err := alltransports.ParseImageName(defaultName) + if quiet { + output = nil + } else { + output = os.Stdout + } + + srcRef, err := alltransports.ParseImageName(imgName) if err != nil { - srcRef2, err2 := alltransports.ParseImageName(imgName) + defaultName := DefaultRegistry + imgName + srcRef2, err2 := alltransports.ParseImageName(defaultName) if err2 != nil { - return errors.Wrapf(err2, "error parsing image name %q", imgName) + return errors.Errorf("error parsing image name %q: %v", defaultName, err2) } srcRef = srcRef2 } - ref := srcRef.DockerReference() - if ref != nil { - imgName = srcRef.DockerReference().Name() - fromName = imgName - tagged, ok := srcRef.DockerReference().(reference.NamedTagged) - if ok { - imgName = imgName + ":" + tagged.Tag() - tag = tagged.Tag() - } - } + splitArr := strings.Split(imgName, ":") - destRef, err := is.Transport.ParseStoreReference(store, imgName) - if err != nil { - return errors.Wrapf(err, "error parsing full image name %q", imgName) + // supports pulling from docker-archive, oci, and registries + if splitArr[0] == "docker-archive" { + tarSource := tarfile.NewSource(splitArr[len(splitArr)-1]) + manifest, err := tarSource.LoadTarManifest() + if err != nil { + return errors.Errorf("error retrieving manifest.json: %v", err) + } + // to pull all the images stored in one tar file + for i := range manifest { + images = append(images, manifest[i].RepoTags[0]) + } + } else if splitArr[0] == "oci" { + // needs to be implemented in future + return errors.Errorf("oci not supported") + } else { + images = append(images, imgName) } policy, err := signature.DefaultPolicy(sc) @@ -142,8 +154,16 @@ func PullImage(store storage.Store, imgName string, allTags bool, sc *types.Syst } defer policyContext.Destroy() - copyOptions := common.GetCopyOptions(os.Stdout, "", nil, nil, common.SigningOptions{}) + copyOptions := common.GetCopyOptions(output, "", nil, nil, common.SigningOptions{}) - fmt.Println(tag + ": pulling from " + fromName) - return cp.Image(policyContext, destRef, srcRef, copyOptions) + for _, image := range images { + destRef, err := is.Transport.ParseStoreReference(store, image) + if err != nil { + return errors.Errorf("error parsing dest reference name: %v", err) + } + if err = cp.Image(policyContext, destRef, srcRef, copyOptions); err != nil { + return errors.Errorf("error loading image %q: %v", image, err) + } + } + return nil } diff --git a/test/kpod_load.bats b/test/kpod_load.bats new file mode 100644 index 00000000..6dcdd2f8 --- /dev/null +++ b/test/kpod_load.bats @@ -0,0 +1,50 @@ +#!/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 load input flag" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alpine.tar $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 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alpine.tar $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} $KPOD_OPTIONS rmi $IMAGE + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} load -q -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 non-existent file" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} load -i alpine.tar + echo "$output" + [ "$status" -ne 0 ] +}