podman ps: show exposed ports under PORTS as well

Docker shows exposed ports as just PORT/PROTO so match that behavior. It
is not clear to me why someone needs that information in ps as "expose"
doesn't effect anything networking related.

Fixes https://issues.redhat.com/browse/RHEL-32154

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2024-04-09 15:45:44 +02:00
parent b59993ce09
commit 0bedf7f1d2
5 changed files with 60 additions and 31 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/docker/go-units"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)
var (
@ -434,10 +435,7 @@ func (l psReporter) Networks() string {
// 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)
return portsToString(l.ListContainer.Ports, l.ListContainer.ExposedPorts)
}
// CreatedAt returns the container creation time in string format. podman
@ -489,8 +487,8 @@ func (l psReporter) UTS() string {
// 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.
// The format is IP:HostPort(-Range)->ContainerPort(-Range)/Proto
func portsToString(ports []types.PortMapping) string {
if len(ports) == 0 {
func portsToString(ports []types.PortMapping, exposedPorts map[uint16][]string) string {
if len(ports) == 0 && len(exposedPorts) == 0 {
return ""
}
sb := &strings.Builder{}
@ -512,6 +510,20 @@ func portsToString(ports []types.PortMapping) string {
}
}
}
// iterating a map is not deterministic so let's convert slice first and sort by port to make it deterministic
sortedPorts := make([]uint16, 0, len(exposedPorts))
for port := range exposedPorts {
sortedPorts = append(sortedPorts, port)
}
slices.Sort(sortedPorts)
for _, port := range sortedPorts {
for _, protocol := range exposedPorts[port] {
// exposed ports do not have a host part and are just written as "NUM/PROTO"
fmt.Fprintf(sb, "%d/%s, ", port, protocol)
}
}
display := sb.String()
// make sure to trim the last ", " of the string
return display[:len(display)-2]

View File

@ -80,6 +80,7 @@ Valid placeholders for the Go template are listed below:
| .ExitCode | Container exit code |
| .Exited | "true" if container has exited |
| .ExitedAt | Time (epoch seconds) that container exited |
| .ExposedPorts ... | Map of exposed ports on this container |
| .ID | Container ID |
| .Image | Image Name/ID |
| .ImageID | Image ID |
@ -92,7 +93,7 @@ Valid placeholders for the Go template are listed below:
| .Pid | Process ID on host system |
| .Pod | Pod the container is associated with (SHA) |
| .PodName | PodName of the container |
| .Ports | Exposed ports |
| .Ports | Forwarded and exposed ports |
| .Restarts | Display the container restart count |
| .RunningFor | Time elapsed since container was started |
| .Size | Size of container |

View File

@ -25,6 +25,11 @@ type ListContainer struct {
ExitedAt int64
// If container has exited, the return code from the command
ExitCode int32
// ExposedPorts contains the ports that are exposed but not forwarded,
// see Ports for forwarded ports.
// The key is the port number and the string slice contains the protocols,
// i.e. "tcp", "udp" and "sctp".
ExposedPorts map[uint16][]string
// The unique identifier for the container
ID string `json:"Id"`
// Container image

View File

@ -238,29 +238,30 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
}
ps := entities.ListContainer{
AutoRemove: ctr.AutoRemove(),
CIDFile: conConfig.Spec.Annotations[define.InspectAnnotationCIDFile],
Command: conConfig.Command,
Created: conConfig.CreatedTime,
ExitCode: exitCode,
Exited: exited,
ExitedAt: exitedTime.Unix(),
ID: conConfig.ID,
Image: conConfig.RootfsImageName,
ImageID: conConfig.RootfsImageID,
IsInfra: conConfig.IsInfra,
Labels: conConfig.Labels,
Mounts: ctr.UserVolumes(),
Names: []string{conConfig.Name},
Networks: networks,
Pid: pid,
Pod: conConfig.Pod,
Ports: portMappings,
Restarts: restartCount,
Size: size,
StartedAt: startedTime.Unix(),
State: conState.String(),
Status: healthStatus,
AutoRemove: ctr.AutoRemove(),
CIDFile: conConfig.Spec.Annotations[define.InspectAnnotationCIDFile],
Command: conConfig.Command,
Created: conConfig.CreatedTime,
ExitCode: exitCode,
Exited: exited,
ExitedAt: exitedTime.Unix(),
ExposedPorts: conConfig.ExposedPorts,
ID: conConfig.ID,
Image: conConfig.RootfsImageName,
ImageID: conConfig.RootfsImageID,
IsInfra: conConfig.IsInfra,
Labels: conConfig.Labels,
Mounts: ctr.UserVolumes(),
Names: []string{conConfig.Name},
Networks: networks,
Pid: pid,
Pod: conConfig.Pod,
Ports: portMappings,
Restarts: restartCount,
Size: size,
StartedAt: startedTime.Unix(),
State: conState.String(),
Status: healthStatus,
}
if opts.Pod && len(conConfig.Pod) > 0 {
podName, err := rt.GetPodName(conConfig.Pod)

View File

@ -26,7 +26,7 @@ var _ = Describe("Podman container inspect", func() {
It("podman inspect shows exposed ports", func() {
name := "testcon"
session := podmanTest.Podman([]string{"run", "-d", "--stop-timeout", "0", "--expose", "8787/udp", "--name", name, ALPINE, "sleep", "inf"})
session := podmanTest.Podman([]string{"run", "-d", "--stop-timeout", "0", "--expose", "8787/udp", "--name", name, ALPINE, "sleep", "100"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
data := podmanTest.InspectContainer(name)
@ -34,6 +34,11 @@ var _ = Describe("Podman container inspect", func() {
Expect(data).To(HaveLen(1))
Expect(data[0].NetworkSettings.Ports).
To(Equal(map[string][]define.InspectHostPort{"8787/udp": nil}))
session = podmanTest.Podman([]string{"ps", "--format", "{{.Ports}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("8787/udp"))
})
It("podman inspect shows exposed ports on image", func() {
@ -46,6 +51,11 @@ var _ = Describe("Podman container inspect", func() {
Expect(data).To(HaveLen(1))
Expect(data[0].NetworkSettings.Ports).
To(Equal(map[string][]define.InspectHostPort{"80/tcp": nil, "8989/tcp": nil}))
session = podmanTest.Podman([]string{"ps", "--format", "{{.Ports}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("80/tcp, 8989/tcp"))
})
It("podman inspect shows volumes-from with mount options", func() {