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 <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-04-07 15:10:15 -05:00
parent 522dcd604e
commit 8ce23775a6
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,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)

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