diff --git a/cmd/kpod/common.go b/cmd/kpod/common.go index b8ffc5ca..6475debe 100644 --- a/cmd/kpod/common.go +++ b/cmd/kpod/common.go @@ -1,7 +1,11 @@ package main import ( + "io" + + cp "github.com/containers/image/copy" is "github.com/containers/image/storage" + "github.com/containers/image/types" "github.com/containers/storage" "github.com/pkg/errors" "github.com/urfave/cli" @@ -9,6 +13,11 @@ import ( func getStore(c *cli.Context) (storage.Store, error) { options := storage.DefaultStoreOptions + if c.GlobalIsSet("root") || c.GlobalIsSet("runroot") { + options.GraphRoot = c.GlobalString("root") + options.RunRoot = c.GlobalString("runroot") + } + if c.GlobalIsSet("storage-driver") { options.GraphDriverName = c.GlobalString("storage-driver") } @@ -43,5 +52,18 @@ func findImage(store storage.Store, image string) (*storage.Image, error) { img = img2 } return img, nil - +} + +func getCopyOptions(reportWriter io.Writer) *cp.Options { + return &cp.Options{ + ReportWriter: reportWriter, + } +} + +func getSystemContext(signaturePolicyPath string) *types.SystemContext { + sc := &types.SystemContext{} + if signaturePolicyPath != "" { + sc.SignaturePolicyPath = signaturePolicyPath + } + return sc } diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index f99afc83..dd4e3d2e 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -4,6 +4,7 @@ import ( "os" "github.com/Sirupsen/logrus" + "github.com/containers/storage/pkg/reexec" "github.com/urfave/cli" ) @@ -11,6 +12,10 @@ import ( const Version string = "0.0.1" func main() { + if reexec.Init() { + return + } + app := cli.NewApp() app.Name = "kpod" app.Usage = "manage pods and images" @@ -20,6 +25,25 @@ func main() { launchCommand, tagCommand, versionCommand, + pullCommand, + } + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "root", + Usage: "path to the root directory in which data, including images, is stored", + }, + cli.StringFlag{ + Name: "runroot", + Usage: "path to the 'run directory' where all state information is stored", + }, + cli.StringFlag{ + Name: "storage-driver, s", + Usage: "select which storage driver is used to manage storage of images and containers (default is overlay2)", + }, + cli.StringSliceFlag{ + Name: "storage-opt", + Usage: "used to pass an option to the storage driver", + }, } if err := app.Run(os.Args); err != nil { diff --git a/cmd/kpod/pull.go b/cmd/kpod/pull.go new file mode 100644 index 00000000..628e5665 --- /dev/null +++ b/cmd/kpod/pull.go @@ -0,0 +1,125 @@ +package main + +import ( + "fmt" + "os" + + "github.com/Sirupsen/logrus" + cp "github.com/containers/image/copy" + "github.com/containers/image/docker/reference" + "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/pkg/errors" + "github.com/urfave/cli" +) + +const ( + // DefaultRegistry is a prefix that we apply to an image name + // to check docker hub first for the image + DefaultRegistry = "docker://" +) + +var ( + pullFlags = []cli.Flag{ + cli.BoolFlag{ + // all-tags is hidden since it has not been implemented yet + Name: "all-tags, a", + Hidden: true, + Usage: "Download all tagged images in the repository", + }, + } + + pullDescription = "Pulls an image from a registry and stores it locally.\n" + + "An image can be pulled using its tag or digest. If a tag is not\n" + + "specified, the image with the 'latest' tag (if it exists) is pulled." + pullCommand = cli.Command{ + Name: "pull", + Usage: "pull an image from a registry", + Description: pullDescription, + Flags: pullFlags, + Action: pullCmd, + ArgsUsage: "", + } +) + +// pullCmd gets the data from the command line and calls pullImage +// to copy an image from a registry to a local machine +func pullCmd(c *cli.Context) error { + args := c.Args() + if len(args) == 0 { + logrus.Errorf("an image name must be specified") + return nil + } + if len(args) > 1 { + logrus.Errorf("too many arguments. Requires exactly 1") + return nil + } + image := args[0] + + store, err := getStore(c) + if err != nil { + return err + } + + allTags := false + if c.IsSet("all-tags") { + allTags = c.Bool("all-tags") + } + + systemContext := getSystemContext("") + + err = pullImage(store, image, allTags, systemContext) + if err != nil { + return errors.Errorf("error pulling image from %q: %v", image, err) + } + return nil +} + +// 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 + + srcRef, err := alltransports.ParseImageName(defaultName) + if err != nil { + srcRef2, err2 := alltransports.ParseImageName(imgName) + if err2 != nil { + return errors.Wrapf(err2, "error parsing image name %q", imgName) + } + 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() + } + } + + destRef, err := is.Transport.ParseStoreReference(store, imgName) + if err != nil { + return errors.Wrapf(err, "error parsing full image name %q", imgName) + } + + policy, err := signature.DefaultPolicy(sc) + if err != nil { + return err + } + + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return err + } + + fmt.Println(tag + ": pulling from " + fromName) + + return cp.Image(policyContext, destRef, srcRef, getCopyOptions(os.Stdout)) +} diff --git a/completions/bash/kpod b/completions/bash/kpod index 51a328dc..ca681342 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -33,7 +33,7 @@ _kpod_version() { local boolean_options=" " _complete_ "$options_with_args" "$boolean_options" - } +} kpod_tag() { local options_with_args=" @@ -43,6 +43,15 @@ kpod_tag() { _complete_ "$options_with_args" "$boolean_options" } +_kpod_pull() { + local options_with_args=" + " + local boolean_options=" + --all-tags -a + " + _complete_ "$options_with_args" "$boolean_options" +} + _kpod_kpod() { local options_with_args=" " @@ -54,6 +63,7 @@ _kpod_kpod() { launch tag version + pull " case "$prev" in diff --git a/docs/kpod-pull.1.md b/docs/kpod-pull.1.md new file mode 100644 index 00000000..1e7df266 --- /dev/null +++ b/docs/kpod-pull.1.md @@ -0,0 +1,39 @@ +% kpod(8) # kpod-pull - Simple tool to pull an image from a registry +% Urvashi Mohnani +% JULY 2017 +# NAME +kpod-pull - Pull an image from a registry + +# SYNOPSIS +**kpod pull** +**NAME[:TAG|@DISGEST]** +[**--help**|**-h**] + +# DESCRIPTION +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]**. + +**kpod [GLOBAL OPTIONS]** + +**kpod pull [GLOBAL OPTIONS]** + +**kpod pull NAME[:TAG|@DIGEST] [GLOBAL OPTIONS]** + +# GLOBAL OPTIONS + +**--help, -h** + Print usage statement + +# COMMANDS + +## pull +Pull an image from a registry + +# SEE ALSO +kpod(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 fb8cfaf1..057d82f5 100644 --- a/test/kpod.bats +++ b/test/kpod.bats @@ -2,8 +2,48 @@ load helpers +ROOT="$TESTDIR/crio" +RUNROOT="$TESTDIR/crio-run" +KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT --storage-driver vfs" + @test "kpod version test" { run ${KPOD_BINARY} version echo "$output" [ "$status" -eq 0 ] } + +@test "kpod pull from docker with tag" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull debian:6.0.10 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "kpod pull from docker without tag" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull debian + echo "$output" + [ "$status" -eq 0 ] +} + +@test "kpod pull from a non-docker registry with tag" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull registry.fedoraproject.org/fedora:rawhide + echo "$output" + [ "$status" -eq 0 ] +} + +@test "kpod pull from a non-docker registry without tag" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull registry.fedoraproject.org/fedora + echo "$output" + [ "$status" -eq 0 ] +} + +@test "kpod pull using digest" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull debian@sha256:7d067f77d2ae5a23fe6920f8fbc2936c4b0d417e9d01b26372561860750815f0 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "kpod pull from a non existent image" { + run ${KPOD_BINARY} $KPOD_OPTIONS pull umohnani/get-started + echo "$output" + [ "$status" -ne 0 ] +}