From 1aad3fd96b61705243e8f6ae35f65946916aa8a5 Mon Sep 17 00:00:00 2001 From: haircommander Date: Mon, 9 Jul 2018 13:04:29 -0400 Subject: Podman pod create/rm commands with man page and tests. Includes a very stripped down version of podman pod ps, just for testing Signed-off-by: haircommander --- cmd/podman/main.go | 1 + cmd/podman/pod.go | 24 +++++ cmd/podman/pod_create.go | 117 ++++++++++++++++++++++ cmd/podman/pod_ps.go | 227 ++++++++++++++++++++++++++++++++++++++++++ cmd/podman/pod_rm.go | 89 +++++++++++++++++ completions/bash/podman | 205 ++++++++++++++++++++++++++++++++++++++ docs/podman-pod-create.1.md | 71 +++++++++++++ docs/podman-pod-rm.1.md | 39 ++++++++ docs/podman-pod.1.md | 32 ++++++ libpod/runtime_pod.go | 45 +++++++++ libpod/runtime_pod_linux.go | 2 +- pkg/spec/createconfig.go | 8 ++ test/e2e/libpod_suite_test.go | 46 +++++++++ test/e2e/pod_create_test.go | 84 ++++++++++++++++ test/e2e/pod_rm_test.go | 148 +++++++++++++++++++++++++++ 15 files changed, 1137 insertions(+), 1 deletion(-) create mode 100644 cmd/podman/pod.go create mode 100644 cmd/podman/pod_create.go create mode 100644 cmd/podman/pod_ps.go create mode 100644 cmd/podman/pod_rm.go create mode 100644 docs/podman-pod-create.1.md create mode 100644 docs/podman-pod-rm.1.md create mode 100644 docs/podman-pod.1.md create mode 100644 test/e2e/pod_create_test.go create mode 100644 test/e2e/pod_rm_test.go diff --git a/cmd/podman/main.go b/cmd/podman/main.go index f533a8b13..a83dc5fb4 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -71,6 +71,7 @@ func main() { mountCommand, pauseCommand, psCommand, + podCommand, portCommand, pullCommand, pushCommand, diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go new file mode 100644 index 000000000..f32ae4626 --- /dev/null +++ b/cmd/podman/pod.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + podDescription = ` + podman pod + + manage pods +` + podCommand = cli.Command{ + Name: "pod", + Usage: "Manage pods", + Description: podDescription, + UseShortOptionHandling: true, + Subcommands: []cli.Command{ + podCreateCommand, + podPsCommand, + podRmCommand, + }, + } +) diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go new file mode 100644 index 000000000..9bbc60d4d --- /dev/null +++ b/cmd/podman/pod_create.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" + "os" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var podCreateDescription = "Creates a new empty pod. The pod ID is then" + + " printed to stdout. You can then start it at any time with the" + + " podman pod start command. The pod will be created with the" + + " initial state 'created'." + +var podCreateFlags = []cli.Flag{ + cli.BoolTFlag{ + Name: "cgroup-to-ctr", + Usage: "Tells containers in this pod to use the cgroup created for the pod", + }, + cli.StringFlag{ + Name: "cgroup-parent", + Usage: "Optional parent cgroup for the pod", + }, + cli.StringSliceFlag{ + Name: "label-file", + Usage: "Read in a line delimited file of labels (default [])", + }, + cli.StringSliceFlag{ + Name: "label, l", + Usage: "Set metadata on pod (default [])", + }, + cli.StringFlag{ + Name: "name, n", + Usage: "Assign a name to the pod", + }, + cli.StringFlag{ + Name: "pod-id-file", + Usage: "Write the pod ID to the file", + }, +} + +var podCreateCommand = cli.Command{ + Name: "create", + Usage: "create but do not start a pod", + Description: podCreateDescription, + Flags: podCreateFlags, + Action: podCreateCmd, + SkipArgReorder: true, + UseShortOptionHandling: true, +} + +func podCreateCmd(c *cli.Context) error { + var options []libpod.PodCreateOption + var err error + + if err = validateFlags(c, createFlags); err != nil { + return err + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + defer runtime.Shutdown(false) + + if c.IsSet("pod-id-file") { + if _, err = os.Stat(c.String("pod-id-file")); err == nil { + return errors.Errorf("pod id file exists. ensure another pod is not using it or delete %s", c.String("pod-id-file")) + } + if err = libpod.WriteFile("", c.String("pod-id-file")); err != nil { + return errors.Wrapf(err, "unable to write pod id file %s", c.String("pod-id-file")) + } + } + // BEGIN GetPodCreateOptions + + // TODO make sure this is correct usage + if c.IsSet("cgroup-parent") { + options = append(options, libpod.WithPodCgroupParent(c.String("cgroup-parent"))) + } + + if c.Bool("cgroup-to-ctr") { + options = append(options, libpod.WithPodCgroups()) + } + // LABEL VARIABLES + // TODO make sure this works as expected + labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("label")) + if err != nil { + return errors.Wrapf(err, "unable to process labels") + } + if len(labels) != 0 { + options = append(options, libpod.WithPodLabels(labels)) + } + + if c.IsSet("name") { + options = append(options, libpod.WithPodName(c.String("name"))) + } + + pod, err := runtime.NewPod(options...) + if err != nil { + return err + } + + if c.IsSet("pod-id-file") { + err = libpod.WriteFile(pod.ID(), c.String("pod-id-file")) + if err != nil { + logrus.Error(err) + } + } + + fmt.Printf("%s\n", pod.ID()) + + return nil +} diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go new file mode 100644 index 000000000..f4e2d0ae5 --- /dev/null +++ b/cmd/podman/pod_ps.go @@ -0,0 +1,227 @@ +package main + +import ( + "reflect" + "strings" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/batchcontainer" + "github.com/projectatomic/libpod/cmd/podman/formats" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/urfave/cli" +) + +var ( + opts batchcontainer.PsOptions +) + +type podPsOptions struct { + NoTrunc bool + Format string + Quiet bool + NumberOfContainers bool +} + +type podPsTemplateParams struct { + ID string + Name string + NumberOfContainers int +} + +// podPsJSONParams is used as a base structure for the psParams +// If template output is requested, podPsJSONParams will be converted to +// podPsTemplateParams. +// podPsJSONParams will be populated by data from libpod.Container, +// the members of the struct are the sama data types as their sources. +type podPsJSONParams struct { + ID string `json:"id"` + Name string `json:"name"` + NumberOfContainers int `json:"numberofcontainers"` +} + +var ( + podPsFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "no-trunc", + Usage: "Display the extended information", + }, + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Print the numeric IDs of the pods only", + }, + } + podPsDescription = "Prints out information about pods" + podPsCommand = cli.Command{ + Name: "ps", + Aliases: []string{"ls", "list"}, + Usage: "List pods", + Description: podPsDescription, + Flags: podPsFlags, + Action: podPsCmd, + UseShortOptionHandling: true, + } +) + +func podPsCmd(c *cli.Context) error { + if err := validateFlags(c, podPsFlags); err != nil { + return err + } + + if err := podCheckFlagsPassed(c); err != nil { + return errors.Wrapf(err, "error with flags passed") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + + defer runtime.Shutdown(false) + + if len(c.Args()) > 0 { + return errors.Errorf("too many arguments, ps takes no arguments") + } + + format := genPodPsFormat(c.Bool("quiet")) + + opts := podPsOptions{ + Format: format, + NoTrunc: c.Bool("no-trunc"), + Quiet: c.Bool("quiet"), + } + + var filterFuncs []libpod.PodFilter + + pods, err := runtime.Pods(filterFuncs...) + if err != nil { + return err + } + + return generatePodPsOutput(pods, opts, runtime) +} + +// podCheckFlagsPassed checks if mutually exclusive flags are passed together +func podCheckFlagsPassed(c *cli.Context) error { + // quiet, and format with Go template are mutually exclusive + flags := 0 + if c.Bool("quiet") { + flags++ + } + if flags > 1 { + return errors.Errorf("quiet, and format with Go template are mutually exclusive") + } + return nil +} + +// generate the template based on conditions given +func genPodPsFormat(quiet bool) string { + if quiet { + return formats.IDString + } + format := "table {{.ID}}\t{{.Name}}" + return format +} + +func podPsToGeneric(templParams []podPsTemplateParams, JSONParams []podPsJSONParams) (genericParams []interface{}) { + if len(templParams) > 0 { + for _, v := range templParams { + genericParams = append(genericParams, interface{}(v)) + } + return + } + for _, v := range JSONParams { + genericParams = append(genericParams, interface{}(v)) + } + return +} + +// generate the accurate header based on template given +func (p *podPsTemplateParams) podHeaderMap() map[string]string { + v := reflect.Indirect(reflect.ValueOf(p)) + values := make(map[string]string) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + if value == "ID" { + value = "Pod" + value + } + values[key] = strings.ToUpper(splitCamelCase(value)) + } + return values +} + +// getPodTemplateOutput returns the modified container information +func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) { + var ( + psOutput []podPsTemplateParams + ) + + for _, psParam := range psParams { + podID := psParam.ID + + if !opts.NoTrunc { + podID = shortID(psParam.ID) + } + params := podPsTemplateParams{ + ID: podID, + Name: psParam.Name, + } + + psOutput = append(psOutput, params) + } + + return psOutput, nil +} + +// getAndSortPodJSONOutput returns the container info in its raw, sorted form +func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) ([]podPsJSONParams, error) { + var ( + psOutput []podPsJSONParams + ) + + for _, pod := range pods { + ctrs, err := runtime.ContainersInPod(pod) + if err != nil { + return nil, err + } + ctrNum := len(ctrs) + + params := podPsJSONParams{ + ID: pod.ID(), + Name: pod.Name(), + NumberOfContainers: ctrNum, + } + + psOutput = append(psOutput, params) + } + return psOutput, nil +} + +func generatePodPsOutput(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) error { + if len(pods) == 0 && opts.Format != formats.JSONString { + return nil + } + psOutput, err := getAndSortPodJSONParams(pods, opts, runtime) + if err != nil { + return err + } + var out formats.Writer + + switch opts.Format { + case formats.JSONString: + if err != nil { + return errors.Wrapf(err, "unable to create JSON for output") + } + out = formats.JSONStructArray{Output: podPsToGeneric([]podPsTemplateParams{}, psOutput)} + default: + psOutput, err := getPodTemplateOutput(psOutput, opts) + if err != nil { + return errors.Wrapf(err, "unable to create output") + } + out = formats.StdoutTemplateArray{Output: podPsToGeneric(psOutput, []podPsJSONParams{}), Template: opts.Format, Fields: psOutput[0].podHeaderMap()} + } + + return formats.Writer(out).Out() +} diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go new file mode 100644 index 000000000..db2b1ee51 --- /dev/null +++ b/cmd/podman/pod_rm.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "os" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/urfave/cli" +) + +var ( + podRmFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "force, f", + Usage: "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false", + }, + cli.BoolFlag{ + Name: "all, a", + Usage: "Remove all pods", + }, + } + podRmDescription = "Remove one or more pods" + podRmCommand = cli.Command{ + Name: "rm", + Usage: fmt.Sprintf(`podman rm will remove one or more pods from the host. The pod name or ID can be used. + A pod with running or attached containrs will not be removed. + If --force, -f is specified, all containers will be stopped, then removed.`), + Description: podRmDescription, + Flags: podRmFlags, + Action: podRmCmd, + ArgsUsage: "", + UseShortOptionHandling: true, + } +) + +// saveCmd saves the image to either docker-archive or oci +func podRmCmd(c *cli.Context) error { + ctx := getContext() + if err := validateFlags(c, rmFlags); err != nil { + return err + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + args := c.Args() + + if len(args) == 0 && !c.Bool("all") { + return errors.Errorf("specify one or more pods to remove") + } + + var delPods []*libpod.Pod + var lastError error + if c.Bool("all") || c.Bool("a") { + delPods, err = runtime.Pods() + if err != nil { + return errors.Wrapf(err, "unable to get pod list") + } + } else { + for _, i := range args { + pod, err := runtime.LookupPod(i) + if err != nil { + fmt.Fprintln(os.Stderr, err) + lastError = errors.Wrapf(err, "unable to find pods %s", i) + continue + } + delPods = append(delPods, pod) + } + } + force := c.IsSet("force") + + for _, pod := range delPods { + err = runtime.RemovePod(ctx, pod, force, force) + if err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to delete pod %v", pod.ID()) + } else { + fmt.Println(pod.ID()) + } + } + return lastError +} diff --git a/completions/bash/podman b/completions/bash/podman index c07987a86..079636c24 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -31,6 +31,29 @@ __podman_containers() { __podman_q ps --format "$format" "$@" } + +# __podman_pods returns a list of pods. Additional options to +# `podman pod ps` may be specified in order to filter the list, e.g. +# `__podman_containers --filter status=running` +# By default, only names are returned. +# Set PODMAN_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs. +# An optional first option `--id|--name` may be used to limit the +# output to the IDs or names of matching items. This setting takes +# precedence over the environment setting. +__podman_pods() { + local format + if [ "$1" = "--id" ] ; then + format='{{.ID}}' + shift + elif [ "$1" = "--name" ] ; then + format='{{.Names}}' + shift + else + format='{{.Names}}' + fi + __podman_q pod ps --format "$format" "$@" +} + # __podman_complete_containers applies completion of containers based on the current # value of `$cur` or the value of the optional first option `--cur`, if given. # Additional filters may be appended, see `__podman_containers`. @@ -43,6 +66,23 @@ __podman_complete_containers() { COMPREPLY=( $(compgen -W "$(__podman_containers "$@")" -- "$current") ) } +# __podman_complete_pods applies completion of pods based on the current +# value of `$cur` or the value of the optional first option `--cur`, if given. +# Additional filters may be appended, see `__podman_pods`. +__podman_complete_pods() { + local current="$cur" + if [ "$1" = "--cur" ] ; then + current="$2" + shift 2 + fi + COMPREPLY=( $(compgen -W "$(__podman_pods "$@")" -- "$current") ) +} + +__podman_complete_pod_names() { + local names=( $(__podman_q pod ps --format={{.Name}}) ) + COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") ) +} + __podman_complete_containers_all() { __podman_complete_containers "$@" --all } @@ -1662,10 +1702,12 @@ _podman_container_run() { esac } + _podman_create() { _podman_container_run } + _podman_run() { _podman_container_run } @@ -2013,6 +2055,168 @@ _podman_logout() { _complete_ "$options_with_args" "$boolean_options" } + +_podman_pod_create() { + local options_with_args=" + --cgroup-parent + --cgroup-to-ctr + --podidfile + --label-file + --label + -l + --name + " + + local boolean_options=" + " + _complete_ "$options_with_args" "$boolean_options" +} + +# _podman_pod_inspect() { +# _podman_container_run +# } +# +# _podman_pod_kill() { +# _podman_container_run +# } + +__podman_pod_ps() { + local options_with_args=" + -f + --filter + --format + --sort + " + + local boolean_options=" + --no-trunc + -q + --quiet + --cgroup + --labels + " + _complete_ "$options_with_args" "$boolean_options" +} + +_podman_pod_ls() { + __podman_pod_ps +} + +_podman_pod_list() { + __podman_pod_ps +} + +_podman_pod_ps() { + __podman_pod_ps +} + +# _podman_pod_pause() { +# _podman_container_run +# } +# +# _podman_pod_restart() { +# _podman_container_run +# } + +_podman_pod_rm() { + local options_with_args=" + " + + local boolean_options=" + -a + --all + -f + --force + -rmctr + " + _complete_ "$options_with_args" "$boolean_options" + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; + esac +} + +_podman_pod_start() { + local options_with_args=" + " + + local boolean_options=" + " + _complete_ "$options_with_args" "$boolean_options" + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; + esac +} + +# _podman_pod_stats() { +# _podman_container_run +# } + +# _podman_pod_stop() { +# _podman_container_run +# } + +# _podman_pod_top() { +# _podman_container_run +# } + +# _podman_pod_unpause() { +# _podman_container_run +# } +# +# _podman_pod_wait() { +# _podman_container_run +# } + + +_podman_pod() { + local boolean_options=" + --help + -h + " + subcommands=" + create + inspect + kill + ls + pause + restart + rm + start + stats + stop + top + unpause + wait + " + local aliases=" + list + ps + " + __podman_subcommands "$subcommands $aliases" && return + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) + ;; + esac +} + + + + _podman_podman() { local options_with_args=" --config -c @@ -2050,6 +2254,7 @@ _podman_podman() { mount pause port + pod ps pull push diff --git a/docs/podman-pod-create.1.md b/docs/podman-pod-create.1.md new file mode 100644 index 000000000..66d74a774 --- /dev/null +++ b/docs/podman-pod-create.1.md @@ -0,0 +1,71 @@ +% podman-pod-create "1" + +## NAME +podman\-pod\-create - Create a new pod + +## SYNOPSIS +**podman pod create** [*options*] + +## DESCRIPTION + +Creates an empty pod, or unit of multiple containers, and prepares it to have +containers added to it. The pod id is printed to STDOUT. You can then use +**podman create --pod ...** to add containers to the pod, and +**podman pod start ** to start the pod. + +## OPTIONS + +**-a**, **--attach**=[] + +Not yet implemented. + +**--cgroup-to-ctr**="" +Tells containers in this pod to use the cgroup created for the pod + +**--cgroup-parent**=*true*|*false* + +Path to cgroups under which the cgroup for the pod will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + +**--podidfile**="" + +Write the pod ID to the file + +**--help** + +Print usage statement + +**-l**, **--label**=[] + +Add metadata to a pod (e.g., --label com.example.key=value) + +**--label-file**=[] + +Read in a line delimited file of labels + +**-n**, **--name**="" + +Assign a name to the pod + +The operator can identify a pod in three ways: +UUID long identifier (“f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778”) +UUID short identifier (“f78375b1c487”) +Name (“jonah”) + +podman generates a UUID for each pod, and if a name is not assigned +to the container with **--name** then the daemon will also generate a random +string name. The name is useful any place you need to identify a pod. + +## EXAMPLES + + +# podman pod create --name test + +## SEE ALSO +podman-pod(1) + +## HISTORY +August 2014, updated by Sven Dowideit +September 2014, updated by Sven Dowideit +November 2014, updated by Sven Dowideit +October 2017, converted from Docker documentation to podman by Dan Walsh for podman +July 2018, adapted from create man page by Peter Hunt diff --git a/docs/podman-pod-rm.1.md b/docs/podman-pod-rm.1.md new file mode 100644 index 000000000..725702714 --- /dev/null +++ b/docs/podman-pod-rm.1.md @@ -0,0 +1,39 @@ +% podman-pod-rm "1" + +## NAME +podman\-pod\-rm - Remove one or more pods + +## SYNOPSIS +**podman rm** [*options*] *container* + +## DESCRIPTION +**podman pod rm** will remove one or more pods from the host. The pod name or ID can be used. The -f option stops all containers then removes them before removing the pod. Without the -f option, a pod cannot be removed if it has attached containers. + +## OPTIONS + +**--force, f** + +Stop running containers and delete all stopped containers before removal of pod. + +**--all, a** + +Remove all pods. Can be used in conjunction with -f and -r as well. + +## EXAMPLE + +podman pod rm mywebserverpod + +podman pod rm mywebserverpod myflaskserverpod 860a4b23 + +podman pod rm -f 860a4b23 + +podman pod rm -f -a + +podman pod rm -fa + +## SEE ALSO +podman-pod(1) + +## HISTORY +August 2017, Originally compiled by Ryan Cole +July 2018, Adapted from podman rm man page by Peter Hunt diff --git a/docs/podman-pod.1.md b/docs/podman-pod.1.md new file mode 100644 index 000000000..9b8afaeb0 --- /dev/null +++ b/docs/podman-pod.1.md @@ -0,0 +1,32 @@ +% podman-pod "1" + +## NAME +podman\-pod - Simple management tool for groups of containers, called pods. + +## SYNOPSIS +**podman pod** *subcommand* + +# DESCRIPTION +podman pod is a set of subcommands that manage pods, or groups of containers. + +## SUBCOMMANDS + +| Subcommand | Description | +| ------------------------------------------------- | ------------------------------------------------------------------------------ | +| [podman-pod-create(1)](podman-pod-create.1.md) | Create a new pod. | +| [podman-pod-inspect(1)](podman-pod-inspect.1.md) | Display a pod's configuration. | +| [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process in one or more pods. | +| [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. | +| [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. | +| [podman-pod-restart(1)](podman-pod-restart.1.md) | Restart one or more pods. | +| [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more pods. | +| [podman-pod-start(1)](podman-pod-start.1.md) | Starts one or more pods. | +| [podman-pod-stats(1)](podman-pod-stats.1.md) | Display a live stream of one or more pod's resource usage statistics. | +| [podman-pod-stop(1)](podman-pod-stop.1.md) | Stop one or more running pods. | +| [podman-pod-top(1)](podman-pod-top.1.md) | Display the running processes of a pod. | +| [podman-pod-unpause(1)](podman-pod-unpause.1.md) | Unpause one or more pods. | +| [podman-pod-wait(1)](podman-pod-wait.1.md) | Wait on one or more pods to stop and print their exit codes. | + +## HISTORY +Dec 2016, Originally compiled by Dan Walsh +July 2018, Adapted from podman man page by Peter Hunt diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go index f5a2b017b..3ad8454b4 100644 --- a/libpod/runtime_pod.go +++ b/libpod/runtime_pod.go @@ -52,6 +52,51 @@ func (r *Runtime) HasPod(id string) (bool, error) { return r.state.HasPod(id) } +// ContainerIDsInPod returns the IDs of containers in the pod +func (r *Runtime) ContainerIDsInPod(pod *Pod) ([]string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, ErrRuntimeStopped + } + + return r.state.PodContainersByID(pod) +} + +// ContainersInPod returns the containers in the pod +func (r *Runtime) ContainersInPod(pod *Pod) ([]*Container, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, ErrRuntimeStopped + } + + return r.state.PodContainers(pod) +} + +// ContainerNamesInPod returns the names of containers in the pod +func (r *Runtime) ContainerNamesInPod(pod *Pod) ([]string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, ErrRuntimeStopped + } + + var ctrNames []string + ctrs, err := r.ContainersInPod(pod) + if err != nil { + return nil, err + } + for _, ctr := range ctrs { + ctrNames = append(ctrNames, ctr.Name()) + } + + return ctrNames, nil +} + // LookupPod retrieves a pod by its name or a partial ID // If a partial ID is not unique, an error will be returned func (r *Runtime) LookupPod(idOrName string) (*Pod, error) { diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index 35d095ba3..25340abdb 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -74,7 +74,7 @@ func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) { return nil, errors.Wrapf(err, "error adding pod to state") } - return nil, ErrNotImplemented + return pod, nil } func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) error { diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 7dfb9588c..dbbf99325 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -318,6 +318,14 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib logrus.Debugf("appending name %s", c.Name) options = append(options, libpod.WithName(c.Name)) } + if c.Pod != "" { + logrus.Debugf("appending to pod %s", c.Pod) + pod, err := runtime.LookupPod(c.Pod) + if err != nil { + return nil, errors.Wrapf(err, "unable to add container to pod") + } + options = append(options, runtime.WithPod(pod)) + } if len(c.PortBindings) > 0 { portBindings, err = c.CreatePortBindings() diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 448dbfc04..6ec995bfc 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -224,6 +224,17 @@ func (p *PodmanTest) Cleanup() { } } +// CleanupPod cleans up the temporary store +func (p *PodmanTest) CleanupPod() { + // Remove all containers + session := p.Podman([]string{"pod", "rm", "-fa"}) + session.Wait(90) + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } +} + // GrepString takes session output and behaves like grep. it returns a bool // if successful and an array of strings on positive matches func (s *PodmanSession) GrepString(term string) (bool, []string) { @@ -459,6 +470,15 @@ func (p *PodmanTest) RunTopContainer(name string) *PodmanSession { return p.Podman(podmanArgs) } +func (p *PodmanTest) RunTopContainerInPod(name, pod string) *PodmanSession { + var podmanArgs = []string{"run", "--pod", pod} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + podmanArgs = append(podmanArgs, "-d", ALPINE, "top") + return p.Podman(podmanArgs) +} + //RunLsContainer runs a simple container in the background that // simply runs ls. If the name passed != "", it will have a name func (p *PodmanTest) RunLsContainer(name string) (*PodmanSession, int, string) { @@ -472,6 +492,17 @@ func (p *PodmanTest) RunLsContainer(name string) (*PodmanSession, int, string) { return session, session.ExitCode(), session.OutputToString() } +func (p *PodmanTest) RunLsContainerInPod(name, pod string) (*PodmanSession, int, string) { + var podmanArgs = []string{"run", "--pod", pod} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + podmanArgs = append(podmanArgs, "-d", ALPINE, "ls") + session := p.Podman(podmanArgs) + session.WaitWithDefaultTimeout() + return session, session.ExitCode(), session.OutputToString() +} + //NumberOfContainersRunning returns an int of how many // containers are currently running. func (p *PodmanTest) NumberOfContainersRunning() int { @@ -502,6 +533,21 @@ func (p *PodmanTest) NumberOfContainers() int { return len(containers) } +// NumberOfPods returns an int of how many +// pods are currently defined. +func (p *PodmanTest) NumberOfPods() int { + var pods []string + ps := p.Podman([]string{"pod", "ps", "-q"}) + ps.WaitWithDefaultTimeout() + Expect(ps.ExitCode()).To(Equal(0)) + for _, i := range ps.OutputToStringArray() { + if i != "" { + pods = append(pods, i) + } + } + return len(pods) +} + // NumberOfRunningContainers returns an int of how many containers are currently // running func (p *PodmanTest) NumberOfRunningContainers() int { diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go new file mode 100644 index 000000000..fa420675f --- /dev/null +++ b/test/e2e/pod_create_test.go @@ -0,0 +1,84 @@ +package integration + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman pod create", func() { + var ( + tempdir string + err error + podmanTest PodmanTest + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.CleanupPod() + }) + + It("podman create pod", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + cid := session.OutputToString() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "-q", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString(cid) + Expect(match).To(BeTrue()) + Expect(len(check.OutputToStringArray())).To(Equal(1)) + }) + + It("podman create pod with name", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"pod", "ps", "--no-trunc"}) + check.WaitWithDefaultTimeout() + match, _ := check.GrepString(name) + Expect(match).To(BeTrue()) + }) + + It("podman create pod with doubled name", func() { + name := "test" + session := podmanTest.Podman([]string{"pod", "create", "--name", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "create", "--name", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(1))) + + check := podmanTest.Podman([]string{"pod", "ps", "-q"}) + check.WaitWithDefaultTimeout() + Expect(len(check.OutputToStringArray())).To(Equal(1)) + }) + + It("podman create pod with same name as ctr", func() { + name := "test" + session := podmanTest.Podman([]string{"create", "--name", name, ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "create", "--name", name}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(1))) + + check := podmanTest.Podman([]string{"pod", "ps", "-q"}) + check.WaitWithDefaultTimeout() + Expect(len(check.OutputToStringArray())).To(Equal(1)) + }) +}) diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go new file mode 100644 index 000000000..ca6b4fcbe --- /dev/null +++ b/test/e2e/pod_rm_test.go @@ -0,0 +1,148 @@ +package integration + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman pod rm", func() { + var ( + tempdir string + err error + podmanTest PodmanTest + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.CleanupPod() + + }) + + It("podman pod rm empty pod", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + cid := session.OutputToString() + + result := podmanTest.Podman([]string{"pod", "rm", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + }) + + It("podman pod rm doesn't remove a pod with a container", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + + cid := session.OutputToString() + + session = podmanTest.Podman([]string{"create", "--pod", cid, ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "rm", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(125)) + + result = podmanTest.Podman([]string{"ps", "-qa"}) + result.WaitWithDefaultTimeout() + Expect(len(result.OutputToStringArray())).To(Equal(1)) + }) + + It("podman pod rm -f does remove a running container", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + + cid := session.OutputToString() + + session = podmanTest.Podman([]string{"run", "-d", "--pod", cid, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "rm", "-f", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + result = podmanTest.Podman([]string{"ps", "-q"}) + result.WaitWithDefaultTimeout() + Expect(result.OutputToString()).To(BeEmpty()) + }) + + It("podman pod rm -a doesn't remove a running container", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + + cid1 := session.OutputToString() + + session = podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"run", "-d", "--pod", cid1, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "rm", "-a"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Not(Equal(0))) + + result = podmanTest.Podman([]string{"ps", "-q"}) + result.WaitWithDefaultTimeout() + Expect(len(result.OutputToStringArray())).To(Equal(1)) + + // one pod should have been deleted + result = podmanTest.Podman([]string{"pod", "ps", "-q"}) + result.WaitWithDefaultTimeout() + Expect(len(result.OutputToStringArray())).To(Equal(1)) + }) + + It("podman pod rm -fa removes everything", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + + cid1 := session.OutputToString() + + session = podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + + cid2 := session.OutputToString() + + session = podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"run", "-d", "--pod", cid1, ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-d", "--pod", cid1, ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "-d", "--pod", cid2, ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "-d", "--pod", cid2, nginx}) + session.WaitWithDefaultTimeout() + + result := podmanTest.Podman([]string{"pod", "rm", "-fa"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + result = podmanTest.Podman([]string{"ps", "-q"}) + result.WaitWithDefaultTimeout() + Expect(result.OutputToString()).To(BeEmpty()) + + // one pod should have been deleted + result = podmanTest.Podman([]string{"pod", "ps", "-q"}) + result.WaitWithDefaultTimeout() + Expect(result.OutputToString()).To(BeEmpty()) + }) +}) -- cgit v1.2.3-54-g00ecf