Skip to content

Commit 1866072

Browse files
Merge pull request #24333 from TomSweeneyRedHat/dev/sweeney/accel299-4.9-rhel
[v4.9-rhel] Fix exposed ports
2 parents 5c84289 + 0889c74 commit 1866072

File tree

10 files changed

+145
-41
lines changed

10 files changed

+145
-41
lines changed

libpod/container_inspect.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver
206206
return nil, err
207207
}
208208
data.NetworkSettings = networkConfig
209+
// Ports in NetworkSettings includes exposed ports for network modes that are not host,
210+
// and not container.
211+
if !(c.config.NetNsCtr != "" || c.NetworkMode() == "host") {
212+
addInspectPortsExpose(c.config.ExposedPorts, data.NetworkSettings.Ports)
213+
}
209214

210215
inspectConfig := c.generateInspectContainerConfig(ctrSpec)
211216
data.Config = inspectConfig
@@ -434,6 +439,25 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp
434439

435440
ctrConfig.SdNotifyMode = c.config.SdNotifyMode
436441
ctrConfig.SdNotifySocket = c.config.SdNotifySocket
442+
443+
// Exosed ports consists of all exposed ports and all port mappings for
444+
// this container. It does *NOT* follow to another container if we share
445+
// the network namespace.
446+
exposedPorts := make(map[string]struct{})
447+
for port, protocols := range c.config.ExposedPorts {
448+
for _, proto := range protocols {
449+
exposedPorts[fmt.Sprintf("%d/%s", port, proto)] = struct{}{}
450+
}
451+
}
452+
for _, mapping := range c.config.PortMappings {
453+
for i := uint16(0); i < mapping.Range; i++ {
454+
exposedPorts[fmt.Sprintf("%d/%s", mapping.ContainerPort+i, mapping.Protocol)] = struct{}{}
455+
}
456+
}
457+
if len(exposedPorts) > 0 {
458+
ctrConfig.ExposedPorts = exposedPorts
459+
}
460+
437461
return ctrConfig
438462
}
439463

libpod/container_validate.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package libpod
55

66
import (
77
"fmt"
8+
"strings"
89

910
"github.com/containers/image/v5/docker"
1011
"github.com/containers/image/v5/pkg/shortnames"
@@ -164,6 +165,13 @@ func (c *Container) validate() error {
164165
return fmt.Errorf("cannot set a startup healthcheck when there is no regular healthcheck: %w", define.ErrInvalidArg)
165166
}
166167

168+
// Ensure all ports list a single protocol
169+
for _, p := range c.config.PortMappings {
170+
if strings.Contains(p.Protocol, ",") {
171+
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)
172+
}
173+
}
174+
167175
return nil
168176
}
169177

libpod/define/container_inspect.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ type InspectContainerConfig struct {
8585
SdNotifyMode string `json:"sdNotifyMode,omitempty"`
8686
// SdNotifySocket is the NOTIFY_SOCKET in use by/configured for the container.
8787
SdNotifySocket string `json:"sdNotifySocket,omitempty"`
88+
// ExposedPorts includes ports the container has exposed.
89+
ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"`
8890
}
8991

9092
// InspectRestartPolicy holds information about the container's restart policy.

libpod/networking_common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
234234
}
235235

236236
settings := new(define.InspectNetworkSettings)
237-
settings.Ports = makeInspectPorts(c.config.PortMappings, c.config.ExposedPorts)
237+
settings.Ports = makeInspectPortBindings(c.config.PortMappings)
238238

