github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/cmd/podman/pod_ps.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/containers/buildah/pkg/formats"
    11  	"github.com/containers/libpod/cmd/podman/cliconfig"
    12  	"github.com/containers/libpod/cmd/podman/shared"
    13  	"github.com/containers/libpod/libpod/define"
    14  	"github.com/containers/libpod/pkg/adapter"
    15  	"github.com/docker/go-units"
    16  	"github.com/pkg/errors"
    17  	"github.com/spf13/cobra"
    18  )
    19  
    20  const (
    21  	STOPPED      = "Stopped" //nolint
    22  	RUNNING      = "Running"
    23  	PAUSED       = "Paused"
    24  	EXITED       = "Exited"
    25  	ERROR        = "Error"
    26  	CREATED      = "Created"
    27  	NUM_CTR_INFO = 10
    28  )
    29  
    30  var (
    31  	bc_opts shared.PsOptions
    32  )
    33  
    34  type podPsCtrInfo struct {
    35  	Name   string `json:"name,omitempty"`
    36  	Id     string `json:"id,omitempty"`
    37  	Status string `json:"status,omitempty"`
    38  }
    39  
    40  type podPsOptions struct {
    41  	NoTrunc            bool
    42  	Format             string
    43  	Sort               string
    44  	Quiet              bool
    45  	NumberOfContainers bool
    46  	Cgroup             bool
    47  	NamesOfContainers  bool
    48  	IdsOfContainers    bool
    49  	StatusOfContainers bool
    50  }
    51  
    52  type podPsTemplateParams struct {
    53  	Created            string
    54  	ID                 string
    55  	Name               string
    56  	NumberOfContainers int
    57  	Status             string
    58  	Cgroup             string
    59  	ContainerInfo      string
    60  	InfraID            string
    61  	Namespaces         string
    62  }
    63  
    64  // podPsJSONParams is used as a base structure for the psParams
    65  // If template output is requested, podPsJSONParams will be converted to
    66  // podPsTemplateParams.
    67  // podPsJSONParams will be populated by data from libpod.Container,
    68  // the members of the struct are the sama data types as their sources.
    69  type podPsJSONParams struct {
    70  	CreatedAt          time.Time      `json:"createdAt"`
    71  	ID                 string         `json:"id"`
    72  	Name               string         `json:"name"`
    73  	NumberOfContainers int            `json:"numberOfContainers"`
    74  	Status             string         `json:"status"`
    75  	CtrsInfo           []podPsCtrInfo `json:"containerInfo,omitempty"`
    76  	Cgroup             string         `json:"cgroup,omitempty"`
    77  	InfraID            string         `json:"infraContainerId,omitempty"`
    78  	Namespaces         []string       `json:"namespaces,omitempty"`
    79  }
    80  
    81  // Type declaration and functions for sorting the pod PS output
    82  type podPsSorted []podPsJSONParams
    83  
    84  func (a podPsSorted) Len() int      { return len(a) }
    85  func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    86  
    87  type podPsSortedCreated struct{ podPsSorted }
    88  
    89  func (a podPsSortedCreated) Less(i, j int) bool {
    90  	return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt)
    91  }
    92  
    93  type podPsSortedId struct{ podPsSorted }
    94  
    95  func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID }
    96  
    97  type podPsSortedNumber struct{ podPsSorted }
    98  
    99  func (a podPsSortedNumber) Less(i, j int) bool {
   100  	return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo)
   101  }
   102  
   103  type podPsSortedName struct{ podPsSorted }
   104  
   105  func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name }
   106  
   107  type podPsSortedStatus struct{ podPsSorted }
   108  
   109  func (a podPsSortedStatus) Less(i, j int) bool {
   110  	return a.podPsSorted[i].Status < a.podPsSorted[j].Status
   111  }
   112  
   113  var (
   114  	podPsCommand cliconfig.PodPsValues
   115  
   116  	podPsDescription = "List all pods on system including their names, ids and current state."
   117  	_podPsCommand    = &cobra.Command{
   118  		Use:     "ps",
   119  		Aliases: []string{"ls", "list"},
   120  		Args:    noSubArgs,
   121  		Short:   "List pods",
   122  		Long:    podPsDescription,
   123  		RunE: func(cmd *cobra.Command, args []string) error {
   124  			podPsCommand.InputArgs = args
   125  			podPsCommand.GlobalFlags = MainGlobalOpts
   126  			podPsCommand.Remote = remoteclient
   127  			return podPsCmd(&podPsCommand)
   128  		},
   129  	}
   130  )
   131  
   132  func init() {
   133  	podPsCommand.Command = _podPsCommand
   134  	podPsCommand.SetHelpTemplate(HelpTemplate())
   135  	podPsCommand.SetUsageTemplate(UsageTemplate())
   136  	flags := podPsCommand.Flags()
   137  	flags.BoolVar(&podPsCommand.CtrNames, "ctr-names", false, "Display the container names")
   138  	flags.BoolVar(&podPsCommand.CtrIDs, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated")
   139  	flags.BoolVar(&podPsCommand.CtrStatus, "ctr-status", false, "Display the container status")
   140  	flags.StringVarP(&podPsCommand.Filter, "filter", "f", "", "Filter output based on conditions given")
   141  	flags.StringVar(&podPsCommand.Format, "format", "", "Pretty-print pods to JSON or using a Go template")
   142  	flags.BoolVarP(&podPsCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of")
   143  	flags.BoolVar(&podPsCommand.Namespace, "namespace", false, "Display namespace information of the pod")
   144  	flags.BoolVar(&podPsCommand.Namespace, "ns", false, "Display namespace information of the pod")
   145  	flags.BoolVar(&podPsCommand.NoTrunc, "no-trunc", false, "Do not truncate pod and container IDs")
   146  	flags.BoolVarP(&podPsCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only")
   147  	flags.StringVar(&podPsCommand.Sort, "sort", "created", "Sort output by created, id, name, or number")
   148  	markFlagHiddenForRemoteClient("latest", flags)
   149  }
   150  
   151  func podPsCmd(c *cliconfig.PodPsValues) error {
   152  	if err := podPsCheckFlagsPassed(c); err != nil {
   153  		return errors.Wrapf(err, "error with flags passed")
   154  	}
   155  
   156  	runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand)
   157  	if err != nil {
   158  		return errors.Wrapf(err, "error creating libpod runtime")
   159  	}
   160  	defer runtime.DeferredShutdown(false)
   161  
   162  	opts := podPsOptions{
   163  		NoTrunc:            c.NoTrunc,
   164  		Quiet:              c.Quiet,
   165  		Sort:               c.Sort,
   166  		IdsOfContainers:    c.CtrIDs,
   167  		NamesOfContainers:  c.CtrNames,
   168  		StatusOfContainers: c.CtrStatus,
   169  	}
   170  
   171  	opts.Format = genPodPsFormat(c)
   172  
   173  	var pods []*adapter.Pod
   174  
   175  	// If latest is set true filters are ignored.
   176  	if c.Latest {
   177  		pod, err := runtime.GetLatestPod()
   178  		if err != nil {
   179  			return err
   180  		}
   181  		pods = append(pods, pod)
   182  		return generatePodPsOutput(pods, opts)
   183  	}
   184  
   185  	if c.Filter != "" {
   186  		pods, err = runtime.GetPodsWithFilters(c.Filter)
   187  		if err != nil {
   188  			return err
   189  		}
   190  	} else {
   191  		pods, err = runtime.GetAllPods()
   192  		if err != nil {
   193  			return err
   194  		}
   195  	}
   196  
   197  	return generatePodPsOutput(pods, opts)
   198  }
   199  
   200  // podPsCheckFlagsPassed checks if mutually exclusive flags are passed together
   201  func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error {
   202  	// quiet, and format with Go template are mutually exclusive
   203  	flags := 0
   204  	if c.Quiet {
   205  		flags++
   206  	}
   207  	if c.Flag("format").Changed && c.Format != formats.JSONString {
   208  		flags++
   209  	}
   210  	if flags > 1 {
   211  		return errors.Errorf("quiet and format with Go template are mutually exclusive")
   212  	}
   213  	return nil
   214  }
   215  
   216  // generate the template based on conditions given
   217  func genPodPsFormat(c *cliconfig.PodPsValues) string {
   218  	format := ""
   219  	switch {
   220  	case c.Format != "":
   221  		// "\t" from the command line is not being recognized as a tab
   222  		// replacing the string "\t" to a tab character if the user passes in "\t"
   223  		format = strings.Replace(c.Format, `\t`, "\t", -1)
   224  	case c.Quiet:
   225  		format = formats.IDString
   226  	default:
   227  		format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}"
   228  		if c.Bool("namespace") {
   229  			format += "\t{{.Cgroup}}\t{{.Namespaces}}"
   230  		}
   231  		if c.CtrNames || c.CtrIDs || c.CtrStatus {
   232  			format += "\t{{.ContainerInfo}}"
   233  		} else {
   234  			format += "\t{{.NumberOfContainers}}"
   235  		}
   236  		format += "\t{{.InfraID}}"
   237  	}
   238  	return format
   239  }
   240  
   241  func podPsToGeneric(templParams []podPsTemplateParams, jsonParams []podPsJSONParams) (genericParams []interface{}) {
   242  	if len(templParams) > 0 {
   243  		for _, v := range templParams {
   244  			genericParams = append(genericParams, interface{}(v))
   245  		}
   246  		return
   247  	}
   248  	for _, v := range jsonParams {
   249  		genericParams = append(genericParams, interface{}(v))
   250  	}
   251  	return
   252  }
   253  
   254  // generate the accurate header based on template given
   255  func (p *podPsTemplateParams) podHeaderMap() map[string]string {
   256  	v := reflect.Indirect(reflect.ValueOf(p))
   257  	values := make(map[string]string)
   258  
   259  	for i := 0; i < v.NumField(); i++ {
   260  		key := v.Type().Field(i).Name
   261  		value := key
   262  		if value == "ID" {
   263  			value = "Pod" + value
   264  		}
   265  		if value == "NumberOfContainers" {
   266  			value = "#OfContainers"
   267  		}
   268  		values[key] = strings.ToUpper(splitCamelCase(value))
   269  	}
   270  	return values
   271  }
   272  
   273  func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) {
   274  	switch sortBy {
   275  	case "created":
   276  		sort.Sort(podPsSortedCreated{psOutput})
   277  	case "id":
   278  		sort.Sort(podPsSortedId{psOutput})
   279  	case "name":
   280  		sort.Sort(podPsSortedName{psOutput})
   281  	case "number":
   282  		sort.Sort(podPsSortedNumber{psOutput})
   283  	case "status":
   284  		sort.Sort(podPsSortedStatus{psOutput})
   285  	default:
   286  		return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number")
   287  	}
   288  	return psOutput, nil
   289  }
   290  
   291  // getPodTemplateOutput returns the modified container information
   292  func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) {
   293  	var (
   294  		psOutput []podPsTemplateParams
   295  	)
   296  
   297  	for _, psParam := range psParams {
   298  		podID := psParam.ID
   299  		infraID := psParam.InfraID
   300  		var ctrStr string
   301  
   302  		truncated := ""
   303  		if !opts.NoTrunc {
   304  			podID = shortID(podID)
   305  			if len(psParam.CtrsInfo) > NUM_CTR_INFO {
   306  				psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO]
   307  				truncated = "..."
   308  			}
   309  			infraID = shortID(infraID)
   310  		}
   311  		for _, ctrInfo := range psParam.CtrsInfo {
   312  			infoSlice := make([]string, 0)
   313  			if opts.IdsOfContainers {
   314  				if opts.NoTrunc {
   315  					infoSlice = append(infoSlice, ctrInfo.Id)
   316  				} else {
   317  					infoSlice = append(infoSlice, shortID(ctrInfo.Id))
   318  				}
   319  			}
   320  			if opts.NamesOfContainers {
   321  				infoSlice = append(infoSlice, ctrInfo.Name)
   322  			}
   323  			if opts.StatusOfContainers {
   324  				infoSlice = append(infoSlice, ctrInfo.Status)
   325  			}
   326  			if len(infoSlice) != 0 {
   327  				ctrStr += fmt.Sprintf("[%s] ", strings.Join(infoSlice, ","))
   328  			}
   329  		}
   330  		ctrStr += truncated
   331  		params := podPsTemplateParams{
   332  			Created:            units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago",
   333  			ID:                 podID,
   334  			Name:               psParam.Name,
   335  			Status:             psParam.Status,
   336  			NumberOfContainers: psParam.NumberOfContainers,
   337  			Cgroup:             psParam.Cgroup,
   338  			ContainerInfo:      ctrStr,
   339  			InfraID:            infraID,
   340  			Namespaces:         strings.Join(psParam.Namespaces, ","),
   341  		}
   342  
   343  		psOutput = append(psOutput, params)
   344  	}
   345  
   346  	return psOutput, nil
   347  }
   348  
   349  func getNamespaces(pod *adapter.Pod) []string {
   350  	var shared []string
   351  	if pod.SharesPID() {
   352  		shared = append(shared, "pid")
   353  	}
   354  	if pod.SharesNet() {
   355  		shared = append(shared, "net")
   356  	}
   357  	if pod.SharesMount() {
   358  		shared = append(shared, "mnt")
   359  	}
   360  	if pod.SharesIPC() {
   361  		shared = append(shared, "ipc")
   362  	}
   363  	if pod.SharesUser() {
   364  		shared = append(shared, "user")
   365  	}
   366  	if pod.SharesCgroup() {
   367  		shared = append(shared, "cgroup")
   368  	}
   369  	if pod.SharesUTS() {
   370  		shared = append(shared, "uts")
   371  	}
   372  	return shared
   373  }
   374  
   375  // getAndSortPodJSONOutput returns the container info in its raw, sorted form
   376  func getAndSortPodJSONParams(pods []*adapter.Pod, opts podPsOptions) ([]podPsJSONParams, error) {
   377  	var (
   378  		psOutput []podPsJSONParams
   379  	)
   380  
   381  	for _, pod := range pods {
   382  		ctrs, err := pod.AllContainers()
   383  		ctrsInfo := make([]podPsCtrInfo, 0)
   384  		if err != nil {
   385  			return nil, err
   386  		}
   387  		ctrNum := len(ctrs)
   388  		status, err := pod.GetPodStatus()
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  
   393  		infraID, err := pod.InfraContainerID()
   394  		if err != nil {
   395  			return nil, err
   396  		}
   397  		for _, ctr := range ctrs {
   398  			batchInfo, err := adapter.BatchContainerOp(ctr, bc_opts)
   399  			if err != nil {
   400  				return nil, err
   401  			}
   402  			var status string
   403  			switch batchInfo.ConState {
   404  			case define.ContainerStateExited:
   405  				fallthrough
   406  			case define.ContainerStateStopped:
   407  				status = EXITED
   408  			case define.ContainerStateRunning:
   409  				status = RUNNING
   410  			case define.ContainerStatePaused:
   411  				status = PAUSED
   412  			case define.ContainerStateCreated, define.ContainerStateConfigured:
   413  				status = CREATED
   414  			default:
   415  				status = ERROR
   416  			}
   417  			ctrsInfo = append(ctrsInfo, podPsCtrInfo{
   418  				Name:   batchInfo.ConConfig.Name,
   419  				Id:     ctr.ID(),
   420  				Status: status,
   421  			})
   422  		}
   423  		params := podPsJSONParams{
   424  			CreatedAt:          pod.CreatedTime(),
   425  			ID:                 pod.ID(),
   426  			Name:               pod.Name(),
   427  			Status:             status,
   428  			Cgroup:             pod.CgroupParent(),
   429  			NumberOfContainers: ctrNum,
   430  			CtrsInfo:           ctrsInfo,
   431  			Namespaces:         getNamespaces(pod),
   432  			InfraID:            infraID,
   433  		}
   434  
   435  		psOutput = append(psOutput, params)
   436  	}
   437  	return sortPodPsOutput(opts.Sort, psOutput)
   438  }
   439  
   440  func generatePodPsOutput(pods []*adapter.Pod, opts podPsOptions) error {
   441  	if len(pods) == 0 && opts.Format != formats.JSONString {
   442  		return nil
   443  	}
   444  	psOutput, err := getAndSortPodJSONParams(pods, opts)
   445  	if err != nil {
   446  		return err
   447  	}
   448  	var out formats.Writer
   449  
   450  	switch opts.Format {
   451  	case formats.JSONString:
   452  		out = formats.JSONStructArray{Output: podPsToGeneric([]podPsTemplateParams{}, psOutput)}
   453  	default:
   454  		psOutput, err := getPodTemplateOutput(psOutput, opts)
   455  		if err != nil {
   456  			return errors.Wrapf(err, "unable to create output")
   457  		}
   458  		out = formats.StdoutTemplateArray{Output: podPsToGeneric(psOutput, []podPsJSONParams{}), Template: opts.Format, Fields: psOutput[0].podHeaderMap()}
   459  	}
   460  
   461  	return out.Out()
   462  }