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 +++++++++++++++++++ 5 files changed, 458 insertions(+) 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 (limited to 'cmd') 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 +} -- cgit v1.2.3-54-g00ecf