From 830f3a4462953b3698a21031db3964d0f0ae63b3 Mon Sep 17 00:00:00 2001
From: Brent Baude <bbaude@redhat.com>
Date: Tue, 14 Apr 2020 09:02:28 -0500
Subject: [PATCH] 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>
---
 cmd/podmanV2/containers/ps.go         | 205 ++++++++++++++++++++++++--
 pkg/bindings/test/containers_test.go  |   2 +-
 pkg/domain/entities/container_ps.go   | 171 ++-------------------
 pkg/domain/infra/tunnel/containers.go |   4 +-
 pkg/domain/infra/tunnel/helpers.go    |   2 +-
 pkg/ps/ps.go                          |  34 ++---
 test/apiv2/20-containers.at           |   4 +-
 7 files changed, 222 insertions(+), 200 deletions(-)

diff --git a/cmd/podmanV2/containers/ps.go b/cmd/podmanV2/containers/ps.go
index 8c1d44842c..8ebbf6ebf5 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)
+}
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index c6501ac9e4..0b1b9ecdd7 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -509,7 +509,7 @@ var _ = Describe("Podman containers ", func() {
 		Expect(err).To(BeNil())
 		containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil, nil)
 		Expect(err).To(BeNil())
-		err = containers.Kill(bt.conn, containerLatestList[0].Names(), "SIGTERM")
+		err = containers.Kill(bt.conn, containerLatestList[0].Names[0], "SIGTERM")
 		Expect(err).To(BeNil())
 	})
 
diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go
index f07b0364f6..ceafecebc4 100644
--- a/pkg/domain/entities/container_ps.go
+++ b/pkg/domain/entities/container_ps.go
@@ -1,23 +1,19 @@
 package entities
 
 import (
-	"fmt"
 	"sort"
-	"strconv"
 	"strings"
-	"time"
 
 	"github.com/containers/libpod/cmd/podman/shared"
 	"github.com/containers/libpod/libpod"
 	"github.com/cri-o/ocicni/pkg/ocicni"
-	"github.com/docker/go-units"
 	"github.com/pkg/errors"
 )
 
 // Listcontainer describes a container suitable for listing
 type ListContainer struct {
 	// Container command
-	Cmd []string
+	Command []string
 	// Container creation time
 	Created int64
 	// If container has exited/stopped
@@ -37,7 +33,7 @@ type ListContainer struct {
 	// User volume mounts
 	Mounts []string
 	// The names assigned to the container
-	ContainerNames []string
+	Names []string
 	// Namespaces the container belongs to.  Requires the
 	// namespace boolean to be true
 	Namespaces ListContainerNamespaces
@@ -50,69 +46,13 @@ type ListContainer struct {
 	// boolean to be set
 	PodName string
 	// Port mappings
-	PortMappings []ocicni.PortMapping
+	Ports []ocicni.PortMapping
 	// Size of the container rootfs.  Requires the size boolean to be true
-	ContainerSize *shared.ContainerSize
+	Size *shared.ContainerSize
 	// Time when container started
 	StartedAt int64
 	// State of container
-	ContainerState string
-}
-
-// State returns the container state in human duration
-func (l ListContainer) State() string {
-	var state string
-	switch l.ContainerState {
-	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.ContainerState
-	}
-	return state
-}
-
-// Command returns the container command in string format
-func (l ListContainer) Command() string {
-	return strings.Join(l.Cmd, " ")
-}
-
-// Size returns the rootfs and virtual sizes in human duration in
-// and output form (string) suitable for ps
-func (l ListContainer) Size() string {
-	virt := units.HumanSizeWithPrecision(float64(l.ContainerSize.RootFsSize), 3)
-	s := units.HumanSizeWithPrecision(float64(l.ContainerSize.RwSize), 3)
-	return fmt.Sprintf("%s (virtual %s)", s, virt)
-}
-
-// Names returns the container name in string format
-func (l ListContainer) Names() string {
-	return l.ContainerNames[0]
-}
-
-// Ports converts from Portmappings to the string form
-// required by ps
-func (l ListContainer) Ports() string {
-	if len(l.PortMappings) < 1 {
-		return ""
-	}
-	return portsToString(l.PortMappings)
-}
-
-// CreatedAt returns the container creation time in string format.  podman
-// and docker both return a timestamped value for createdat
-func (l ListContainer) CreatedAt() string {
-	return time.Unix(l.Created, 0).String()
-}
-
-// CreateHuman allows us to output the created time in human readable format
-func (l ListContainer) CreatedHuman() string {
-	return units.HumanDuration(time.Since(time.Unix(l.Created, 0))) + " ago"
+	State string
 }
 
 // ListContainer Namespaces contains the identifiers of the container's Linux namespaces
@@ -153,7 +93,7 @@ func (a SortListContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
 type psSortedCommand struct{ SortListContainers }
 
 func (a psSortedCommand) Less(i, j int) bool {
-	return strings.Join(a.SortListContainers[i].Cmd, " ") < strings.Join(a.SortListContainers[j].Cmd, " ")
+	return strings.Join(a.SortListContainers[i].Command, " ") < strings.Join(a.SortListContainers[j].Command, " ")
 }
 
 type psSortedId struct{ SortListContainers }
@@ -171,7 +111,7 @@ func (a psSortedImage) Less(i, j int) bool {
 type psSortedNames struct{ SortListContainers }
 
 func (a psSortedNames) Less(i, j int) bool {
-	return a.SortListContainers[i].ContainerNames[0] < a.SortListContainers[j].ContainerNames[0]
+	return a.SortListContainers[i].Names[0] < a.SortListContainers[j].Names[0]
 }
 
 type psSortedPod struct{ SortListContainers }
@@ -189,16 +129,16 @@ func (a psSortedRunningFor) Less(i, j int) bool {
 type psSortedStatus struct{ SortListContainers }
 
 func (a psSortedStatus) Less(i, j int) bool {
-	return a.SortListContainers[i].ContainerState < a.SortListContainers[j].ContainerState
+	return a.SortListContainers[i].State < a.SortListContainers[j].State
 }
 
 type psSortedSize struct{ SortListContainers }
 
 func (a psSortedSize) Less(i, j int) bool {
-	if a.SortListContainers[i].ContainerSize == nil || a.SortListContainers[j].ContainerSize == nil {
+	if a.SortListContainers[i].Size == nil || a.SortListContainers[j].Size == nil {
 		return false
 	}
-	return a.SortListContainers[i].ContainerSize.RootFsSize < a.SortListContainers[j].ContainerSize.RootFsSize
+	return a.SortListContainers[i].Size.RootFsSize < a.SortListContainers[j].Size.RootFsSize
 }
 
 type PsSortedCreateTime struct{ SortListContainers }
@@ -232,94 +172,3 @@ func SortPsOutput(sortBy string, psOutput SortListContainers) (SortListContainer
 	}
 	return psOutput, nil
 }
-
-// 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)
-}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 1a430e3d0a..ea9aa835bc 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -242,7 +242,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
 		}
 		// narrow the list to running only
 		for _, c := range allCtrs {
-			if c.ContainerState == define.ContainerStateRunning.String() {
+			if c.State == define.ContainerStateRunning.String() {
 				ctrs = append(ctrs, c)
 			}
 		}
@@ -276,7 +276,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
 		}
 		// narrow the list to exited only
 		for _, c := range allCtrs {
-			if c.ContainerState == define.ContainerStateExited.String() {
+			if c.State == define.ContainerStateExited.String() {
 				ctrs = append(ctrs, c)
 			}
 		}
diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go
index 4d7e458972..682d60d6a3 100644
--- a/pkg/domain/infra/tunnel/helpers.go
+++ b/pkg/domain/infra/tunnel/helpers.go
@@ -30,7 +30,7 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam
 	for _, id := range namesOrIds {
 		var found bool
 		for _, con := range c {
-			if id == con.ID || strings.HasPrefix(con.ID, id) || util.StringInSlice(id, con.ContainerNames) {
+			if id == con.ID || strings.HasPrefix(con.ID, id) || util.StringInSlice(id, con.Names) {
 				cons = append(cons, con)
 				found = true
 				break
diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go
index 9217fa595b..58fcc2c216 100644
--- a/pkg/ps/ps.go
+++ b/pkg/ps/ps.go
@@ -148,23 +148,23 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
 	}
 
 	ps := entities.ListContainer{
-		Cmd:            conConfig.Command,
-		Created:        conConfig.CreatedTime.Unix(),
-		Exited:         exited,
-		ExitCode:       exitCode,
-		ExitedAt:       exitedTime.Unix(),
-		ID:             conConfig.ID,
-		Image:          conConfig.RootfsImageName,
-		IsInfra:        conConfig.IsInfra,
-		Labels:         conConfig.Labels,
-		Mounts:         ctr.UserVolumes(),
-		ContainerNames: []string{conConfig.Name},
-		Pid:            pid,
-		Pod:            conConfig.Pod,
-		PortMappings:   conConfig.PortMappings,
-		ContainerSize:  size,
-		StartedAt:      startedTime.Unix(),
-		ContainerState: conState.String(),
+		Command:   conConfig.Command,
+		Created:   conConfig.CreatedTime.Unix(),
+		Exited:    exited,
+		ExitCode:  exitCode,
+		ExitedAt:  exitedTime.Unix(),
+		ID:        conConfig.ID,
+		Image:     conConfig.RootfsImageName,
+		IsInfra:   conConfig.IsInfra,
+		Labels:    conConfig.Labels,
+		Mounts:    ctr.UserVolumes(),
+		Names:     []string{conConfig.Name},
+		Pid:       pid,
+		Pod:       conConfig.Pod,
+		Ports:     conConfig.PortMappings,
+		Size:      size,
+		StartedAt: startedTime.Unix(),
+		State:     conState.String(),
 	}
 	if opts.Pod && len(conConfig.Pod) > 0 {
 		pod, err := rt.GetPod(conConfig.Pod)
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index 04e2fa64ce..7fb39b2213 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -21,8 +21,8 @@ t GET libpod/containers/json?all=true 200 \
   length=1 \
   .[0].Id~[0-9a-f]\\{12\\} \
   .[0].Image=$IMAGE \
-  .[0].Cmd[0]="true" \
-  .[0].ContainerState~\\\(exited\\\|stopped\\\) \
+  .[0].Command[0]="true" \
+  .[0].State~\\\(exited\\\|stopped\\\) \
   .[0].ExitCode=0 \
   .[0].IsInfra=false