Merge pull request #6808 from mheon/allow_empty_hostport

Allow empty host port in --publish flag
This commit is contained in:
OpenShift Merge Robot
2020-06-29 13:52:47 -04:00
committed by GitHub
6 changed files with 134 additions and 32 deletions

View File

@ -184,8 +184,10 @@ func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string)
}
if hostPort != nil {
if *hostPort == "" {
return newPort, errors.Errorf("must provide a non-empty container host port to publish")
}
// Set 0 as a placeholder. The server side of Specgen
// will find a random, open, unused port to use.
newPort.HostPort = 0
} else {
hostStart, hostLen, err := parseAndValidateRange(*hostPort)
if err != nil {
return newPort, errors.Wrapf(err, "error parsing host port")
@ -195,11 +197,11 @@ func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string)
}
newPort.HostPort = hostStart
}
} else {
newPort.HostPort = newPort.ContainerPort
}
hport := newPort.HostPort
if hport == 0 {
hport = newPort.ContainerPort
}
logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol)
return newPort, nil

View File

@ -626,6 +626,8 @@ When specifying ranges for both, the number of container ports in the range must
(e.g., `podman run -p 1234-1236:1222-1224 --name thisWorks -t busybox`
but not `podman run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`)
With ip: `podman run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage`
Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`).
If it is not, the container port will be randomly assigned a port on the host.
Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT`
**--publish-all**, **-P**=*true|false*

View File

@ -638,6 +638,9 @@ Both hostPort and containerPort can be specified as a range of ports.
When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range.
Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`).
If it is not, the container port will be randomly assigned a port on the host.
Use **podman port** to see the actual mapping: **podman port $CONTAINER $CONTAINERPORT**.
**--publish-all**, **-P**=**true**|**false**

View File

@ -43,6 +43,8 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
containerPortValidate[proto] = make(map[string]map[uint16]uint16)
}
postAssignHostPort := false
// Iterate through all port mappings, generating OCICNI PortMapping
// structs and validating there is no overlap.
for _, port := range portMappings {
@ -71,9 +73,6 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
return nil, nil, nil, errors.Errorf("container port number must be non-0")
}
hostPort := port.HostPort
if hostPort == 0 {
hostPort = containerPort
}
if uint32(len-1)+uint32(containerPort) > 65535 {
return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number")
}
@ -105,10 +104,25 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
cPort := containerPort + index
hPort := hostPort + index
if cPort == 0 || hPort == 0 {
return nil, nil, nil, errors.Errorf("host and container ports cannot be 0")
if cPort == 0 {
return nil, nil, nil, errors.Errorf("container port cannot be 0")
}
// Host port is allowed to be 0. If it is, we
// select a random port on the host.
// This will happen *after* all other ports are
// placed, to ensure we don't accidentally
// select a port that a later mapping wanted.
if hPort == 0 {
// If we already have a host port
// assigned to their container port -
// just use that.
if ctrPortMap[cPort] != 0 {
hPort = ctrPortMap[cPort]
} else {
postAssignHostPort = true
}
} else {
testCPort := ctrPortMap[cPort]
if testCPort != 0 && testCPort != hPort {
// This is an attempt to redefine a port
@ -126,6 +140,7 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
if testCPort == hPort && testHPort == cPort {
continue
}
}
// We appear to be clear. Make an OCICNI port
// struct.
@ -142,6 +157,61 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
}
}
// Handle any 0 host ports now by setting random container ports.
if postAssignHostPort {
remadeMappings := make([]ocicni.PortMapping, 0, len(finalMappings))
// Iterate over all
for _, p := range finalMappings {
if p.HostPort != 0 {
remadeMappings = append(remadeMappings, p)
continue
}
hostIPMap := hostPortValidate[p.Protocol]
ctrIPMap := containerPortValidate[p.Protocol]
hostPortMap, ok := hostIPMap[p.HostIP]
if !ok {
hostPortMap = make(map[uint16]uint16)
hostIPMap[p.HostIP] = hostPortMap
}
ctrPortMap, ok := ctrIPMap[p.HostIP]
if !ok {
ctrPortMap = make(map[uint16]uint16)
ctrIPMap[p.HostIP] = ctrPortMap
}
// See if container port has been used elsewhere
if ctrPortMap[uint16(p.ContainerPort)] != 0 {
// Duplicate definition. Let's not bother
// including it.
continue
}
// Max retries to ensure we don't loop forever.
for i := 0; i < 15; i++ {
candidate, err := getRandomPort()
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort)
}
if hostPortMap[uint16(candidate)] == 0 {
logrus.Debugf("Successfully assigned container port %d to host port %d (IP %s Protocol %s)", p.ContainerPort, candidate, p.HostIP, p.Protocol)
hostPortMap[uint16(candidate)] = uint16(p.ContainerPort)
ctrPortMap[uint16(p.ContainerPort)] = uint16(candidate)
p.HostPort = int32(candidate)
break
}
}
if p.HostPort == 0 {
return nil, nil, nil, errors.Errorf("could not find open host port to map container port %d to", p.ContainerPort)
}
remadeMappings = append(remadeMappings, p)
}
return remadeMappings, containerPortValidate, hostPortValidate, nil
}
return finalMappings, containerPortValidate, hostPortValidate, nil
}

View File

@ -435,7 +435,8 @@ type PortMapping struct {
ContainerPort uint16 `json:"container_port"`
// HostPort is the port number that will be forwarded from the host into
// the container.
// If omitted, will be assumed to be identical to
// If omitted, a random port on the host (guaranteed to be over 1024)
// will be assigned.
HostPort uint16 `json:"host_port,omitempty"`
// Range is the number of ports that will be forwarded, starting at
// HostPort and ContainerPort and counting up.

View File

@ -184,6 +184,30 @@ var _ = Describe("Podman run networking", func() {
Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
It("podman run -p 127.0.0.1::8080/udp", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1::8080/udp", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports["8080/udp"])).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostPort).To(Not(Equal("8080")))
Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostIP).To(Equal("127.0.0.1"))
})
It("podman run -p :8080", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", ":8080", "--name", name, ALPINE, "/bin/sh"})
session.WaitWithDefaultTimeout()
inspectOut := podmanTest.InspectContainer(name)
Expect(len(inspectOut)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
Expect(len(inspectOut[0].NetworkSettings.Ports["8080/tcp"])).To(Equal(1))
Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostPort).To(Not(Equal("8080")))
Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostIP).To(Equal(""))
})
It("podman run network expose host port 80 to container port 8000", func() {
SkipIfRootless()
session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"})