summaryrefslogtreecommitdiff
path: root/cmd/podman
diff options
context:
space:
mode:
authorhaircommander <pehunt@redhat.com>2018-07-09 17:48:20 -0400
committerhaircommander <pehunt@redhat.com>2018-07-13 09:17:33 -0400
commita04a8d1dd4d375ebe5084bac760dc82f88cfc77f (patch)
tree4b02c7e49ec737c6e9ffd4412e5212b856df518c /cmd/podman
parent1aad3fd96b61705243e8f6ae35f65946916aa8a5 (diff)
downloadpodman-a04a8d1dd4d375ebe5084bac760dc82f88cfc77f.tar.gz
podman-a04a8d1dd4d375ebe5084bac760dc82f88cfc77f.tar.bz2
podman-a04a8d1dd4d375ebe5084bac760dc82f88cfc77f.zip
Added full podman pod ps, with tests and man page
Signed-off-by: haircommander <pehunt@redhat.com>
Diffstat (limited to 'cmd/podman')
-rw-r--r--cmd/podman/pod.go3
-rw-r--r--cmd/podman/pod_create.go18
-rw-r--r--cmd/podman/pod_ps.go431
-rw-r--r--cmd/podman/pod_rm.go26
4 files changed, 429 insertions, 49 deletions
diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go
index f32ae4626..6cf2920a5 100644
--- a/cmd/podman/pod.go
+++ b/cmd/podman/pod.go
@@ -8,7 +8,8 @@ var (
podDescription = `
podman pod
- manage pods
+ 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",
diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go
index 9bbc60d4d..f86faa409 100644
--- a/cmd/podman/pod_create.go
+++ b/cmd/podman/pod_create.go
@@ -17,13 +17,9 @@ var podCreateDescription = "Creates a new empty pod. The pod ID is then" +
" 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",
+ Usage: "Set parent cgroup for the pod",
},
cli.StringSliceFlag{
Name: "label-file",
@@ -45,7 +41,7 @@ var podCreateFlags = []cli.Flag{
var podCreateCommand = cli.Command{
Name: "create",
- Usage: "create but do not start a pod",
+ Usage: "create a new empty pod",
Description: podCreateDescription,
Flags: podCreateFlags,
Action: podCreateCmd,
@@ -75,18 +71,11 @@ func podCreateCmd(c *cli.Context) error {
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")
@@ -99,6 +88,9 @@ func podCreateCmd(c *cli.Context) error {
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
diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go
index f4e2d0ae5..470810901 100644
--- a/cmd/podman/pod_ps.go
+++ b/cmd/podman/pod_ps.go
@@ -2,31 +2,62 @@ 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 (
- opts batchcontainer.PsOptions
+ 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
@@ -35,23 +66,93 @@ type podPsTemplateParams struct {
// 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"`
+ 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: "Display the extended information",
+ 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 = "Prints out information about pods"
+ podPsDescription = "List all pods on system including their names, ids and current state."
podPsCommand = cli.Command{
Name: "ps",
Aliases: []string{"ls", "list"},
@@ -68,7 +169,7 @@ func podPsCmd(c *cli.Context) error {
return err
}
- if err := podCheckFlagsPassed(c); err != nil {
+ if err := podPsCheckFlagsPassed(c); err != nil {
return errors.Wrapf(err, "error with flags passed")
}
@@ -76,50 +177,194 @@ func podPsCmd(c *cli.Context) error {
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"),
+ 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)
+ }
+ }
- pods, err := runtime.Pods(filterFuncs...)
- if err != nil {
- return err
+ 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(pods, opts, runtime)
+ return generatePodPsOutput(podsFiltered, opts, runtime)
}
-// podCheckFlagsPassed checks if mutually exclusive flags are passed together
-func podCheckFlagsPassed(c *cli.Context) error {
+// 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 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(quiet bool) string {
- if quiet {
- return formats.IDString
+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}}"
+ }
}
- format := "table {{.ID}}\t{{.Name}}"
return format
}
@@ -152,6 +397,24 @@ func (p *podPsTemplateParams) podHeaderMap() map[string]string {
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 (
@@ -160,13 +423,43 @@ func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podP
for _, psParam := range psParams {
podID := psParam.ID
+ var ctrStr string
+ truncated := ""
if !opts.NoTrunc {
- podID = shortID(psParam.ID)
+ 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{
- ID: podID,
- Name: psParam.Name,
+ 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)
@@ -175,6 +468,52 @@ func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podP
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 (
@@ -182,21 +521,55 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib
)
for _, pod := range pods {
- ctrs, err := runtime.ContainersInPod(pod)
+ 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 psOutput, nil
+ return sortPodPsOutput(opts.Sort, psOutput)
}
func generatePodPsOutput(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) error {
diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go
index db2b1ee51..8cc46761e 100644
--- a/cmd/podman/pod_rm.go
+++ b/cmd/podman/pod_rm.go
@@ -20,17 +20,18 @@ var (
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 running or attached containrs will not be removed.
- If --force, -f is specified, all containers will be stopped, then removed.`),
+ 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: "",
+ ArgsUsage: "[POD ...]",
UseShortOptionHandling: true,
}
)
@@ -42,6 +43,10 @@ func podRmCmd(c *cli.Context) error {
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")
@@ -50,22 +55,31 @@ func podRmCmd(c *cli.Context) error {
args := c.Args()
- if len(args) == 0 && !c.Bool("all") {
+ 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.Bool("all") || c.Bool("a") {
- delPods, err = runtime.Pods()
+ 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
}