diff --git a/cmd/kpod/common.go b/cmd/kpod/common.go new file mode 100644 index 00000000..b8ffc5ca --- /dev/null +++ b/cmd/kpod/common.go @@ -0,0 +1,47 @@ +package main + +import ( + is "github.com/containers/image/storage" + "github.com/containers/storage" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +func getStore(c *cli.Context) (storage.Store, error) { + options := storage.DefaultStoreOptions + if c.GlobalIsSet("storage-driver") { + options.GraphDriverName = c.GlobalString("storage-driver") + } + if c.GlobalIsSet("storage-opt") { + opts := c.GlobalStringSlice("storage-opt") + if len(opts) > 0 { + options.GraphDriverOptions = opts + } + } + store, err := storage.GetStore(options) + if err != nil { + return nil, err + } + is.Transport.SetStore(store) + return store, nil +} + +func findImage(store storage.Store, image string) (*storage.Image, error) { + var img *storage.Image + ref, err := is.Transport.ParseStoreReference(store, image) + if err == nil { + img, err = is.Transport.GetStoreImage(store, ref) + } + if err != nil { + img2, err2 := store.Image(image) + if err2 != nil { + if ref == nil { + return nil, errors.Wrapf(err, "error parsing reference to image %q", image) + } + return nil, errors.Wrapf(err, "unable to locate image %q", image) + } + img = img2 + } + return img, nil + +} diff --git a/cmd/kpod/common_test.go b/cmd/kpod/common_test.go new file mode 100644 index 00000000..b975fcd3 --- /dev/null +++ b/cmd/kpod/common_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "os/user" + "testing" + + "flag" + + "github.com/urfave/cli" +) + +func TestGetStore(t *testing.T) { + u, err := user.Current() + if err != nil { + t.Log("Could not determine user. Running as root may cause tests to fail") + } else if u.Uid != "0" { + t.Fatal("tests will fail unless run as root") + } + + set := flag.NewFlagSet("test", 0) + globalSet := flag.NewFlagSet("test", 0) + globalSet.String("root", "", "path to the root directory in which data, including images, is stored") + globalCtx := cli.NewContext(nil, globalSet, nil) + command := cli.Command{Name: "imagesCommand"} + c := cli.NewContext(nil, set, globalCtx) + c.Command = command + + _, err = getStore(c) + if err != nil { + t.Error(err) + } +} diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index 26a354a1..f99afc83 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -18,6 +18,7 @@ func main() { app.Commands = []cli.Command{ launchCommand, + tagCommand, versionCommand, } diff --git a/cmd/kpod/tag.go b/cmd/kpod/tag.go new file mode 100644 index 00000000..575290d3 --- /dev/null +++ b/cmd/kpod/tag.go @@ -0,0 +1,73 @@ +package main + +import ( + "github.com/containers/image/docker/reference" + "github.com/containers/storage" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + tagDescription = "Adds one or more additional names to locally-stored image" + tagCommand = cli.Command{ + Name: "tag", + Usage: "Add an additional name to a local image", + Description: tagDescription, + Action: tagCmd, + ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", + } +) + +func tagCmd(c *cli.Context) error { + args := c.Args() + if len(args) < 2 { + return errors.Errorf("image name and at least one new name must be specified") + } + store, err := getStore(c) + if err != nil { + return err + } + img, err := findImage(store, args[0]) + if err != nil { + return err + } + if img == nil { + return errors.New("null image") + } + err = addImageNames(store, img, args[1:]) + if err != nil { + return errors.Wrapf(err, "error adding names %v to image %q", args[1:], args[0]) + } + return nil +} + +func addImageNames(store storage.Store, image *storage.Image, addNames []string) error { + // Add tags to the names if applicable + names, err := expandedTags(addNames) + if err != nil { + return err + } + err = store.SetNames(image.ID, append(image.Names, names...)) + if err != nil { + return errors.Wrapf(err, "error adding names (%v) to image %q", names, image.ID) + } + return nil +} + +func expandedTags(tags []string) ([]string, error) { + expandedNames := []string{} + for _, tag := range tags { + name, err := reference.ParseNormalizedNamed(tag) + if err != nil { + return nil, errors.Wrapf(err, "error parsing tag %q", name) + } + + name = reference.TagNameOnly(name) + newTag := "" + if tagged, ok := name.(reference.NamedTagged); ok { + newTag = tagged.Tag() + } + expandedNames = append(expandedNames, name.Name()+":"+newTag) + } + return expandedNames, nil +} diff --git a/completions/bash/kpod b/completions/bash/kpod index d11acf94..51a328dc 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -33,6 +33,14 @@ _kpod_version() { local boolean_options=" " _complete_ "$options_with_args" "$boolean_options" + } + +kpod_tag() { + local options_with_args=" + " + local boolean_options=" + " + _complete_ "$options_with_args" "$boolean_options" } _kpod_kpod() { @@ -44,6 +52,7 @@ _kpod_kpod() { " commands=" launch + tag version " diff --git a/docs/kpod-tag.1.md b/docs/kpod-tag.1.md new file mode 100644 index 00000000..497b90b3 --- /dev/null +++ b/docs/kpod-tag.1.md @@ -0,0 +1,29 @@ +## kpod-tag "1" "June 2017" "kpod" + +# NAME +kpod tag - add tags to an image + +# SYNOPSIS +**kpod tag** +[**--help**|**-h**] + +# DESCRIPTION +Assigns a new alias to an image in a registry. An alias refers to the entire image name, including the optional **TAG** after the ':' + +**kpod [GLOBAL OPTIONS]** + +**kpod [GLOBAL OPTIONS] launch [OPTIONS]** + +# GLOBAL OPTIONS + +**--help, -h** + Print usage statement + +# EXAMPLES + + kpod tag 0e3bbc2 fedora:latest + + kpod tag httpd myregistryhost:5000/fedora/httpd:v2 + +# SEE ALSO +kpod(1), crio(8), crio.conf(5) diff --git a/docs/kpod.1.md b/docs/kpod.1.md index 7570f309..c29a66ed 100644 --- a/docs/kpod.1.md +++ b/docs/kpod.1.md @@ -32,6 +32,9 @@ has the capability to debug pods/images created by crio. ## launch Launch a pod +## tag +Add one or more additional names to locally-stored image + # SEE ALSO crio(8), crio.conf(5)