summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2018-07-13 13:57:04 -0400
committerGitHub <noreply@github.com>2018-07-13 13:57:04 -0400
commit827359c8e6b116b839a95460cc1775a11f84b682 (patch)
tree2c75493ec56ff9f882c766c56f128d933b0dab8b /cmd
parent35b7a875fd9747a6f322e12f358aeacea778eae5 (diff)
parenta04a8d1dd4d375ebe5084bac760dc82f88cfc77f (diff)
downloadpodman-827359c8e6b116b839a95460cc1775a11f84b682.tar.gz
podman-827359c8e6b116b839a95460cc1775a11f84b682.tar.bz2
podman-827359c8e6b116b839a95460cc1775a11f84b682.zip
Merge pull request #1065 from haircommander/pod-start-create-rm
Podman pod create/rm/ps commands with man pages and tests
Diffstat (limited to 'cmd')
-rw-r--r--cmd/podman/main.go1
-rw-r--r--cmd/podman/pod.go25
-rw-r--r--cmd/podman/pod_create.go109
-rw-r--r--cmd/podman/pod_ps.go600
-rw-r--r--cmd/podman/pod_rm.go103
5 files changed, 838 insertions, 0 deletions
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..6cf2920a5
--- /dev/null
+++ b/cmd/podman/pod.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "github.com/urfave/cli"
+)
+
+var (
+ podDescription = `
+ podman pod
+
+ Manage container pods.
+ Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.
+`
+ 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..f86faa409
--- /dev/null
+++ b/cmd/podman/pod_create.go
@@ -0,0 +1,109 @@
+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 <pod_id> command. The pod will be created with the" +
+ " initial state 'created'."
+
+var podCreateFlags = []cli.Flag{
+ cli.StringFlag{
+ Name: "cgroup-parent",
+ Usage: "Set 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 a new empty 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"))
+ }
+ }
+
+ if c.IsSet("cgroup-parent") {
+ options = append(options, libpod.WithPodCgroupParent(c.String("cgroup-parent")))
+ }
+
+ 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")))
+ }
+
+ // always have containers use pod cgroups
+ options = append(options, libpod.WithPodCgroups())
+
+ 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..470810901
--- /dev/null
+++ b/cmd/podman/pod_ps.go
@@ -0,0 +1,600 @@
+package main
+
+import (
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/docker/go-units"
+ "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/projectatomic/libpod/pkg/util"
+ "github.com/urfave/cli"
+)
+
+const (
+ STOPPED = "Stopped"
+ RUNNING = "Running"
+ PAUSED = "Paused"
+ EXITED = "Exited"
+ ERROR = "Error"
+ CREATED = "Created"
+ NUM_CTR_INFO = 10
+)
+
+var (
+ bc_opts batchcontainer.PsOptions
+)
+
+type podPsCtrInfo struct {
+ Name string `"json:name,omitempty"`
+ Id string `"json:id,omitempty"`
+ Status string `"json:status,omitempty"`
+}
+
+type podPsOptions struct {
+ NoTrunc bool
+ Format string
+ Sort string
+ Quiet bool
+ NumberOfContainers bool
+ Cgroup bool
+ NamesOfContainers bool
+ IdsOfContainers bool
+ StatusOfContainers bool
+}
+
+type podPsTemplateParams struct {
+ Created string
+ ID string
+ Name string
+ NumberOfContainers int
+ Status string
+ Cgroup string
+ UsePodCgroup bool
+ ContainerInfo string
+}
+
+// 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 {
+ CreatedAt time.Time `json:"createdAt"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ NumberOfContainers int `json:"numberofcontainers"`
+ Status string `json:"status"`
+ CtrsInfo []podPsCtrInfo `json:"containerinfo,omitempty"`
+ Cgroup string `json:"cgroup,omitempty"`
+ UsePodCgroup bool `json:"podcgroup,omitempty"`
+}
+
+// Type declaration and functions for sorting the pod PS output
+type podPsSorted []podPsJSONParams
+
+func (a podPsSorted) Len() int { return len(a) }
+func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type podPsSortedCreated struct{ podPsSorted }
+
+func (a podPsSortedCreated) Less(i, j int) bool {
+ return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt)
+}
+
+type podPsSortedId struct{ podPsSorted }
+
+func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID }
+
+type podPsSortedNumber struct{ podPsSorted }
+
+func (a podPsSortedNumber) Less(i, j int) bool {
+ return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo)
+}
+
+type podPsSortedName struct{ podPsSorted }
+
+func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name }
+
+type podPsSortedStatus struct{ podPsSorted }
+
+func (a podPsSortedStatus) Less(i, j int) bool {
+ return a.podPsSorted[i].Status < a.podPsSorted[j].Status
+}
+
+var (
+ podPsFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "cgroup",
+ Usage: "Print the Cgroup information of the pod",
+ },
+ cli.BoolFlag{
+ Name: "ctr-names",
+ Usage: "Display the container names",
+ },
+ cli.BoolFlag{
+ Name: "ctr-ids",
+ Usage: "Display the container UUIDs. If no-trunc is not set they will be truncated",
+ },
+ cli.BoolFlag{
+ Name: "ctr-status",
+ Usage: "Display the container status",
+ },
+ cli.StringFlag{
+ Name: "filter, f",
+ Usage: "Filter output based on conditions given",
+ },
+ cli.StringFlag{
+ Name: "format",
+ Usage: "Pretty-print pods to JSON or using a Go template",
+ },
+ cli.BoolFlag{
+ Name: "latest, l",
+ Usage: "Show the latest pod created",
+ },
+ cli.BoolFlag{
+ Name: "no-trunc",
+ Usage: "Do not truncate pod and container IDs",
+ },
+ cli.BoolFlag{
+ Name: "quiet, q",
+ Usage: "Print the numeric IDs of the pods only",
+ },
+ cli.StringFlag{
+ Name: "sort",
+ Usage: "Sort output by created, id, name, or number",
+ Value: "created",
+ },
+ }
+ podPsDescription = "List all pods on system including their names, ids and current state."
+ 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 := podPsCheckFlagsPassed(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")
+ }
+
+ opts := podPsOptions{
+ NoTrunc: c.Bool("no-trunc"),
+ Quiet: c.Bool("quiet"),
+ Sort: c.String("sort"),
+ IdsOfContainers: c.Bool("ctr-ids"),
+ NamesOfContainers: c.Bool("ctr-names"),
+ StatusOfContainers: c.Bool("ctr-status"),
+ }
+
+ opts.Format = genPodPsFormat(c)
+
+ var filterFuncs []libpod.PodFilter
+ if c.String("filter") != "" {
+ filters := strings.Split(c.String("filter"), ",")
+ for _, f := range filters {
+ filterSplit := strings.Split(f, "=")
+ if len(filterSplit) < 2 {
+ return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ }
+ generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1], runtime)
+ if err != nil {
+ return errors.Wrapf(err, "invalid filter")
+ }
+ filterFuncs = append(filterFuncs, generatedFunc)
+ }
+ }
+
+ var pods []*libpod.Pod
+ if c.IsSet("latest") {
+ pod, err := runtime.GetLatestPod()
+ if err != nil {
+ return err
+ }
+ pods = append(pods, pod)
+ } else {
+ pods, err = runtime.GetAllPods()
+ if err != nil {
+ return err
+ }
+ }
+
+ podsFiltered := make([]*libpod.Pod, 0, len(pods))
+ for _, pod := range pods {
+ include := true
+ for _, filter := range filterFuncs {
+ include = include && filter(pod)
+ }
+
+ if include {
+ podsFiltered = append(podsFiltered, pod)
+ }
+ }
+
+ return generatePodPsOutput(podsFiltered, opts, runtime)
+}
+
+// podPsCheckFlagsPassed checks if mutually exclusive flags are passed together
+func podPsCheckFlagsPassed(c *cli.Context) error {
+ // quiet, and format with Go template are mutually exclusive
+ flags := 0
+ if c.Bool("quiet") {
+ flags++
+ }
+ if c.IsSet("format") && c.String("format") != formats.JSONString {
+ flags++
+ }
+ if flags > 1 {
+ return errors.Errorf("quiet and format with Go template are mutually exclusive")
+ }
+ return nil
+}
+
+func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(pod *libpod.Pod) bool, error) {
+ switch filter {
+ case "ctr-ids":
+ return func(p *libpod.Pod) bool {
+ ctrIds, err := p.AllContainersByID()
+ if err != nil {
+ return false
+ }
+ return util.StringInSlice(filterValue, ctrIds)
+ }, nil
+ case "ctr-names":
+ return func(p *libpod.Pod) bool {
+ ctrs, err := p.AllContainers()
+ if err != nil {
+ return false
+ }
+ for _, ctr := range ctrs {
+ if filterValue == ctr.Name() {
+ return true
+ }
+ }
+ return false
+ }, nil
+ case "ctr-number":
+ return func(p *libpod.Pod) bool {
+ ctrIds, err := p.AllContainersByID()
+ if err != nil {
+ return false
+ }
+
+ fVint, err2 := strconv.Atoi(filterValue)
+ if err2 != nil {
+ return false
+ }
+ return len(ctrIds) == fVint
+ }, nil
+ case "ctr-status":
+ if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) {
+ return nil, errors.Errorf("%s is not a valid status", filterValue)
+ }
+ return func(p *libpod.Pod) bool {
+ ctrs, err := p.AllContainers()
+ if err != nil {
+ return false
+ }
+ for _, ctr := range ctrs {
+ status, err := ctr.State()
+ if err != nil {
+ return false
+ }
+ state := status.String()
+ if status == libpod.ContainerStateConfigured {
+ state = "created"
+ }
+ if state == filterValue {
+ return true
+ }
+ }
+ return false
+ }, nil
+ case "id":
+ return func(p *libpod.Pod) bool {
+ return strings.Contains(p.ID(), filterValue)
+ }, nil
+ case "name":
+ return func(p *libpod.Pod) bool {
+ return strings.Contains(p.Name(), filterValue)
+ }, nil
+ case "status":
+ if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) {
+ return nil, errors.Errorf("%s is not a valid pod status", filterValue)
+ }
+ return func(p *libpod.Pod) bool {
+ ctrs, err := p.AllContainers()
+ if err != nil {
+ return false
+ }
+ status, err := getPodStatus(ctrs)
+ if err != nil {
+ return false
+ }
+ if strings.ToLower(status) == filterValue {
+ return true
+ }
+ return false
+ }, nil
+ }
+ return nil, errors.Errorf("%s is an invalid filter", filter)
+}
+
+// generate the template based on conditions given
+func genPodPsFormat(c *cli.Context) string {
+ format := ""
+ if c.String("format") != "" {
+ // "\t" from the command line is not being recognized as a tab
+ // replacing the string "\t" to a tab character if the user passes in "\t"
+ format = strings.Replace(c.String("format"), `\t`, "\t", -1)
+ } else if c.Bool("quiet") {
+ format = formats.IDString
+ } else {
+ format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}"
+ if c.Bool("cgroup") {
+ format += "\t{{.Cgroup}}\t{{.UsePodCgroup}}"
+ }
+ if c.Bool("ctr-names") || c.Bool("ctr-ids") || c.Bool("ctr-status") {
+ format += "\t{{.ContainerInfo}}"
+ } else {
+ format += "\t{{.NumberOfContainers}}"
+ }
+ }
+ 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
+}
+
+func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) {
+ switch sortBy {
+ case "created":
+ sort.Sort(podPsSortedCreated{psOutput})
+ case "id":
+ sort.Sort(podPsSortedId{psOutput})
+ case "name":
+ sort.Sort(podPsSortedName{psOutput})
+ case "number":
+ sort.Sort(podPsSortedNumber{psOutput})
+ case "status":
+ sort.Sort(podPsSortedStatus{psOutput})
+ default:
+ return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number")
+ }
+ return psOutput, nil
+}
+
+// getPodTemplateOutput returns the modified container information
+func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) {
+ var (
+ psOutput []podPsTemplateParams
+ )
+
+ for _, psParam := range psParams {
+ podID := psParam.ID
+ var ctrStr string
+
+ truncated := ""
+ if !opts.NoTrunc {
+ podID = shortID(podID)
+ if len(psParam.CtrsInfo) > NUM_CTR_INFO {
+ psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO]
+ truncated = "..."
+ }
+ }
+ for _, ctrInfo := range psParam.CtrsInfo {
+ ctrStr += "[ "
+ if opts.IdsOfContainers {
+ if opts.NoTrunc {
+ ctrStr += ctrInfo.Id
+ } else {
+ ctrStr += shortID(ctrInfo.Id)
+ }
+ }
+ if opts.NamesOfContainers {
+ ctrStr += ctrInfo.Name + " "
+ }
+ if opts.StatusOfContainers {
+ ctrStr += ctrInfo.Status + " "
+ }
+ ctrStr += "] "
+ }
+ ctrStr += truncated
+ params := podPsTemplateParams{
+ Created: units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago",
+ ID: podID,
+ Name: psParam.Name,
+ Status: psParam.Status,
+ NumberOfContainers: psParam.NumberOfContainers,
+ UsePodCgroup: psParam.UsePodCgroup,
+ Cgroup: psParam.Cgroup,
+ ContainerInfo: ctrStr,
+ }
+
+ psOutput = append(psOutput, params)
+ }
+
+ return psOutput, nil
+}
+
+func getPodStatus(ctrs []*libpod.Container) (string, error) {
+ ctrNum := len(ctrs)
+ if ctrNum == 0 {
+ return CREATED, nil
+ }
+ statuses := map[string]int{
+ STOPPED: 0,
+ RUNNING: 0,
+ PAUSED: 0,
+ CREATED: 0,
+ ERROR: 0,
+ }
+ for _, ctr := range ctrs {
+ state, err := ctr.State()
+ if err != nil {
+ return "", err
+ }
+ switch state {
+ case libpod.ContainerStateStopped:
+ statuses[STOPPED]++
+ case libpod.ContainerStateRunning:
+ statuses[RUNNING]++
+ case libpod.ContainerStatePaused:
+ statuses[PAUSED]++
+ case libpod.ContainerStateCreated, libpod.ContainerStateConfigured:
+ statuses[CREATED]++
+ default:
+ statuses[ERROR]++
+ }
+ }
+
+ if statuses[RUNNING] > 0 {
+ return RUNNING, nil
+ } else if statuses[PAUSED] == ctrNum {
+ return PAUSED, nil
+ } else if statuses[STOPPED] == ctrNum {
+ return EXITED, nil
+ } else if statuses[STOPPED] > 0 {
+ return STOPPED, nil
+ } else if statuses[ERROR] > 0 {
+ return ERROR, nil
+ } else {
+ return CREATED, 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 := pod.AllContainers()
+ ctrsInfo := make([]podPsCtrInfo, 0)
+ if err != nil {
+ return nil, err
+ }
+ ctrNum := len(ctrs)
+ status, err := getPodStatus(ctrs)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, ctr := range ctrs {
+ batchInfo, err := batchcontainer.BatchContainerOp(ctr, bc_opts)
+ if err != nil {
+ return nil, err
+ }
+ var status string
+ switch batchInfo.ConState {
+ case libpod.ContainerStateStopped:
+ status = EXITED
+ case libpod.ContainerStateRunning:
+ status = RUNNING
+ case libpod.ContainerStatePaused:
+ status = PAUSED
+ case libpod.ContainerStateCreated, libpod.ContainerStateConfigured:
+ status = CREATED
+ default:
+ status = ERROR
+ }
+ ctrsInfo = append(ctrsInfo, podPsCtrInfo{
+ Name: batchInfo.ConConfig.Name,
+ Id: ctr.ID(),
+ Status: status,
+ })
+ }
+ params := podPsJSONParams{
+ CreatedAt: pod.CreatedTime(),
+ ID: pod.ID(),
+ Name: pod.Name(),
+ Status: status,
+ Cgroup: pod.CgroupParent(),
+ UsePodCgroup: pod.UsePodCgroup(),
+ NumberOfContainers: ctrNum,
+ CtrsInfo: ctrsInfo,
+ }
+
+ psOutput = append(psOutput, params)
+ }
+ return sortPodPsOutput(opts.Sort, psOutput)
+}
+
+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..8cc46761e
--- /dev/null
+++ b/cmd/podman/pod_rm.go
@@ -0,0 +1,103 @@
+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",
+ },
+ LatestFlag,
+ }
+ 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 containers will not be removed without --force.
+ If --force is specified, all containers will be stopped, then removed.`),
+ Description: podRmDescription,
+ Flags: podRmFlags,
+ Action: podRmCmd,
+ ArgsUsage: "[POD ...]",
+ 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
+ }
+
+ if c.Bool("latest") && c.Bool("all") {
+ return errors.Errorf("--all and --latest cannot be used together")
+ }
+
+ 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") && !c.Bool("latest") {
+ return errors.Errorf("specify one or more pods to remove")
+ }
+
+ var delPods []*libpod.Pod
+ var lastError error
+ if c.IsSet("all") {
+ delPods, err = runtime.GetAllPods()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get pod list")
+ }
+ } else if c.IsSet("latest") {
+ delPod, err := runtime.GetLatestPod()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get latest pod")
+ }
+ delPods = append(delPods, delPod)
+ } else {
+ for _, i := range args {
+ pod, err := runtime.LookupPod(i)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ if lastError != nil {
+ fmt.Fprintln(os.Stderr, lastError)
+ }
+ 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
+}