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

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"html/template"
     6  	"os"
     7  	"reflect"
     8  	"sort"
     9  	"strings"
    10  	"text/tabwriter"
    11  	"time"
    12  
    13  	tm "github.com/buger/goterm"
    14  	"github.com/containers/buildah/pkg/formats"
    15  	"github.com/containers/libpod/cmd/podman/cliconfig"
    16  	"github.com/containers/libpod/cmd/podman/shared"
    17  	"github.com/containers/libpod/pkg/adapter"
    18  	"github.com/docker/go-units"
    19  	"github.com/pkg/errors"
    20  	"github.com/spf13/cobra"
    21  )
    22  
    23  const (
    24  	hid      = "CONTAINER ID"
    25  	himage   = "IMAGE"
    26  	hcommand = "COMMAND"
    27  	hcreated = "CREATED"
    28  	hstatus  = "STATUS"
    29  	hports   = "PORTS"
    30  	hnames   = "NAMES"
    31  	hsize    = "SIZE"
    32  	hinfra   = "IS INFRA" //nolint
    33  	hpod     = "POD"
    34  	hpodname = "POD NAME"
    35  	nspid    = "PID"
    36  	nscgroup = "CGROUPNS"
    37  	nsipc    = "IPC"
    38  	nsmnt    = "MNT"
    39  	nsnet    = "NET"
    40  	nspidns  = "PIDNS"
    41  	nsuserns = "USERNS"
    42  	nsuts    = "UTS"
    43  )
    44  
    45  // Type declaration and functions for sorting the PS output
    46  type psSorted []shared.PsContainerOutput
    47  
    48  func (a psSorted) Len() int      { return len(a) }
    49  func (a psSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
    50  
    51  type psSortedCommand struct{ psSorted }
    52  
    53  func (a psSortedCommand) Less(i, j int) bool {
    54  	return a.psSorted[i].Command < a.psSorted[j].Command
    55  }
    56  
    57  type psSortedCreated struct{ psSorted }
    58  
    59  func (a psSortedCreated) Less(i, j int) bool {
    60  	return a.psSorted[i].CreatedAt.After(a.psSorted[j].CreatedAt)
    61  }
    62  
    63  type psSortedId struct{ psSorted }
    64  
    65  func (a psSortedId) Less(i, j int) bool { return a.psSorted[i].ID < a.psSorted[j].ID }
    66  
    67  type psSortedImage struct{ psSorted }
    68  
    69  func (a psSortedImage) Less(i, j int) bool { return a.psSorted[i].Image < a.psSorted[j].Image }
    70  
    71  type psSortedNames struct{ psSorted }
    72  
    73  func (a psSortedNames) Less(i, j int) bool { return a.psSorted[i].Names < a.psSorted[j].Names }
    74  
    75  type psSortedPod struct{ psSorted }
    76  
    77  func (a psSortedPod) Less(i, j int) bool { return a.psSorted[i].Pod < a.psSorted[j].Pod }
    78  
    79  type psSortedRunningFor struct{ psSorted }
    80  
    81  func (a psSortedRunningFor) Less(i, j int) bool {
    82  	return a.psSorted[j].StartedAt.After(a.psSorted[i].StartedAt)
    83  }
    84  
    85  type psSortedStatus struct{ psSorted }
    86  
    87  func (a psSortedStatus) Less(i, j int) bool { return a.psSorted[i].Status < a.psSorted[j].Status }
    88  
    89  type psSortedSize struct{ psSorted }
    90  
    91  func (a psSortedSize) Less(i, j int) bool {
    92  	if a.psSorted[i].Size == nil || a.psSorted[j].Size == nil {
    93  		return false
    94  	}
    95  	return a.psSorted[i].Size.RootFsSize < a.psSorted[j].Size.RootFsSize
    96  }
    97  
    98  var (
    99  	psCommand     cliconfig.PsValues
   100  	psDescription = "Prints out information about the containers"
   101  	_psCommand    = cobra.Command{
   102  		Use:   "ps",
   103  		Args:  noSubArgs,
   104  		Short: "List containers",
   105  		Long:  psDescription,
   106  		RunE: func(cmd *cobra.Command, args []string) error {
   107  			psCommand.InputArgs = args
   108  			psCommand.GlobalFlags = MainGlobalOpts
   109  			psCommand.Remote = remoteclient
   110  			return psCmd(&psCommand)
   111  		},
   112  		Example: `podman ps -a
   113    podman ps -a --format "{{.ID}}  {{.Image}}  {{.Labels}}  {{.Mounts}}"
   114    podman ps --size --sort names`,
   115  	}
   116  )
   117  
   118  func psInit(command *cliconfig.PsValues) {
   119  	command.SetHelpTemplate(HelpTemplate())
   120  	command.SetUsageTemplate(UsageTemplate())
   121  	flags := command.Flags()
   122  	flags.BoolVarP(&command.All, "all", "a", false, "Show all the containers, default is only running containers")
   123  	flags.StringSliceVarP(&command.Filter, "filter", "f", []string{}, "Filter output based on conditions given")
   124  	flags.StringVar(&command.Format, "format", "", "Pretty-print containers to JSON or using a Go template")
   125  	flags.IntVarP(&command.Last, "last", "n", -1, "Print the n last created containers (all states)")
   126  	flags.BoolVarP(&command.Latest, "latest", "l", false, "Show the latest container created (all states)")
   127  	flags.BoolVar(&command.Namespace, "namespace", false, "Display namespace information")
   128  	flags.BoolVar(&command.Namespace, "ns", false, "Display namespace information")
   129  	flags.BoolVar(&command.NoTrunct, "no-trunc", false, "Display the extended information")
   130  	flags.BoolVarP(&command.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with")
   131  	flags.BoolVarP(&command.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only")
   132  	flags.BoolVarP(&command.Size, "size", "s", false, "Display the total file sizes")
   133  	flags.StringVar(&command.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status")
   134  	flags.BoolVar(&command.Sync, "sync", false, "Sync container state with OCI runtime")
   135  	flags.UintVarP(&command.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds")
   136  
   137  	markFlagHiddenForRemoteClient("latest", flags)
   138  }
   139  
   140  func init() {
   141  	psCommand.Command = &_psCommand
   142  	psInit(&psCommand)
   143  }
   144  
   145  func psCmd(c *cliconfig.PsValues) error {
   146  	var (
   147  		watch   bool
   148  		runtime *adapter.LocalRuntime
   149  		err     error
   150  	)
   151  
   152  	if c.Watch > 0 {
   153  		watch = true
   154  	}
   155  
   156  	if c.Watch > 0 && c.Latest {
   157  		return errors.New("the watch and latest flags cannot be used together")
   158  	}
   159  
   160  	if err := checkFlagsPassed(c); err != nil {
   161  		return errors.Wrapf(err, "error with flags passed")
   162  	}
   163  	if !c.Size {
   164  		runtime, err = adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand)
   165  	} else {
   166  		runtime, err = adapter.GetRuntime(getContext(), &c.PodmanCommand)
   167  	}
   168  	if err != nil {
   169  		return errors.Wrapf(err, "error creating libpod runtime")
   170  	}
   171  
   172  	defer runtime.DeferredShutdown(false)
   173  
   174  	if !watch {
   175  		if err := psDisplay(c, runtime); err != nil {
   176  			return err
   177  		}
   178  	} else {
   179  		for {
   180  			tm.Clear()
   181  			tm.MoveCursor(1, 1)
   182  			tm.Flush()
   183  			if err := psDisplay(c, runtime); err != nil {
   184  				return err
   185  			}
   186  			time.Sleep(time.Duration(c.Watch) * time.Second)
   187  			tm.Clear()
   188  			tm.MoveCursor(1, 1)
   189  			tm.Flush()
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  func printQuiet(containers []shared.PsContainerOutput) error {
   196  	for _, c := range containers {
   197  		fmt.Println(c.ID)
   198  	}
   199  	return nil
   200  }
   201  
   202  // checkFlagsPassed checks if mutually exclusive flags are passed together
   203  func checkFlagsPassed(c *cliconfig.PsValues) error {
   204  	// latest, and last are mutually exclusive.
   205  	if c.Last >= 0 && c.Latest {
   206  		return errors.Errorf("last and latest are mutually exclusive")
   207  	}
   208  	// Filter on status forces all
   209  	if len(c.Filter) > 0 {
   210  		for _, filter := range c.Filter {
   211  			splitFilter := strings.SplitN(filter, "=", 2)
   212  			if strings.ToLower(splitFilter[0]) == "status" {
   213  				c.All = true
   214  				break
   215  			}
   216  		}
   217  	}
   218  	// Quiet conflicts with size and namespace and is overridden by a Go
   219  	// template.
   220  	if c.Quiet {
   221  		if c.Size || c.Namespace {
   222  			return errors.Errorf("quiet conflicts with size and namespace")
   223  		}
   224  		if c.Flag("format").Changed && c.Format != formats.JSONString {
   225  			// Quiet is overridden by Go template output.
   226  			c.Quiet = false
   227  		}
   228  	}
   229  	// Size and namespace conflict with each other
   230  	if c.Size && c.Namespace {
   231  		return errors.Errorf("size and namespace options conflict")
   232  	}
   233  	return nil
   234  }
   235  
   236  func sortPsOutput(sortBy string, psOutput psSorted) (psSorted, error) {
   237  	switch sortBy {
   238  	case "id":
   239  		sort.Sort(psSortedId{psOutput})
   240  	case "image":
   241  		sort.Sort(psSortedImage{psOutput})
   242  	case "command":
   243  		sort.Sort(psSortedCommand{psOutput})
   244  	case "runningfor":
   245  		sort.Sort(psSortedRunningFor{psOutput})
   246  	case "status":
   247  		sort.Sort(psSortedStatus{psOutput})
   248  	case "size":
   249  		sort.Sort(psSortedSize{psOutput})
   250  	case "names":
   251  		sort.Sort(psSortedNames{psOutput})
   252  	case "created":
   253  		sort.Sort(psSortedCreated{psOutput})
   254  	case "pod":
   255  		sort.Sort(psSortedPod{psOutput})
   256  	default:
   257  		return nil, errors.Errorf("invalid option for --sort, options are: command, created, id, image, names, runningfor, size, or status")
   258  	}
   259  	return psOutput, nil
   260  }
   261  
   262  func printFormat(format string, containers []shared.PsContainerOutput) error {
   263  	// return immediately if no containers are present
   264  	if len(containers) == 0 {
   265  		return nil
   266  	}
   267  
   268  	// Use a tabwriter to align column format
   269  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
   270  
   271  	// Make a map of the field names for the headers
   272  	headerNames := make(map[string]string)
   273  	v := reflect.ValueOf(containers[0])
   274  	t := v.Type()
   275  	for i := 0; i < t.NumField(); i++ {
   276  		headerNames[t.Field(i).Name] = t.Field(i).Name
   277  	}
   278  
   279  	// Spit out the header if "table" is present in the format
   280  	if strings.HasPrefix(format, "table") {
   281  		hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1)
   282  		format = hformat
   283  		headerTmpl, err := template.New("header").Parse(hformat)
   284  		if err != nil {
   285  			return err
   286  		}
   287  		if err := headerTmpl.Execute(w, headerNames); err != nil {
   288  			return err
   289  		}
   290  		fmt.Fprintln(w, "")
   291  	}
   292  
   293  	// Spit out the data rows now
   294  	dataTmpl, err := template.New("data").Parse(format)
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	for _, container := range containers {
   300  		if err := dataTmpl.Execute(w, container); err != nil {
   301  			return err
   302  		}
   303  		fmt.Fprintln(w, "")
   304  	}
   305  	// Flush the writer
   306  	return w.Flush()
   307  }
   308  
   309  func dumpJSON(containers []shared.PsContainerOutput) error {
   310  	b, err := json.MarshalIndent(containers, "", "     ")
   311  	if err != nil {
   312  		return err
   313  	}
   314  	os.Stdout.Write(b)
   315  	return nil
   316  }
   317  
   318  func psDisplay(c *cliconfig.PsValues, runtime *adapter.LocalRuntime) error {
   319  	var (
   320  		err error
   321  	)
   322  	opts := shared.PsOptions{
   323  		All:       c.All,
   324  		Format:    c.Format,
   325  		Last:      c.Last,
   326  		Latest:    c.Latest,
   327  		NoTrunc:   c.NoTrunct,
   328  		Pod:       c.Pod,
   329  		Quiet:     c.Quiet,
   330  		Size:      c.Size,
   331  		Namespace: c.Namespace,
   332  		Sort:      c.Sort,
   333  		Sync:      c.Sync,
   334  	}
   335  
   336  	pss, err := runtime.Ps(c, opts)
   337  	if err != nil {
   338  		return err
   339  	}
   340  	// Here and down
   341  	if opts.Sort != "" {
   342  		pss, err = sortPsOutput(opts.Sort, pss)
   343  		if err != nil {
   344  			return err
   345  		}
   346  	}
   347  
   348  	// If quiet, print only cids and return
   349  	if opts.Quiet {
   350  		return printQuiet(pss)
   351  	}
   352  
   353  	// If the user wants their own GO template format
   354  	if opts.Format != "" {
   355  		if opts.Format == "json" {
   356  			return dumpJSON(pss)
   357  		}
   358  		return printFormat(opts.Format, pss)
   359  	}
   360  
   361  	// Define a tab writer with stdout as the output
   362  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   363  
   364  	// Output standard PS headers
   365  	if !opts.Namespace {
   366  		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s", hid, himage, hcommand, hcreated, hstatus, hports, hnames)
   367  		// User wants pod info
   368  		if opts.Pod {
   369  			fmt.Fprintf(w, "\t%s\t%s", hpod, hpodname)
   370  		}
   371  		//User wants size info
   372  		if opts.Size {
   373  			fmt.Fprintf(w, "\t%s", hsize)
   374  		}
   375  	} else {
   376  		// Output Namespace headers
   377  		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s", hid, hnames, nspid, nscgroup, nsipc, nsmnt, nsnet, nspidns, nsuserns, nsuts)
   378  	}
   379  
   380  	// Now iterate each container and output its information
   381  	for _, container := range pss {
   382  
   383  		// Standard PS output
   384  		if !opts.Namespace {
   385  			fmt.Fprintf(w, "\n%s\t%s\t%s\t%s\t%s\t%s\t%s", container.ID, container.Image, container.Command, container.Created, container.Status, container.Ports, container.Names)
   386  			// User wants pod info
   387  			if opts.Pod {
   388  				fmt.Fprintf(w, "\t%s\t%s", container.Pod, container.PodName)
   389  			}
   390  			//User wants size info
   391  			if opts.Size {
   392  				var size string
   393  				if container.Size == nil {
   394  					size = units.HumanSizeWithPrecision(0, 0)
   395  				} else {
   396  					size = units.HumanSizeWithPrecision(float64(container.Size.RwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(container.Size.RootFsSize), 3) + ")"
   397  				}
   398  				fmt.Fprintf(w, "\t%s", size)
   399  			}
   400  
   401  		} else {
   402  			// Print namespace information
   403  			ns := runtime.GetNamespaces(container)
   404  			fmt.Fprintf(w, "\n%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s", container.ID, container.Names, container.Pid, ns.Cgroup, ns.IPC, ns.MNT, ns.NET, ns.PIDNS, ns.User, ns.UTS)
   405  		}
   406  
   407  	}
   408  	fmt.Fprint(w, "\n")
   409  	return w.Flush()
   410  }