239239
networks, err := c.networks()
240240
if err != nil {

libpod/options.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption {
10691069
// namespace with a minimal configuration.
10701070
// An optional array of port mappings can be provided.
10711071
// Conflicts with WithNetNSFrom().
1072-
func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks map[string]nettypes.PerNetworkOptions) CtrCreateOption {
1072+
func WithNetNS(portMappings []nettypes.PortMapping, postConfigureNetNS bool, netmode string, networks map[string]nettypes.PerNetworkOptions) CtrCreateOption {
10731073
return func(ctr *Container) error {
10741074
if ctr.valid {
10751075
return define.ErrCtrFinalized
@@ -1079,7 +1079,6 @@ func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]st
10791079
ctr.config.NetMode = namespaces.NetworkMode(netmode)
10801080
ctr.config.CreateNetNS = true
10811081
ctr.config.PortMappings = portMappings
1082-
ctr.config.ExposedPorts = exposedPorts
10831082

10841083
if !ctr.config.NetMode.IsBridge() && len(networks) > 0 {
10851084
return errors.New("cannot use networks when network mode is not bridge")
@@ -1090,6 +1089,20 @@ func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]st
10901089
}
10911090
}
10921091

1092+
// WithExposedPorts includes a set of ports that were exposed by the image in
1093+
// the container config, e.g. for display when the container is inspected.
1094+
func WithExposedPorts(exposedPorts map[uint16][]string) CtrCreateOption {
1095+
return func(ctr *Container) error {
1096+
if ctr.valid {
1097+
return define.ErrCtrFinalized
1098+
}
1099+
1100+
ctr.config.ExposedPorts = exposedPorts
1101+
1102+
return nil
1103+
}
1104+
}
1105+
10931106
// WithNetworkOptions sets additional options for the networks.
10941107
func WithNetworkOptions(options map[string][]string) CtrCreateOption {
10951108
return func(ctr *Container) error {

libpod/util.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,8 @@ func writeHijackHeader(r *http.Request, conn io.Writer, tty bool) {
220220
}
221221
}
222222

223-
// Convert OCICNI port bindings into Inspect-formatted port bindings.
223+
// Generate inspect-formatted port mappings from the format used in our config file
224224
func makeInspectPortBindings(bindings []types.PortMapping) map[string][]define.InspectHostPort {
225-
return makeInspectPorts(bindings, nil)
226-
}
227-
228-
// Convert OCICNI port bindings into Inspect-formatted port bindings with exposed, but not bound ports set to nil.
229-
func makeInspectPorts(bindings []types.PortMapping, expose map[uint16][]string) map[string][]define.InspectHostPort {
230225
portBindings := make(map[string][]define.InspectHostPort)
231226
for _, port := range bindings {
232227
protocols := strings.Split(port.Protocol, ",")
@@ -242,7 +237,12 @@ func makeInspectPorts(bindings []types.PortMapping, expose map[uint16][]string)
242237
}
243238
}
244239
}
245-
// add exposed ports without host port information to match docker
240+
241+
return portBindings
242+
}
243+
244+
// Add exposed ports to inspect port bindings. These must be done on a per-container basis, not per-netns basis.
245+
func addInspectPortsExpose(expose map[uint16][]string, portBindings map[string][]define.InspectHostPort) {
246246
for port, protocols := range expose {
247247
for _, protocol := range protocols {
248248
key := fmt.Sprintf("%d/%s", port, protocol)
@@ -251,7 +251,6 @@ func makeInspectPorts(bindings []types.PortMapping, expose map[uint16][]string)
251251
}
252252
}
253253
}
254-
return portBindings
255254
}
256255

257256
// Write a given string to a new file at a given path.

pkg/api/handlers/compat/containers.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -522,19 +522,6 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
522522
}
523523
stopTimeout := int(l.StopTimeout())
524524

525-
exposedPorts := make(nat.PortSet)
526-
for ep := range inspect.NetworkSettings.Ports {
527-
splitp := strings.SplitN(ep, "/", 2)
528-
if len(splitp) != 2 {
529-
return nil, fmt.Errorf("PORT/PROTOCOL Format required for %q", ep)
530-
}
531-
exposedPort, err := nat.NewPort(splitp[1], splitp[0])
532-
if err != nil {
533-
return nil, err
534-
}
535-
exposedPorts[exposedPort] = struct{}{}
536-
}
537-
538525
var healthcheck *container.HealthConfig
539526
if inspect.Config.Healthcheck != nil {
540527
healthcheck = &container.HealthConfig{
@@ -546,6 +533,16 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
546533
}
547534
}
548535

