diff options
author | Brent Baude <bbaude@redhat.com> | 2020-04-14 09:02:28 -0500 |
---|---|---|
committer | Brent Baude <bbaude@redhat.com> | 2020-04-14 09:40:43 -0500 |
commit | 830f3a4462953b3698a21031db3964d0f0ae63b3 (patch) | |
tree | 26730d69cf6a96990b933bf3af2de3f2e891c4a4 /cmd/podmanV2 | |
parent | 004826653f2a8b064af8c1f055b1a402f4060d1c (diff) | |
download | podman-830f3a4462953b3698a21031db3964d0f0ae63b3.tar.gz podman-830f3a4462953b3698a21031db3964d0f0ae63b3.tar.bz2 podman-830f3a4462953b3698a21031db3964d0f0ae63b3.zip |
v2podman ps revert structure changes
reverting name changes to the listcontainer structure because it negatively impacted the direct consumption of the restful API. instead we now use a local structure in the CLI to modify the output as needed.
Signed-off-by: Brent Baude <bbaude@redhat.com>
Diffstat (limited to 'cmd/podmanV2')
-rw-r--r-- | cmd/podmanV2/containers/ps.go | 205 |
1 files changed, 189 insertions, 16 deletions
diff --git a/cmd/podmanV2/containers/ps.go b/cmd/podmanV2/containers/ps.go index 8c1d44842..8ebbf6ebf 100644 --- a/cmd/podmanV2/containers/ps.go +++ b/cmd/podmanV2/containers/ps.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "os" + "sort" + "strconv" "strings" "text/tabwriter" "text/template" @@ -13,6 +15,8 @@ import ( "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podmanV2/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -134,6 +138,7 @@ func getResponses() ([]entities.ListContainer, error) { } func ps(cmd *cobra.Command, args []string) error { + var responses []psReporter for _, f := range filters { split := strings.SplitN(f, "=", 2) if len(split) == 1 { @@ -141,22 +146,27 @@ func ps(cmd *cobra.Command, args []string) error { } listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1]) } - responses, err := getResponses() + listContainers, err := getResponses() if err != nil { return err } if len(listOpts.Sort) > 0 { - responses, err = entities.SortPsOutput(listOpts.Sort, responses) + listContainers, err = entities.SortPsOutput(listOpts.Sort, listContainers) if err != nil { return err } } if listOpts.Format == "json" { - return jsonOut(responses) + return jsonOut(listContainers) } if listOpts.Quiet { - return quietOut(responses) + return quietOut(listContainers) + } + + for _, r := range listContainers { + responses = append(responses, psReporter{r}) } + headers, row := createPsOut() if cmd.Flag("format").Changed { row = listOpts.Format @@ -175,10 +185,14 @@ func ps(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) if listOpts.Watch > 0 { for { + var responses []psReporter tm.Clear() tm.MoveCursor(1, 1) tm.Flush() - responses, err := getResponses() + listContainers, err := getResponses() + for _, r := range listContainers { + responses = append(responses, psReporter{r}) + } if err != nil { return err } @@ -210,21 +224,12 @@ func createPsOut() (string, string) { return headers, row } headers := defaultHeaders - if noTrunc { - row += "{{.ID}}" - } else { - row += "{{slice .ID 0 12}}" - } + row += "{{.ID}}" 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}}" + row += "\t{{.Pod}}\t{{.PodName}}" } if listOpts.Size { @@ -239,3 +244,171 @@ func createPsOut() (string, string) { } return headers, row } + +type psReporter struct { + entities.ListContainer +} + +// ID returns the ID of the container +func (l psReporter) ID() string { + if !noTrunc { + return l.ListContainer.ID[0:12] + } + return l.ListContainer.ID +} + +// Pod returns the ID of the pod the container +// belongs to and appropriately truncates the ID +func (l psReporter) Pod() string { + if !noTrunc && len(l.ListContainer.Pod) > 0 { + return l.ListContainer.Pod[0:12] + } + return l.ListContainer.Pod +} + +// State returns the container state in human duration +func (l psReporter) State() string { + var state string + switch l.ListContainer.State { + case "running": + t := units.HumanDuration(time.Since(time.Unix(l.StartedAt, 0))) + state = "Up " + t + " ago" + case "configured": + state = "Created" + case "exited", "stopped": + t := units.HumanDuration(time.Since(time.Unix(l.ExitedAt, 0))) + state = fmt.Sprintf("Exited (%d) %s ago", l.ExitCode, t) + default: + state = l.ListContainer.State + } + return state +} + +// Command returns the container command in string format +func (l psReporter) Command() string { + return strings.Join(l.ListContainer.Command, " ") +} + +// Size returns the rootfs and virtual sizes in human duration in +// and output form (string) suitable for ps +func (l psReporter) Size() string { + virt := units.HumanSizeWithPrecision(float64(l.ListContainer.Size.RootFsSize), 3) + s := units.HumanSizeWithPrecision(float64(l.ListContainer.Size.RwSize), 3) + return fmt.Sprintf("%s (virtual %s)", s, virt) +} + +// Names returns the container name in string format +func (l psReporter) Names() string { + return l.ListContainer.Names[0] +} + +// Ports converts from Portmappings to the string form +// required by ps +func (l psReporter) Ports() string { + if len(l.ListContainer.Ports) < 1 { + return "" + } + return portsToString(l.ListContainer.Ports) +} + +// CreatedAt returns the container creation time in string format. podman +// and docker both return a timestamped value for createdat +func (l psReporter) CreatedAt() string { + return time.Unix(l.Created, 0).String() +} + +// CreateHuman allows us to output the created time in human readable format +func (l psReporter) CreatedHuman() string { + return units.HumanDuration(time.Since(time.Unix(l.Created, 0))) + " ago" +} + +// portsToString converts the ports used to a string of the from "port1, port2" +// and also groups a continuous list of ports into a readable format. +func portsToString(ports []ocicni.PortMapping) string { + type portGroup struct { + first int32 + last int32 + } + var portDisplay []string + if len(ports) == 0 { + return "" + } + //Sort the ports, so grouping continuous ports become easy. + sort.Slice(ports, func(i, j int) bool { + return comparePorts(ports[i], ports[j]) + }) + + // portGroupMap is used for grouping continuous ports. + portGroupMap := make(map[string]*portGroup) + var groupKeyList []string + + for _, v := range ports { + + hostIP := v.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + // If hostPort and containerPort are not same, consider as individual port. + if v.ContainerPort != v.HostPort { + portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + continue + } + + portMapKey := fmt.Sprintf("%s/%s", hostIP, v.Protocol) + + portgroup, ok := portGroupMap[portMapKey] + if !ok { + portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} + // This list is required to traverse portGroupMap. + groupKeyList = append(groupKeyList, portMapKey) + continue + } + + if portgroup.last == (v.ContainerPort - 1) { + portgroup.last = v.ContainerPort + continue + } + } + // For each portMapKey, format group list and appned to output string. + for _, portKey := range groupKeyList { + group := portGroupMap[portKey] + portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) + } + return strings.Join(portDisplay, ", ") +} + +func comparePorts(i, j ocicni.PortMapping) bool { + if i.ContainerPort != j.ContainerPort { + return i.ContainerPort < j.ContainerPort + } + + if i.HostIP != j.HostIP { + return i.HostIP < j.HostIP + } + + if i.HostPort != j.HostPort { + return i.HostPort < j.HostPort + } + + return i.Protocol < j.Protocol +} + +// formatGroup returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> +// e.g 0.0.0.0:1000-1006->1000-1006/tcp. +func formatGroup(key string, start, last int32) string { + parts := strings.Split(key, "/") + groupType := parts[0] + var ip string + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + group := strconv.Itoa(int(start)) + if start != last { + group = fmt.Sprintf("%s-%d", group, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) +} |