diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 5a80ca7cbf..4a33534f61 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -459,6 +459,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 a98545c91f..29c71077f3 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -4,6 +4,7 @@ package libpod import ( "fmt" + "strings" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/pkg/shortnames" @@ -175,6 +176,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 4be6fd913c..4062e7bc9d 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -97,6 +97,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"` // V4PodmanCompatMarshal indicates that the json marshaller should // use the old v4 inspect format to keep API compatibility. diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index bd883bf6f2..706d2d97ba 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -535,19 +535,6 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, } stopTimeout := int(l.StopTimeout()) - exposedPorts := make(nat.PortSet) - for ep := range inspect.NetworkSettings.Ports { - port, proto, ok := strings.Cut(ep, "/") - if !ok { - return nil, fmt.Errorf("PORT/PROTOCOL Format required for %q", ep) - } - exposedPort, err := nat.NewPort(proto, port) - if err != nil { - return nil, err - } - exposedPorts[exposedPort] = struct{}{} - } - var healthcheck *container.HealthConfig if inspect.Config.Healthcheck != nil { healthcheck = &container.HealthConfig{ @@ -559,6 +546,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 00bd21db7f..293460f496 100644 --- a/test/e2e/container_inspect_test.go +++ b/test/e2e/container_inspect_test.go @@ -37,6 +37,8 @@ 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, "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() @@ -61,6 +63,27 @@ var _ = Describe("Podman container inspect", func() { Expect(session.OutputToString()).To(Equal("80/tcp, 8989/tcp")) }) + 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()) + }) + It("podman inspect shows volumes-from with mount options", func() { ctr1 := "volfctr" ctr2 := "voltctr"