536+
// Apparently the compiler can't convert a map[string]struct{} into a nat.PortSet
537+
// (Despite a nat.PortSet being that exact struct with some types added)
538+
var exposedPorts nat.PortSet
539+
if len(inspect.Config.ExposedPorts) > 0 {
540+
exposedPorts = make(nat.PortSet)
541+
for p := range inspect.Config.ExposedPorts {
542+
exposedPorts[nat.Port(p)] = struct{}{}
543+
}
544+
}
545+
549546
config := container.Config{
550547
Hostname: l.Hostname(),
551548
Domainname: inspect.Config.DomainName,

pkg/specgen/generate/namespaces.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,13 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
298298

299299
postConfigureNetNS := !s.UserNS.IsHost()
300300

301+
// Network
302+
portMappings, expose, err := createPortMappings(s, imageData)
303+
if err != nil {
304+
return nil, err
305+
}
306+
toReturn = append(toReturn, libpod.WithExposedPorts(expose))
307+
301308
switch s.NetNS.NSMode {
302309
case specgen.FromPod:
303310
if pod == nil || infraCtr == nil {
@@ -317,28 +324,15 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
317324
toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
318325
}
319326
case specgen.Slirp:
320-
portMappings, expose, err := createPortMappings(s, imageData)
321-
if err != nil {
322-
return nil, err
323-
}
324327
val := "slirp4netns"
325328
if s.NetNS.Value != "" {
326329
val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
327330
}
328-
toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
331+
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil))
329332
case specgen.Pasta:
330-
portMappings, expose, err := createPortMappings(s, imageData)
331-
if err != nil {
332-
return nil, err
333-
}
334333
val := "pasta"
335-
toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
334+
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil))
336335
case specgen.Bridge, specgen.Private, specgen.Default:
337-
portMappings, expose, err := createPortMappings(s, imageData)
338-
if err != nil {
339-
return nil, err
340-
}
341-
342336
rtConfig, err := rt.GetConfigNoCopy()
343337
if err != nil {
344338
return nil, err
@@ -365,7 +359,7 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
365359
s.Networks[rtConfig.Network.DefaultNetwork] = opts
366360
delete(s.Networks, "default")
367361
}
368-
toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.Networks))
362+
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.Networks))
369363
}
370364

