mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-11-05 02:24:44 +00:00
In commit d81021ed26 I introduced shell completion for the `--format`
flag. This is a very nice way to complete go template field names.
However it did not work correct for anonymous fields. In this case the
child fields can be accessed directly from the parent.
For example:
```
type Anonymous struct {
Field1 string
Field2 string
...
}
type MyType struct {
Anonymous
}
var s = MyType{}
```
Now if you want to access a field from the Anonymous struct you can just
do `s.Field1`. The same is allowed for go templates, using `{{.Field1}}`
should work. This commit adds this functionality, if the field is anonymous
read the child field names recursively and add them to the suggestions.
Signed-off-by: Paul Holzinger <pholzing@redhat.com>
320 lines
8.7 KiB
Go
320 lines
8.7 KiB
Go
package pods
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/common/pkg/completion"
|
|
"github.com/containers/common/pkg/report"
|
|
"github.com/containers/podman/v3/cmd/podman/common"
|
|
"github.com/containers/podman/v3/cmd/podman/registry"
|
|
"github.com/containers/podman/v3/cmd/podman/validate"
|
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
|
"github.com/docker/go-units"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
psDescription = "List all pods on system including their names, ids and current state."
|
|
|
|
// Command: podman pod _ps_
|
|
psCmd = &cobra.Command{
|
|
Use: "ps [options]",
|
|
Aliases: []string{"ls", "list"},
|
|
Short: "List pods",
|
|
Long: psDescription,
|
|
RunE: pods,
|
|
Args: validate.NoArgs,
|
|
ValidArgsFunction: completion.AutocompleteNone,
|
|
}
|
|
)
|
|
|
|
var (
|
|
inputFilters []string
|
|
noTrunc bool
|
|
psInput entities.PodPSOptions
|
|
)
|
|
|
|
func init() {
|
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
|
Command: psCmd,
|
|
Parent: podCmd,
|
|
})
|
|
flags := psCmd.Flags()
|
|
flags.BoolVar(&psInput.CtrNames, "ctr-names", false, "Display the container names")
|
|
flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated")
|
|
flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status")
|
|
// TODO should we make this a [] ?
|
|
|
|
filterFlagName := "filter"
|
|
flags.StringSliceVarP(&inputFilters, filterFlagName, "f", []string{}, "Filter output based on conditions given")
|
|
_ = psCmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePodPsFilters)
|
|
|
|
formatFlagName := "format"
|
|
flags.StringVar(&psInput.Format, formatFlagName, "", "Pretty-print pods to JSON or using a Go template")
|
|
_ = psCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(ListPodReporter{ListPodsReport: &entities.ListPodsReport{}}))
|
|
|
|
flags.Bool("noheading", false, "Do not print headers")
|
|
flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod")
|
|
flags.BoolVar(&psInput.Namespace, "ns", false, "Display namespace information of the pod")
|
|
flags.BoolVar(&noTrunc, "no-trunc", false, "Do not truncate pod and container IDs")
|
|
flags.BoolVarP(&psInput.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only")
|
|
|
|
sortFlagName := "sort"
|
|
flags.StringVar(&psInput.Sort, sortFlagName, "created", "Sort output by created, id, name, or number")
|
|
_ = psCmd.RegisterFlagCompletionFunc(sortFlagName, common.AutocompletePodPsSort)
|
|
|
|
validate.AddLatestFlag(psCmd, &psInput.Latest)
|
|
}
|
|
|
|
func pods(cmd *cobra.Command, _ []string) error {
|
|
if psInput.Quiet && len(psInput.Format) > 0 {
|
|
return errors.New("quiet and format cannot be used together")
|
|
}
|
|
if cmd.Flag("filter").Changed {
|
|
psInput.Filters = make(map[string][]string)
|
|
for _, f := range inputFilters {
|
|
split := strings.SplitN(f, "=", 2)
|
|
if len(split) < 2 {
|
|
return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
|
|
}
|
|
psInput.Filters[split[0]] = append(psInput.Filters[split[0]], split[1])
|
|
}
|
|
}
|
|
responses, err := registry.ContainerEngine().PodPs(context.Background(), psInput)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := sortPodPsOutput(psInput.Sort, responses); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case report.IsJSON(psInput.Format):
|
|
b, err := json.MarshalIndent(responses, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(b))
|
|
return nil
|
|
case psInput.Quiet:
|
|
for _, p := range responses {
|
|
fmt.Println(p.Id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Formatted output below
|
|
lpr := make([]ListPodReporter, 0, len(responses))
|
|
for _, r := range responses {
|
|
lpr = append(lpr, ListPodReporter{r})
|
|
}
|
|
|
|
headers := report.Headers(ListPodReporter{}, map[string]string{
|
|
"Id": "POD ID",
|
|
"Name": "NAME",
|
|
"Status": "STATUS",
|
|
"Labels": "LABELS",
|
|
"NumberOfContainers": "# OF CONTAINERS",
|
|
"Created": "CREATED",
|
|
"InfraID": "INFRA ID",
|
|
"ContainerIds": "IDS",
|
|
"ContainerNames": "NAMES",
|
|
"ContainerStatuses": "STATUS",
|
|
"Cgroup": "CGROUP",
|
|
"Namespace": "NAMESPACES",
|
|
})
|
|
renderHeaders := true
|
|
row := podPsFormat()
|
|
if cmd.Flags().Changed("format") {
|
|
renderHeaders = report.HasTable(psInput.Format)
|
|
row = report.NormalizeFormat(psInput.Format)
|
|
}
|
|
noHeading, _ := cmd.Flags().GetBool("noheading")
|
|
if noHeading {
|
|
renderHeaders = false
|
|
}
|
|
format := report.EnforceRange(row)
|
|
|
|
tmpl, err := report.NewTemplate("list").Parse(format)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w, err := report.NewWriterDefault(os.Stdout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer w.Flush()
|
|
|
|
if renderHeaders {
|
|
if err := tmpl.Execute(w, headers); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return tmpl.Execute(w, lpr)
|
|
}
|
|
|
|
func podPsFormat() string {
|
|
row := []string{"{{.Id}}", "{{.Name}}", "{{.Status}}", "{{.Created}}", "{{.InfraID}}"}
|
|
|
|
if psInput.CtrIds {
|
|
row = append(row, "{{.ContainerIds}}")
|
|
}
|
|
|
|
if psInput.CtrNames {
|
|
row = append(row, "{{.ContainerNames}}")
|
|
}
|
|
|
|
if psInput.CtrStatus {
|
|
row = append(row, "{{.ContainerStatuses}}")
|
|
}
|
|
|
|
if psInput.Namespace {
|
|
row = append(row, "{{.Cgroup}}", "{{.Namespace}}")
|
|
}
|
|
|
|
if !psInput.CtrStatus && !psInput.CtrNames && !psInput.CtrIds {
|
|
row = append(row, "{{.NumberOfContainers}}")
|
|
}
|
|
return strings.Join(row, "\t") + "\n"
|
|
}
|
|
|
|
// ListPodReporter is a struct for pod ps output
|
|
type ListPodReporter struct {
|
|
*entities.ListPodsReport
|
|
}
|
|
|
|
// Created returns a human readable created time/date
|
|
func (l ListPodReporter) Created() string {
|
|
return units.HumanDuration(time.Since(l.ListPodsReport.Created)) + " ago"
|
|
}
|
|
|
|
// Labels returns a map of the pod's labels
|
|
func (l ListPodReporter) Labels() map[string]string {
|
|
return l.ListPodsReport.Labels
|
|
}
|
|
|
|
// Networks returns the infra container network names in string format
|
|
func (l ListPodReporter) Networks() string {
|
|
return strings.Join(l.ListPodsReport.Networks, ",")
|
|
}
|
|
|
|
// NumberOfContainers returns an int representation for
|
|
// the number of containers belonging to the pod
|
|
func (l ListPodReporter) NumberOfContainers() int {
|
|
return len(l.Containers)
|
|
}
|
|
|
|
// ID is a wrapper to Id for compat, typos
|
|
func (l ListPodReporter) ID() string {
|
|
return l.Id()
|
|
}
|
|
|
|
// Id returns the Pod id
|
|
func (l ListPodReporter) Id() string { // nolint
|
|
if noTrunc {
|
|
return l.ListPodsReport.Id
|
|
}
|
|
return l.ListPodsReport.Id[0:12]
|
|
}
|
|
|
|
// Added for backwards compatibility with podmanv1
|
|
func (l ListPodReporter) InfraID() string {
|
|
return l.InfraId()
|
|
}
|
|
|
|
// InfraId returns the infra container id for the pod
|
|
// depending on trunc
|
|
func (l ListPodReporter) InfraId() string { // nolint
|
|
if len(l.ListPodsReport.InfraId) == 0 {
|
|
return ""
|
|
}
|
|
if noTrunc {
|
|
return l.ListPodsReport.InfraId
|
|
}
|
|
return l.ListPodsReport.InfraId[0:12]
|
|
}
|
|
|
|
func (l ListPodReporter) ContainerIds() string {
|
|
ctrids := make([]string, 0, len(l.Containers))
|
|
for _, c := range l.Containers {
|
|
id := c.Id
|
|
if !noTrunc {
|
|
id = id[0:12]
|
|
}
|
|
ctrids = append(ctrids, id)
|
|
}
|
|
return strings.Join(ctrids, ",")
|
|
}
|
|
|
|
func (l ListPodReporter) ContainerNames() string {
|
|
ctrNames := make([]string, 0, len(l.Containers))
|
|
for _, c := range l.Containers {
|
|
ctrNames = append(ctrNames, c.Names)
|
|
}
|
|
return strings.Join(ctrNames, ",")
|
|
}
|
|
|
|
func (l ListPodReporter) ContainerStatuses() string {
|
|
statuses := make([]string, 0, len(l.Containers))
|
|
for _, c := range l.Containers {
|
|
statuses = append(statuses, c.Status)
|
|
}
|
|
return strings.Join(statuses, ",")
|
|
}
|
|
|
|
func sortPodPsOutput(sortBy string, lprs []*entities.ListPodsReport) error {
|
|
switch sortBy {
|
|
case "created":
|
|
sort.Sort(podPsSortedCreated{lprs})
|
|
case "id":
|
|
sort.Sort(podPsSortedID{lprs})
|
|
case "name":
|
|
sort.Sort(podPsSortedName{lprs})
|
|
case "number":
|
|
sort.Sort(podPsSortedNumber{lprs})
|
|
case "status":
|
|
sort.Sort(podPsSortedStatus{lprs})
|
|
default:
|
|
return errors.Errorf("invalid option for --sort, options are: id, names, or number")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type lprSort []*entities.ListPodsReport
|
|
|
|
func (a lprSort) Len() int { return len(a) }
|
|
func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
type podPsSortedCreated struct{ lprSort }
|
|
|
|
func (a podPsSortedCreated) Less(i, j int) bool {
|
|
return a.lprSort[i].Created.After(a.lprSort[j].Created)
|
|
}
|
|
|
|
type podPsSortedID struct{ lprSort }
|
|
|
|
func (a podPsSortedID) Less(i, j int) bool { return a.lprSort[i].Id < a.lprSort[j].Id }
|
|
|
|
type podPsSortedNumber struct{ lprSort }
|
|
|
|
func (a podPsSortedNumber) Less(i, j int) bool {
|
|
return len(a.lprSort[i].Containers) < len(a.lprSort[j].Containers)
|
|
}
|
|
|
|
type podPsSortedName struct{ lprSort }
|
|
|
|
func (a podPsSortedName) Less(i, j int) bool { return a.lprSort[i].Name < a.lprSort[j].Name }
|
|
|
|
type podPsSortedStatus struct{ lprSort }
|
|
|
|
func (a podPsSortedStatus) Less(i, j int) bool {
|
|
return a.lprSort[i].Status < a.lprSort[j].Status
|
|
}
|