Merge pull request #5754 from baude/v2psformat

v2podman ps alter formats
This commit is contained in:
OpenShift Merge Robot
2020-04-09 15:56:26 +02:00
committed by GitHub
6 changed files with 187 additions and 174 deletions

View File

@ -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 <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)
}

View File

@ -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 <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)
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -148,7 +148,7 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
}
ps := entities.ListContainer{
Command: conConfig.Command,
Cmd: conConfig.Command,
Created: conConfig.CreatedTime.Unix(),
Exited: exited,
ExitCode: exitCode,
@ -158,13 +158,13 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
IsInfra: conConfig.IsInfra,
Labels: conConfig.Labels,
Mounts: ctr.UserVolumes(),
Names: []string{conConfig.Name},
ContainerNames: []string{conConfig.Name},
Pid: pid,
Pod: conConfig.Pod,
Ports: conConfig.PortMappings,
Size: size,
PortMappings: conConfig.PortMappings,
ContainerSize: size,
StartedAt: startedTime.Unix(),
State: conState.String(),
ContainerState: conState.String(),
}
if opts.Pod && len(conConfig.Pod) > 0 {
pod, err := rt.GetPod(conConfig.Pod)

View File

@ -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