summaryrefslogtreecommitdiff
path: root/cmd/podman/pod_ps.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/podman/pod_ps.go')
-rw-r--r--cmd/podman/pod_ps.go600
1 files changed, 600 insertions, 0 deletions
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()
+}