Files
Brent Baude 8ce23775a6 v2podman ps alter formats
in order to get the go templating to work for custom input, we now use structure methods instead of template map funcs.  this requires some manipulation of fields so that the funcs can have the proper names.

Signed-off-by: Brent Baude <bbaude@redhat.com>
2020-04-08 09:11:26 -05:00

242 lines
6.8 KiB
Go

package containers
import (
"encoding/json"
"fmt"
"os"
"strings"
"text/tabwriter"
"text/template"
"time"
tm "github.com/buger/goterm"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
psDescription = "Prints out information about the containers"
psCommand = &cobra.Command{
Use: "ps",
Args: checkFlags,
Short: "List containers",
Long: psDescription,
RunE: ps,
PreRunE: preRunE,
Example: `podman ps -a
podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}"
podman ps --size --sort names`,
}
)
var (
listOpts = entities.ContainerListOptions{
Filters: make(map[string][]string),
}
filters []string
noTrunc bool
defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES"
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: psCommand,
})
flags := psCommand.Flags()
flags.BoolVarP(&listOpts.All, "all", "a", false, "Show all the containers, default is only running containers")
flags.StringSliceVarP(&filters, "filter", "f", []string{}, "Filter output based on conditions given")
flags.StringVar(&listOpts.Format, "format", "", "Pretty-print containers to JSON or using a Go template")
flags.IntVarP(&listOpts.Last, "last", "n", -1, "Print the n last created containers (all states)")
flags.BoolVarP(&listOpts.Latest, "latest", "l", false, "Show the latest container created (all states)")
flags.BoolVar(&listOpts.Namespace, "namespace", false, "Display namespace information")
flags.BoolVar(&listOpts.Namespace, "ns", false, "Display namespace information")
flags.BoolVar(&noTrunc, "no-trunc", false, "Display the extended information")
flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with")
flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only")
flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes")
flags.StringVar(&listOpts.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status")
flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime")
flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
}
}
func checkFlags(c *cobra.Command, args []string) error {
// latest, and last are mutually exclusive.
if listOpts.Last >= 0 && listOpts.Latest {
return errors.Errorf("last and latest are mutually exclusive")
}
// Filter on status forces all
for _, filter := range filters {
splitFilter := strings.SplitN(filter, "=", 2)
if strings.ToLower(splitFilter[0]) == "status" {
listOpts.All = true
break
}
}
// Quiet conflicts with size and namespace and is overridden by a Go
// template.
if listOpts.Quiet {
if listOpts.Size || listOpts.Namespace {
return errors.Errorf("quiet conflicts with size and namespace")
}
if c.Flag("format").Changed && listOpts.Format != formats.JSONString {
// Quiet is overridden by Go template output.
listOpts.Quiet = false
}
}
// Size and namespace conflict with each other
if listOpts.Size && listOpts.Namespace {
return errors.Errorf("size and namespace options conflict")
}
if listOpts.Watch > 0 && listOpts.Latest {
return errors.New("the watch and latest flags cannot be used together")
}
return nil
}
func jsonOut(responses []entities.ListContainer) error {
b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err
}
fmt.Println(string(b))
return nil
}
func quietOut(responses []entities.ListContainer) error {
for _, r := range responses {
id := r.ID
if !noTrunc {
id = id[0:12]
}
fmt.Println(id)
}
return nil
}
func getResponses() ([]entities.ListContainer, error) {
responses, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts)
if err != nil {
return nil, err
}
if len(listOpts.Sort) > 0 {
responses, err = entities.SortPsOutput(listOpts.Sort, responses)
if err != nil {
return nil, err
}
}
return responses, nil
}
func ps(cmd *cobra.Command, args []string) error {
for _, f := range filters {
split := strings.SplitN(f, "=", 2)
if len(split) == 1 {
return errors.Errorf("invalid filter %q", f)
}
listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1])
}
responses, err := getResponses()
if err != nil {
return err
}
if len(listOpts.Sort) > 0 {
responses, err = entities.SortPsOutput(listOpts.Sort, responses)
if err != nil {
return err
}
}
if listOpts.Format == "json" {
return jsonOut(responses)
}
if listOpts.Quiet {
return quietOut(responses)
}
headers, row := createPsOut()
if cmd.Flag("format").Changed {
row = listOpts.Format
if !strings.HasPrefix(row, "\n") {
row += "\n"
}
}
format := "{{range . }}" + row + "{{end}}"
if !listOpts.Quiet && !cmd.Flag("format").Changed {
format = headers + format
}
tmpl, err := template.New("listContainers").Parse(format)
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
if listOpts.Watch > 0 {
for {
tm.Clear()
tm.MoveCursor(1, 1)
tm.Flush()
responses, err := getResponses()
if err != nil {
return err
}
if err := tmpl.Execute(w, responses); err != nil {
return nil
}
if err := w.Flush(); err != nil {
return err
}
time.Sleep(time.Duration(listOpts.Watch) * time.Second)
tm.Clear()
tm.MoveCursor(1, 1)
tm.Flush()
}
} else if listOpts.Watch < 1 {
if err := tmpl.Execute(w, responses); err != nil {
return err
}
return w.Flush()
}
return nil
}
func createPsOut() (string, string) {
var row string
if listOpts.Namespace {
headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDN\tUSERNS\tUTS\n"
row := "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n"
return headers, row
}
headers := defaultHeaders
if noTrunc {
row += "{{.ID}}"
} else {
row += "{{slice .ID 0 12}}"
}
row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.State}}\t{{.Ports}}\t{{.Names}}"
if listOpts.Pod {
headers += "\tPOD ID\tPODNAME"
if noTrunc {
row += "\t{{.Pod}}"
} else {
row += "\t{{slice .Pod 0 12}}"
}
row += "\t{{.PodName}}"
}
if listOpts.Size {
headers += "\tSIZE"
row += "\t{{.Size}}"
}
if !strings.HasSuffix(headers, "\n") {
headers += "\n"
}
if !strings.HasSuffix(row, "\n") {
row += "\n"
}
return headers, row
}