371365
if s.UseImageHosts {

test/e2e/container_inspect_test.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package integration
22

33
import (
4+
"fmt"
5+
46
"github.com/containers/podman/v4/libpod/define"
57
"github.com/containers/podman/v4/pkg/annotations"
68
. "github.com/containers/podman/v4/test/utils"
@@ -30,7 +32,14 @@ var _ = Describe("Podman container inspect", func() {
3032

3133
Expect(data).To(HaveLen(1))
3234
Expect(data[0].NetworkSettings.Ports).
33-
To(Equal(map[string][]define.InspectHostPort{"8787/udp": nil}))
35+
To(Equal(map[string][]define.InspectHostPort{"8787/udp": nil, "99/sctp": nil}))
36+
Expect(data[0].Config.ExposedPorts).
37+
To(Equal(map[string]struct{}{"8787/udp": {}, "99/sctp": {}}))
38+
39+
session = podmanTest.Podman([]string{"ps", "--format", "{{.Ports}}"})
40+
session.WaitWithDefaultTimeout()
41+
Expect(session).Should(ExitCleanly())
42+
Expect(session.OutputToString()).To(Equal("99/sctp, 8787/udp"))
3443
})
3544

3645
It("podman inspect shows exposed ports on image", func() {
@@ -44,4 +53,25 @@ var _ = Describe("Podman container inspect", func() {
4453
Expect(data[0].NetworkSettings.Ports).
4554
To(Equal(map[string][]define.InspectHostPort{"80/tcp": nil, "8989/tcp": nil}))
4655
})
56+
57+
It("podman inspect exposed ports includes published ports", func() {
58+
c1 := "ctr1"
59+
c1s := podmanTest.Podman([]string{"run", "-d", "--expose", "22/tcp", "-p", "8080:80/tcp", "--name", c1, ALPINE, "top"})
60+
c1s.WaitWithDefaultTimeout()
61+
Expect(c1s).Should(ExitCleanly())
62+
63+
c2 := "ctr2"
64+
c2s := podmanTest.Podman([]string{"run", "-d", "--net", fmt.Sprintf("container:%s", c1), "--name", c2, ALPINE, "top"})
65+
c2s.WaitWithDefaultTimeout()
66+
Expect(c2s).Should(ExitCleanly())
67+
68+
data1 := podmanTest.InspectContainer(c1)
69+
Expect(data1).To(HaveLen(1))
70+
Expect(data1[0].Config.ExposedPorts).
71+
To(Equal(map[string]struct{}{"22/tcp": {}, "80/tcp": {}}))
72+
73+
data2 := podmanTest.InspectContainer(c2)
74+
Expect(data2).To(HaveLen(1))
75+
Expect(data2[0].Config.ExposedPorts).To(BeNil())
76+
})
4777
})

test/e2e/run_networking_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,43 @@ EXPOSE 2004-2005/tcp`, ALPINE)
434434
Expect(inspectOut[0].HostConfig.PublishAllPorts).To(BeTrue())
435435
})
436436

437+
It("podman run --net=host --expose includes ports in inspect output", func() {
438+
containerName := "testctr"
439+
session := podmanTest.Podman([]string{"run", "--net=host", "--name", containerName, "-d", "--expose", "8080/tcp", NGINX_IMAGE, "sleep", "+inf"})
440+
session.WaitWithDefaultTimeout()
441+
Expect(session).Should(ExitCleanly())
442+
443+
inspectOut := podmanTest.InspectContainer(containerName)
444+
Expect(inspectOut).To(HaveLen(1))
445+
446+
// Ports is empty. ExposedPorts is not.
447+
Expect(inspectOut[0].NetworkSettings.Ports).To(BeEmpty())
448+
449+
// 80 from the image, 8080 from the expose
450+
Expect(inspectOut[0].Config.ExposedPorts).To(HaveLen(2))
451+
Expect(inspectOut[0].Config.ExposedPorts).To(HaveKey("80/tcp"))
452+
Expect(inspectOut[0].Config.ExposedPorts).To(HaveKey("8080/tcp"))
453+
})
454+
455+
It("podman run --net=container --expose exposed port from own container", func() {
456+
ctr1 := "test1"
457+
session1 := podmanTest.Podman([]string{"run", "-d", "--name", ctr1, "--expose", "8080/tcp", ALPINE, "top"})
458+
session1.WaitWithDefaultTimeout()
459+
Expect(session1).Should(ExitCleanly())
460+
461+
ctr2 := "test2"
462+
session2 := podmanTest.Podman([]string{"run", "-d", "--name", ctr2, "--net", fmt.Sprintf("container:%s", ctr1), "--expose", "8090/tcp", ALPINE, "top"})
463+
session2.WaitWithDefaultTimeout()
464+
Expect(session2).Should(ExitCleanly())
465+
466+
inspectOut := podmanTest.InspectContainer(ctr2)
467+
Expect(inspectOut).To(HaveLen(1))
468+
// Ports will not be populated. ExposedPorts will be.
469+
Expect(inspectOut[0].NetworkSettings.Ports).To(BeEmpty())
470+
Expect(inspectOut[0].Config.ExposedPorts).To(HaveLen(1))
471+
Expect(inspectOut[0].Config.ExposedPorts).To(HaveKey("8090/tcp"))
472+
})
473+
437474
It("podman run -p 127.0.0.1::8980/udp", func() {
438475
name := "testctr"
439476
session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1::8980/udp", "--name", name, ALPINE, "/bin/sh"})

0 commit comments

Comments
 (0)