From 8ce23775a62b816c599e6d65c92985f62acc85c5 Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 7 Apr 2020 15:10:15 -0500 Subject: 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 --- cmd/podmanV2/containers/ps.go | 146 +---------------------------- 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 +- 6 files changed, 187 insertions(+), 174 deletions(-) diff --git a/cmd/podmanV2/containers/ps.go b/cmd/podmanV2/containers/ps.go index 2397eb8c0..8c1d44842 100644 --- a/cmd/podmanV2/containers/ps.go +++ b/cmd/podmanV2/containers/ps.go @@ -4,8 +4,6 @@ import ( "encoding/json" "fmt" "os" - "sort" - "strconv" "strings" "text/tabwriter" "text/template" @@ -13,12 +11,8 @@ import ( tm "github.com/buger/goterm" "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podmanV2/registry" - "github.com/containers/libpod/cmd/podmanV2/report" "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" ) @@ -44,9 +38,6 @@ var ( filters []string noTrunc bool defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" - -// CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - ) func init() { @@ -143,7 +134,6 @@ func getResponses() ([]entities.ListContainer, error) { } func ps(cmd *cobra.Command, args []string) error { - // []string to map[string][]string for _, f := range filters { split := strings.SplitN(f, "=", 2) if len(split) == 1 { @@ -178,8 +168,7 @@ func ps(cmd *cobra.Command, args []string) error { if !listOpts.Quiet && !cmd.Flag("format").Changed { format = headers + format } - funcs := report.AppendFuncMap(psFuncMap) - tmpl, err := template.New("listPods").Funcs(funcs).Parse(format) + tmpl, err := template.New("listContainers").Parse(format) if err != nil { return err } @@ -217,7 +206,7 @@ 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 .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" + 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 @@ -226,7 +215,7 @@ func createPsOut() (string, string) { } else { row += "{{slice .ID 0 12}}" } - row += "\t{{.Image}}\t{{cmd .Command}}\t{{humanDuration .Created}}\t{{state .}}\t{{ports .Ports}}\t{{names .Names}}" + row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.State}}\t{{.Ports}}\t{{.Names}}" if listOpts.Pod { headers += "\tPOD ID\tPODNAME" @@ -240,7 +229,7 @@ func createPsOut() (string, string) { if listOpts.Size { headers += "\tSIZE" - row += "\t{{consize .Size}}" + row += "\t{{.Size}}" } if !strings.HasSuffix(headers, "\n") { headers += "\n" @@ -250,130 +239,3 @@ func createPsOut() (string, string) { } return headers, row } - -var psFuncMap = template.FuncMap{ - "cmd": func(conCommand []string) string { - return strings.Join(conCommand, " ") - }, - "state": func(con entities.ListContainer) string { - var state string - switch con.State { - case "running": - t := units.HumanDuration(time.Since(time.Unix(con.StartedAt, 0))) - state = "Up " + t + " ago" - case "configured": - state = "Created" - case "exited": - t := units.HumanDuration(time.Since(time.Unix(con.ExitedAt, 0))) - state = fmt.Sprintf("Exited (%d) %s ago", con.ExitCode, t) - default: - state = con.State - } - return state - }, - "ports": func(ports []ocicni.PortMapping) string { - if len(ports) == 0 { - return "" - } - return portsToString(ports) - }, - "names": func(names []string) string { - return names[0] - }, - "consize": func(csize shared.ContainerSize) string { - virt := units.HumanSizeWithPrecision(float64(csize.RootFsSize), 3) - s := units.HumanSizeWithPrecision(float64(csize.RwSize), 3) - return fmt.Sprintf("%s (virtual %s)", s, virt) - }, -} - -// 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 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/entities/container_ps.go b/pkg/domain/entities/container_ps.go index ceafecebc..8a8666ff9 100644 --- a/pkg/domain/entities/container_ps.go +++ b/pkg/domain/entities/container_ps.go @@ -1,19 +1,23 @@ 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 - Command []string + Cmd []string // Container creation time Created int64 // If container has exited/stopped @@ -33,7 +37,7 @@ type ListContainer struct { // User volume mounts Mounts []string // The names assigned to the container - Names []string + ContainerNames []string // Namespaces the container belongs to. Requires the // namespace boolean to be true Namespaces ListContainerNamespaces @@ -46,13 +50,69 @@ type ListContainer struct { // boolean to be set PodName string // Port mappings - Ports []ocicni.PortMapping + PortMappings []ocicni.PortMapping // Size of the container rootfs. Requires the size boolean to be true - Size *shared.ContainerSize + ContainerSize *shared.ContainerSize // Time when container started StartedAt int64 // State of container - State string + 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": + 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" } // ListContainer Namespaces contains the identifiers of the container's Linux namespaces @@ -93,7 +153,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].Command, " ") < strings.Join(a.SortListContainers[j].Command, " ") + return strings.Join(a.SortListContainers[i].Cmd, " ") < strings.Join(a.SortListContainers[j].Cmd, " ") } type psSortedId struct{ SortListContainers } @@ -111,7 +171,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].Names[0] < a.SortListContainers[j].Names[0] + return a.SortListContainers[i].ContainerNames[0] < a.SortListContainers[j].ContainerNames[0] } type psSortedPod struct{ SortListContainers } @@ -129,16 +189,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].State < a.SortListContainers[j].State + return a.SortListContainers[i].ContainerState < a.SortListContainers[j].ContainerState } type psSortedSize struct{ SortListContainers } func (a psSortedSize) Less(i, j int) bool { - if a.SortListContainers[i].Size == nil || a.SortListContainers[j].Size == nil { + if a.SortListContainers[i].ContainerSize == nil || a.SortListContainers[j].ContainerSize == nil { return false } - return a.SortListContainers[i].Size.RootFsSize < a.SortListContainers[j].Size.RootFsSize + return a.SortListContainers[i].ContainerSize.RootFsSize < a.SortListContainers[j].ContainerSize.RootFsSize } type PsSortedCreateTime struct{ SortListContainers } @@ -172,3 +232,94 @@ 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 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 b22c6e3ba..6d39f96db 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.State == define.ContainerStateRunning.String() { + if c.ContainerState == 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.State == define.ContainerStateExited.String() { + if c.ContainerState == define.ContainerStateExited.String() { ctrs = append(ctrs, c) } } diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 682d60d6a..4d7e45897 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.Names) { + if id == con.ID || strings.HasPrefix(con.ID, id) || util.StringInSlice(id, con.ContainerNames) { cons = append(cons, con) found = true break diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 58fcc2c21..9217fa595 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{ - 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(), + 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(), } 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 7fb39b221..04e2fa64c 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].Command[0]="true" \ - .[0].State~\\\(exited\\\|stopped\\\) \ + .[0].Cmd[0]="true" \ + .[0].ContainerState~\\\(exited\\\|stopped\\\) \ .[0].ExitCode=0 \ .[0].IsInfra=false -- cgit v1.2.3-54-g00ecf