From cc9b6977019d2cb109e8ce908319a86749db46c1 Mon Sep 17 00:00:00 2001 From: Matt Heon Date: Mon, 30 Sep 2024 08:59:27 -0400 Subject: [PATCH] [v4.9-rhel] Add ExposedPorts to Inspect's ContainerConfig A field we missed versus Docker. Matches the format of our existing Ports list in the NetworkConfig, but only includes exposed ports (and maps these to struct{}, as they never go to real ports on the host). Fixes https://issues.redhat.com/browse/RHEL-60382 Signed-off-by: Matt Heon (cherry picked from commit edc3dc5e118f7676055130a76d8007f1776c026c) Signed-off-by: tomsweeneyredhat --- libpod/container_inspect.go | 19 +++++++++++++++++ libpod/container_validate.go | 8 +++++++ libpod/define/container_inspect.go | 2 ++ pkg/api/handlers/compat/containers.go | 23 +++++++++----------- test/e2e/container_inspect_test.go | 30 ++++++++++++++++++++++++++- 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index be46b32910..8b359b9f0b 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -435,6 +435,25 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.SdNotifyMode = c.config.SdNotifyMode ctrConfig.SdNotifySocket = c.config.SdNotifySocket + + // Exosed ports consists of all exposed ports and all port mappings for + // this container. It does *NOT* follow to another container if we share + // the network namespace. + exposedPorts := make(map[string]struct{}) + for port, protocols := range c.config.ExposedPorts { + for _, proto := range protocols { + exposedPorts[fmt.Sprintf("%d/%s", port, proto)] = struct{}{} + } + } + for _, mapping := range c.config.PortMappings { + for i := range mapping.Range { + exposedPorts[fmt.Sprintf("%d/%s", mapping.ContainerPort+i, mapping.Protocol)] = struct{}{} + } + } + if len(exposedPorts) > 0 { + ctrConfig.ExposedPorts = exposedPorts + } + return ctrConfig } diff --git a/libpod/container_validate.go b/libpod/container_validate.go index e01d8ef098..1ce3755288 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -5,6 +5,7 @@ package libpod import ( "fmt" + "strings" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/pkg/shortnames" @@ -164,6 +165,13 @@ func (c *Container) validate() error { return fmt.Errorf("cannot set a startup healthcheck when there is no regular healthcheck: %w", define.ErrInvalidArg) } + // Ensure all ports list a single protocol + for _, p := range c.config.PortMappings { + if strings.Contains(p.Protocol, ",") { + return fmt.Errorf("each port mapping must define a single protocol, got a comma-separated list for container port %d (protocols requested %q): %w", p.ContainerPort, p.Protocol, define.ErrInvalidArg) + } + } + return nil } diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 879dd13959..3f5a99ffeb 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -85,6 +85,8 @@ type InspectContainerConfig struct { SdNotifyMode string `json:"sdNotifyMode,omitempty"` // SdNotifySocket is the NOTIFY_SOCKET in use by/configured for the container. SdNotifySocket string `json:"sdNotifySocket,omitempty"` + // ExposedPorts includes ports the container has exposed. + ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"` } // InspectRestartPolicy holds information about the container's restart policy. diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 32a8024ed2..022dcffe31 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -522,19 +522,6 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, } stopTimeout := int(l.StopTimeout()) - exposedPorts := make(nat.PortSet) - for ep := range inspect.NetworkSettings.Ports { - splitp := strings.SplitN(ep, "/", 2) - if len(splitp) != 2 { - return nil, fmt.Errorf("PORT/PROTOCOL Format required for %q", ep) - } - exposedPort, err := nat.NewPort(splitp[1], splitp[0]) - if err != nil { - return nil, err - } - exposedPorts[exposedPort] = struct{}{} - } - var healthcheck *container.HealthConfig if inspect.Config.Healthcheck != nil { healthcheck = &container.HealthConfig{ @@ -546,6 +533,16 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, } } + // Apparently the compiler can't convert a map[string]struct{} into a nat.PortSet + // (Despite a nat.PortSet being that exact struct with some types added) + var exposedPorts nat.PortSet + if len(inspect.Config.ExposedPorts) > 0 { + exposedPorts = make(nat.PortSet) + for p := range inspect.Config.ExposedPorts { + exposedPorts[nat.Port(p)] = struct{}{} + } + } + config := container.Config{ Hostname: l.Hostname(), Domainname: inspect.Config.DomainName, diff --git a/test/e2e/container_inspect_test.go b/test/e2e/container_inspect_test.go index 953c6af06f..84d9c24e9e 100644 --- a/test/e2e/container_inspect_test.go +++ b/test/e2e/container_inspect_test.go @@ -30,7 +30,14 @@ 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})) + To(Equal(map[string][]define.InspectHostPort{"8787/udp": nil, "99/sctp": nil})) + Expect(data[0].Config.ExposedPorts). + To(Equal(map[string]struct{}{"8787/udp": {}, "99/sctp": {}})) + + session = podmanTest.Podman([]string{"ps", "--format", "{{.Ports}}"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToString()).To(Equal("99/sctp, 8787/udp")) }) It("podman inspect shows exposed ports on image", func() { @@ -44,4 +51,25 @@ var _ = Describe("Podman container inspect", func() { Expect(data[0].NetworkSettings.Ports). To(Equal(map[string][]define.InspectHostPort{"80/tcp": nil, "8989/tcp": nil})) }) + + It("podman inspect exposed ports includes published ports", func() { + c1 := "ctr1" + c1s := podmanTest.Podman([]string{"run", "-d", "--expose", "22/tcp", "-p", "8080:80/tcp", "--name", c1, ALPINE, "top"}) + c1s.WaitWithDefaultTimeout() + Expect(c1s).Should(ExitCleanly()) + + c2 := "ctr2" + c2s := podmanTest.Podman([]string{"run", "-d", "--net", fmt.Sprintf("container:%s", c1), "--name", c2, ALPINE, "top"}) + c2s.WaitWithDefaultTimeout() + Expect(c2s).Should(ExitCleanly()) + + data1 := podmanTest.InspectContainer(c1) + Expect(data1).To(HaveLen(1)) + Expect(data1[0].Config.ExposedPorts). + To(Equal(map[string]struct{}{"22/tcp": {}, "80/tcp": {}})) + + data2 := podmanTest.InspectContainer(c2) + Expect(data2).To(HaveLen(1)) + Expect(data2[0].Config.ExposedPorts).To(BeNil()) + }) })