mirror of https://github.com/containers/podman.git
396 lines
11 KiB
Go
396 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"text/tabwriter"
|
|
"time"
|
|
|
|
tm "github.com/buger/goterm"
|
|
"github.com/containers/buildah/pkg/formats"
|
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
|
"github.com/containers/libpod/cmd/podman/shared"
|
|
"github.com/containers/libpod/pkg/adapter"
|
|
"github.com/docker/go-units"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
hid = "CONTAINER ID"
|
|
himage = "IMAGE"
|
|
hcommand = "COMMAND"
|
|
hcreated = "CREATED"
|
|
hstatus = "STATUS"
|
|
hports = "PORTS"
|
|
hnames = "NAMES"
|
|
hsize = "SIZE"
|
|
hinfra = "IS INFRA" //nolint
|
|
hpod = "POD"
|
|
nspid = "PID"
|
|
nscgroup = "CGROUPNS"
|
|
nsipc = "IPC"
|
|
nsmnt = "MNT"
|
|
nsnet = "NET"
|
|
nspidns = "PIDNS"
|
|
nsuserns = "USERNS"
|
|
nsuts = "UTS"
|
|
)
|
|
|
|
// Type declaration and functions for sorting the PS output
|
|
type psSorted []shared.PsContainerOutput
|
|
|
|
func (a psSorted) Len() int { return len(a) }
|
|
func (a psSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type psSortedCommand struct{ psSorted }
|
|
|
|
func (a psSortedCommand) Less(i, j int) bool {
|
|
return a.psSorted[i].Command < a.psSorted[j].Command
|
|
}
|
|
|
|
type psSortedCreated struct{ psSorted }
|
|
|
|
func (a psSortedCreated) Less(i, j int) bool {
|
|
return a.psSorted[i].CreatedAt.After(a.psSorted[j].CreatedAt)
|
|
}
|
|
|
|
type psSortedId struct{ psSorted }
|
|
|
|
func (a psSortedId) Less(i, j int) bool { return a.psSorted[i].ID < a.psSorted[j].ID }
|
|
|
|
type psSortedImage struct{ psSorted }
|
|
|
|
func (a psSortedImage) Less(i, j int) bool { return a.psSorted[i].Image < a.psSorted[j].Image }
|
|
|
|
type psSortedNames struct{ psSorted }
|
|
|
|
func (a psSortedNames) Less(i, j int) bool { return a.psSorted[i].Names < a.psSorted[j].Names }
|
|
|
|
type psSortedPod struct{ psSorted }
|
|
|
|
func (a psSortedPod) Less(i, j int) bool { return a.psSorted[i].Pod < a.psSorted[j].Pod }
|
|
|
|
type psSortedRunningFor struct{ psSorted }
|
|
|
|
func (a psSortedRunningFor) Less(i, j int) bool {
|
|
return a.psSorted[j].StartedAt.After(a.psSorted[i].StartedAt)
|
|
}
|
|
|
|
type psSortedStatus struct{ psSorted }
|
|
|
|
func (a psSortedStatus) Less(i, j int) bool { return a.psSorted[i].Status < a.psSorted[j].Status }
|
|
|
|
type psSortedSize struct{ psSorted }
|
|
|
|
func (a psSortedSize) Less(i, j int) bool {
|
|
if a.psSorted[i].Size == nil || a.psSorted[j].Size == nil {
|
|
return false
|
|
}
|
|
return a.psSorted[i].Size.RootFsSize < a.psSorted[j].Size.RootFsSize
|
|
}
|
|
|
|
var (
|
|
psCommand cliconfig.PsValues
|
|
psDescription = "Prints out information about the containers"
|
|
_psCommand = cobra.Command{
|
|
Use: "ps",
|
|
Args: noSubArgs,
|
|
Short: "List containers",
|
|
Long: psDescription,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
psCommand.InputArgs = args
|
|
psCommand.GlobalFlags = MainGlobalOpts
|
|
psCommand.Remote = remoteclient
|
|
return psCmd(&psCommand)
|
|
},
|
|
Example: `podman ps -a
|
|
podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}"
|
|
podman ps --size --sort names`,
|
|
}
|
|
)
|
|
|
|
func psInit(command *cliconfig.PsValues) {
|
|
command.SetHelpTemplate(HelpTemplate())
|
|
command.SetUsageTemplate(UsageTemplate())
|
|
flags := command.Flags()
|
|
flags.BoolVarP(&command.All, "all", "a", false, "Show all the containers, default is only running containers")
|
|
flags.StringSliceVarP(&command.Filter, "filter", "f", []string{}, "Filter output based on conditions given")
|
|
flags.StringVar(&command.Format, "format", "", "Pretty-print containers to JSON or using a Go template")
|
|
flags.IntVarP(&command.Last, "last", "n", -1, "Print the n last created containers (all states)")
|
|
flags.BoolVarP(&command.Latest, "latest", "l", false, "Show the latest container created (all states)")
|
|
flags.BoolVar(&command.Namespace, "namespace", false, "Display namespace information")
|
|
flags.BoolVar(&command.Namespace, "ns", false, "Display namespace information")
|
|
flags.BoolVar(&command.NoTrunct, "no-trunc", false, "Display the extended information")
|
|
flags.BoolVarP(&command.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with")
|
|
flags.BoolVarP(&command.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only")
|
|
flags.BoolVarP(&command.Size, "size", "s", false, "Display the total file sizes")
|
|
flags.StringVar(&command.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status")
|
|
flags.BoolVar(&command.Sync, "sync", false, "Sync container state with OCI runtime")
|
|
flags.UintVarP(&command.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds")
|
|
|
|
markFlagHiddenForRemoteClient("latest", flags)
|
|
}
|
|
|
|
func init() {
|
|
psCommand.Command = &_psCommand
|
|
psInit(&psCommand)
|
|
}
|
|
|
|
func psCmd(c *cliconfig.PsValues) error {
|
|
var (
|
|
watch bool
|
|
runtime *adapter.LocalRuntime
|
|
err error
|
|
)
|
|
|
|
if c.Watch > 0 {
|
|
watch = true
|
|
}
|
|
|
|
if c.Watch > 0 && c.Latest {
|
|
return errors.New("the watch and latest flags cannot be used together")
|
|
}
|
|
|
|
if err := checkFlagsPassed(c); err != nil {
|
|
return errors.Wrapf(err, "error with flags passed")
|
|
}
|
|
if !c.Size {
|
|
runtime, err = adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand)
|
|
} else {
|
|
runtime, err = adapter.GetRuntime(getContext(), &c.PodmanCommand)
|
|
}
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error creating libpod runtime")
|
|
}
|
|
|
|
defer runtime.DeferredShutdown(false)
|
|
|
|
if !watch {
|
|
if err := psDisplay(c, runtime); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
for {
|
|
tm.Clear()
|
|
tm.MoveCursor(1, 1)
|
|
tm.Flush()
|
|
if err := psDisplay(c, runtime); err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(time.Duration(c.Watch) * time.Second)
|
|
tm.Clear()
|
|
tm.MoveCursor(1, 1)
|
|
tm.Flush()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func printQuiet(containers []shared.PsContainerOutput) error {
|
|
for _, c := range containers {
|
|
fmt.Println(c.ID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkFlagsPassed checks if mutually exclusive flags are passed together
|
|
func checkFlagsPassed(c *cliconfig.PsValues) error {
|
|
// latest, and last are mutually exclusive.
|
|
if c.Last >= 0 && c.Latest {
|
|
return errors.Errorf("last and latest are mutually exclusive")
|
|
}
|
|
// Quiet conflicts with size, namespace, and format with a Go template
|
|
if c.Quiet {
|
|
if c.Size || c.Namespace || (c.Flag("format").Changed &&
|
|
c.Format != formats.JSONString) {
|
|
return errors.Errorf("quiet conflicts with size, namespace, and format with go template")
|
|
}
|
|
}
|
|
// Size and namespace conflict with each other
|
|
if c.Size && c.Namespace {
|
|
return errors.Errorf("size and namespace options conflict")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func sortPsOutput(sortBy string, psOutput psSorted) (psSorted, error) {
|
|
switch sortBy {
|
|
case "id":
|
|
sort.Sort(psSortedId{psOutput})
|
|
case "image":
|
|
sort.Sort(psSortedImage{psOutput})
|
|
case "command":
|
|
sort.Sort(psSortedCommand{psOutput})
|
|
case "runningfor":
|
|
sort.Sort(psSortedRunningFor{psOutput})
|
|
case "status":
|
|
sort.Sort(psSortedStatus{psOutput})
|
|
case "size":
|
|
sort.Sort(psSortedSize{psOutput})
|
|
case "names":
|
|
sort.Sort(psSortedNames{psOutput})
|
|
case "created":
|
|
sort.Sort(psSortedCreated{psOutput})
|
|
case "pod":
|
|
sort.Sort(psSortedPod{psOutput})
|
|
default:
|
|
return nil, errors.Errorf("invalid option for --sort, options are: command, created, id, image, names, runningfor, size, or status")
|
|
}
|
|
return psOutput, nil
|
|
}
|
|
|
|
func printFormat(format string, containers []shared.PsContainerOutput) error {
|
|
// return immediately if no containers are present
|
|
if len(containers) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Use a tabwriter to align column format
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
|
|
|
// Make a map of the field names for the headers
|
|
headerNames := make(map[string]string)
|
|
v := reflect.ValueOf(containers[0])
|
|
t := v.Type()
|
|
for i := 0; i < t.NumField(); i++ {
|
|
headerNames[t.Field(i).Name] = t.Field(i).Name
|
|
}
|
|
|
|
// Spit out the header if "table" is present in the format
|
|
if strings.HasPrefix(format, "table") {
|
|
hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1)
|
|
format = hformat
|
|
headerTmpl, err := template.New("header").Parse(hformat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := headerTmpl.Execute(w, headerNames); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintln(w, "")
|
|
}
|
|
|
|
// Spit out the data rows now
|
|
dataTmpl, err := template.New("data").Parse(format)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, container := range containers {
|
|
if err := dataTmpl.Execute(w, container); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintln(w, "")
|
|
}
|
|
// Flush the writer
|
|
return w.Flush()
|
|
}
|
|
|
|
func dumpJSON(containers []shared.PsContainerOutput) error {
|
|
b, err := json.MarshalIndent(containers, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
os.Stdout.Write(b)
|
|
return nil
|
|
}
|
|
|
|
func psDisplay(c *cliconfig.PsValues, runtime *adapter.LocalRuntime) error {
|
|
var (
|
|
err error
|
|
)
|
|
opts := shared.PsOptions{
|
|
All: c.All,
|
|
Format: c.Format,
|
|
Last: c.Last,
|
|
Latest: c.Latest,
|
|
NoTrunc: c.NoTrunct,
|
|
Pod: c.Pod,
|
|
Quiet: c.Quiet,
|
|
Size: c.Size,
|
|
Namespace: c.Namespace,
|
|
Sort: c.Sort,
|
|
Sync: c.Sync,
|
|
}
|
|
|
|
pss, err := runtime.Ps(c, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Here and down
|
|
if opts.Sort != "" {
|
|
pss, err = sortPsOutput(opts.Sort, pss)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If quiet, print only cids and return
|
|
if opts.Quiet {
|
|
return printQuiet(pss)
|
|
}
|
|
|
|
// If the user wants their own GO template format
|
|
if opts.Format != "" {
|
|
if opts.Format == "json" {
|
|
return dumpJSON(pss)
|
|
}
|
|
return printFormat(opts.Format, pss)
|
|
}
|
|
|
|
// Define a tab writer with stdout as the output
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
|
|
// Output standard PS headers
|
|
if !opts.Namespace {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s", hid, himage, hcommand, hcreated, hstatus, hports, hnames)
|
|
// User wants pod info
|
|
if opts.Pod {
|
|
fmt.Fprintf(w, "\t%s", hpod)
|
|
}
|
|
//User wants size info
|
|
if opts.Size {
|
|
fmt.Fprintf(w, "\t%s", hsize)
|
|
}
|
|
} else {
|
|
// Output Namespace headers
|
|
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)
|
|
}
|
|
|
|
// Now iterate each container and output its information
|
|
for _, container := range pss {
|
|
|
|
// Standard PS output
|
|
if !opts.Namespace {
|
|
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)
|
|
// User wants pod info
|
|
if opts.Pod {
|
|
fmt.Fprintf(w, "\t%s", container.Pod)
|
|
}
|
|
//User wants size info
|
|
if opts.Size {
|
|
var size string
|
|
if container.Size == nil {
|
|
size = units.HumanSizeWithPrecision(0, 0)
|
|
} else {
|
|
size = units.HumanSizeWithPrecision(float64(container.Size.RwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(container.Size.RootFsSize), 3) + ")"
|
|
}
|
|
fmt.Fprintf(w, "\t%s", size)
|
|
}
|
|
|
|
} else {
|
|
// Print namespace information
|
|
ns := runtime.GetNamespaces(container)
|
|
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)
|
|
}
|
|
|
|
}
|
|
fmt.Fprint(w, "\n")
|
|
return w.Flush()
|
|
}
|