diff options
-rw-r--r-- | cmd/podmanV2/containers/ps.go | 146 | ||||
-rw-r--r-- | pkg/domain/entities/container_ps.go | 171 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 4 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/helpers.go | 2 | ||||
-rw-r--r-- | pkg/ps/ps.go | 34 | ||||
-rw-r--r-- | 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 <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/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 <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 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 |