diff --git a/docs/source/markdown/podman-systemd.unit.5.md b/docs/source/markdown/podman-systemd.unit.5.md index 16d99f6fb0..a45001c9ed 100644 --- a/docs/source/markdown/podman-systemd.unit.5.md +++ b/docs/source/markdown/podman-systemd.unit.5.md @@ -365,6 +365,23 @@ it may be absolute or relative to the location of the unit file. This key may be used multiple times +#### `PublishPort=` + +Exposes a port, or a range of ports (e.g. `50-59`), from the container to the host. Equivalent +to the `podman kube play`'s `--publish` option. The format is similar to the Podman options, which is of +the form `ip:hostPort:containerPort`, `ip::containerPort`, `hostPort:containerPort` or +`containerPort`, where the number of host and container ports must be the same (in the case +of a range). + +If the IP is set to 0.0.0.0 or not set at all, the port will be bound on all IPv4 addresses on +the host; use [::] for IPv6. + +The list of published ports specified in the unit file will be merged with the list of ports specified +in the Kubernetes YAML file. If the same container port and protocol is specified in both, the +entry from the unit file will take precedence + +This key can be listed multiple times. + ### Volume units Volume files are named with a `.volume` extension and contain a section `[Volume]` describing the diff --git a/pkg/systemd/quadlet/quadlet.go b/pkg/systemd/quadlet/quadlet.go index d7545b32c6..89768b8ee3 100644 --- a/pkg/systemd/quadlet/quadlet.go +++ b/pkg/systemd/quadlet/quadlet.go @@ -141,6 +141,7 @@ var ( KeyRemapUIDSize: true, KeyNetwork: true, KeyConfigMap: true, + KeyPublishPort: true, } ) @@ -454,63 +455,8 @@ func ConvertContainer(container *parser.UnitFile, isUser bool) (*parser.UnitFile podman.addf("--expose=%s", exposedPort) } - publishPorts := container.LookupAll(ContainerGroup, KeyPublishPort) - for _, publishPort := range publishPorts { - publishPort = strings.TrimSpace(publishPort) // Allow whitespace after - - // IP address could have colons in it. For example: "[::]:8080:80/tcp, so use custom splitter - parts := splitPorts(publishPort) - - var containerPort string - ip := "" - hostPort := "" - - // format (from podman run): - // ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort - // - // ip could be IPv6 with minimum of these chars "[::]" - // containerPort can have a suffix of "/tcp" or "/udp" - // - - switch len(parts) { - case 1: - containerPort = parts[0] - - case 2: - hostPort = parts[0] - containerPort = parts[1] - - case 3: - ip = parts[0] - hostPort = parts[1] - containerPort = parts[2] - - default: - return nil, fmt.Errorf("invalid published port '%s'", publishPort) - } - - if ip == "0.0.0.0" { - ip = "" - } - - if len(hostPort) > 0 && !isPortRange(hostPort) { - return nil, fmt.Errorf("invalid port format '%s'", hostPort) - } - - if len(containerPort) > 0 && !isPortRange(containerPort) { - return nil, fmt.Errorf("invalid port format '%s'", containerPort) - } - - switch { - case len(ip) > 0 && len(hostPort) > 0: - podman.addf("-p=%s:%s:%s", ip, hostPort, containerPort) - case len(ip) > 0: - podman.addf("-p=%s::%s", ip, containerPort) - case len(hostPort) > 0: - podman.addf("-p=%s:%s", hostPort, containerPort) - default: - podman.addf("-p=%s", containerPort) - } + if err := handlePublishPorts(container, ContainerGroup, podman); err != nil { + return nil, err } podman.addEnv(podmanEnv) @@ -775,6 +721,10 @@ func ConvertKube(kube *parser.UnitFile, isUser bool) (*parser.UnitFile, error) { execStart.add("--configmap", configMapPath) } + if err := handlePublishPorts(kube, KubeGroup, execStart); err != nil { + return nil, err + } + execStart.add(yamlPath) service.AddCmdline(ServiceGroup, "ExecStart", execStart.Args) @@ -876,3 +826,67 @@ func getAbsolutePath(quadletUnitFile *parser.UnitFile, filePath string) (string, } return filePath, nil } + +func handlePublishPorts(unitFile *parser.UnitFile, groupName string, podman *PodmanCmdline) error { + publishPorts := unitFile.LookupAll(groupName, KeyPublishPort) + for _, publishPort := range publishPorts { + publishPort = strings.TrimSpace(publishPort) // Allow whitespace after + + // IP address could have colons in it. For example: "[::]:8080:80/tcp, so use custom splitter + parts := splitPorts(publishPort) + + var containerPort string + ip := "" + hostPort := "" + + // format (from podman run): + // ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort + // + // ip could be IPv6 with minimum of these chars "[::]" + // containerPort can have a suffix of "/tcp" or "/udp" + // + + switch len(parts) { + case 1: + containerPort = parts[0] + + case 2: + hostPort = parts[0] + containerPort = parts[1] + + case 3: + ip = parts[0] + hostPort = parts[1] + containerPort = parts[2] + + default: + return fmt.Errorf("invalid published port '%s'", publishPort) + } + + if ip == "0.0.0.0" { + ip = "" + } + + if len(hostPort) > 0 && !isPortRange(hostPort) { + return fmt.Errorf("invalid port format '%s'", hostPort) + } + + if len(containerPort) > 0 && !isPortRange(containerPort) { + return fmt.Errorf("invalid port format '%s'", containerPort) + } + + podman.add("--publish") + switch { + case len(ip) > 0 && len(hostPort) > 0: + podman.addf("%s:%s:%s", ip, hostPort, containerPort) + case len(ip) > 0: + podman.addf("%s::%s", ip, containerPort) + case len(hostPort) > 0: + podman.addf("%s:%s", hostPort, containerPort) + default: + podman.addf("%s", containerPort) + } + } + + return nil +} diff --git a/test/e2e/quadlet/ports.container b/test/e2e/quadlet/ports.container index 38725dcbe9..22eff3e08b 100644 --- a/test/e2e/quadlet/ports.container +++ b/test/e2e/quadlet/ports.container @@ -5,46 +5,46 @@ ExposeHostPort=1000 ## assert-podman-args --expose=2000-3000 ExposeHostPort=2000-3000 -## assert-podman-args -p=127.0.0.1:80:90 +## assert-podman-args --publish 127.0.0.1:80:90 PublishPort=127.0.0.1:80:90 -## assert-podman-args -p=80:91 +## assert-podman-args --publish 80:91 PublishPort=0.0.0.0:80:91 -## assert-podman-args -p=80:92 +## assert-podman-args --publish 80:92 PublishPort=:80:92 -## assert-podman-args -p=127.0.0.1::93 +## assert-podman-args --publish 127.0.0.1::93 PublishPort=127.0.0.1::93 -## assert-podman-args -p=94 +## assert-podman-args --publish 94 PublishPort=0.0.0.0::94 -## assert-podman-args -p=95 +## assert-podman-args --publish 95 PublishPort=::95 -## assert-podman-args -p=80:96 +## assert-podman-args --publish 80:96 PublishPort=80:96 -## assert-podman-args -p=97 +## assert-podman-args --publish 97 PublishPort=97 -## assert-podman-args -p=1234/udp +## assert-podman-args --publish 1234/udp PublishPort=1234/udp -## assert-podman-args -p=1234:1234/udp +## assert-podman-args --publish 1234:1234/udp PublishPort=1234:1234/udp -## assert-podman-args -p=127.0.0.1:1234:1234/udp +## assert-podman-args --publish 127.0.0.1:1234:1234/udp PublishPort=127.0.0.1:1234:1234/udp -## assert-podman-args -p=1234/tcp +## assert-podman-args --publish 1234/tcp PublishPort=1234/tcp -## assert-podman-args -p=1234:1234/tcp +## assert-podman-args --publish 1234:1234/tcp PublishPort=1234:1234/tcp -## assert-podman-args -p=127.0.0.1:1234:1234/tcp +## assert-podman-args --publish 127.0.0.1:1234:1234/tcp PublishPort=127.0.0.1:1234:1234/tcp ## assert-podman-args --expose=2000-3000/udp diff --git a/test/e2e/quadlet/ports.kube b/test/e2e/quadlet/ports.kube new file mode 100644 index 0000000000..cc07a31f0d --- /dev/null +++ b/test/e2e/quadlet/ports.kube @@ -0,0 +1,44 @@ +[Kube] +Yaml=/opt/k8s/deployment.yml + +## assert-podman-args --publish 127.0.0.1:80:90 +PublishPort=127.0.0.1:80:90 + +## assert-podman-args --publish 80:91 +PublishPort=0.0.0.0:80:91 + +## assert-podman-args --publish 80:92 +PublishPort=:80:92 + +## assert-podman-args --publish 127.0.0.1::93 +PublishPort=127.0.0.1::93 + +## assert-podman-args --publish 94 +PublishPort=0.0.0.0::94 + +## assert-podman-args --publish 95 +PublishPort=::95 + +## assert-podman-args --publish 80:96 +PublishPort=80:96 + +## assert-podman-args --publish 97 +PublishPort=97 + +## assert-podman-args --publish 1234/udp +PublishPort=1234/udp + +## assert-podman-args --publish 1234:1234/udp +PublishPort=1234:1234/udp + +## assert-podman-args --publish 127.0.0.1:1234:1234/udp +PublishPort=127.0.0.1:1234:1234/udp + +## assert-podman-args --publish 1234/tcp +PublishPort=1234/tcp + +## assert-podman-args --publish 1234:1234/tcp +PublishPort=1234:1234/tcp + +## assert-podman-args --publish 127.0.0.1:1234:1234/tcp +PublishPort=127.0.0.1:1234:1234/tcp diff --git a/test/e2e/quadlet/ports_ipv6.container b/test/e2e/quadlet/ports_ipv6.container index 2b2adf9562..7109554c54 100644 --- a/test/e2e/quadlet/ports_ipv6.container +++ b/test/e2e/quadlet/ports_ipv6.container @@ -1,28 +1,28 @@ [Container] Image=localhost/imagename -## assert-podman-args -p=[::1]:80:90 +## assert-podman-args --publish [::1]:80:90 PublishPort=[::1]:80:90 -## assert-podman-args -p=[::]:80:91 +## assert-podman-args --publish [::]:80:91 PublishPort=[::]:80:91 -## assert-podman-args -p=[2001:DB8::23]:80:91 +## assert-podman-args --publish [2001:DB8::23]:80:91 PublishPort=[2001:DB8::23]:80:91 -## assert-podman-args -p=[::1]::93 +## assert-podman-args --publish [::1]::93 PublishPort=[::1]::93 -## assert-podman-args -p=[::]::94 +## assert-podman-args --publish [::]::94 PublishPort=[::]::94 -## assert-podman-args -p=[2001:db8::42]::94 +## assert-podman-args --publish [2001:db8::42]::94 PublishPort=[2001:db8::42]::94 -## assert-podman-args -p=[::1]:1234:1234/udp +## assert-podman-args --publish [::1]:1234:1234/udp PublishPort=[::1]:1234:1234/udp -## assert-podman-args -p=[::1]:1234:1234/tcp +## assert-podman-args --publish [::1]:1234:1234/tcp PublishPort=[::1]:1234:1234/tcp -## assert-podman-args -p=[2001:db8:c0:ff:ee::1]:1234:1234/udp +## assert-podman-args --publish [2001:db8:c0:ff:ee::1]:1234:1234/udp PublishPort=[2001:db8:c0:ff:ee::1]:1234:1234/udp diff --git a/test/e2e/quadlet/ports_ipv6.kube b/test/e2e/quadlet/ports_ipv6.kube new file mode 100644 index 0000000000..b1b4fe356a --- /dev/null +++ b/test/e2e/quadlet/ports_ipv6.kube @@ -0,0 +1,29 @@ +[Kube] +Yaml=/opt/k8s/deployment.yml + +## assert-podman-args --publish [::1]:80:90 +PublishPort=[::1]:80:90 + +## assert-podman-args --publish [::]:80:91 +PublishPort=[::]:80:91 + +## assert-podman-args --publish [2001:DB8::23]:80:91 +PublishPort=[2001:DB8::23]:80:91 + +## assert-podman-args --publish [::1]::93 +PublishPort=[::1]::93 + +## assert-podman-args --publish [::]::94 +PublishPort=[::]::94 + +## assert-podman-args --publish [2001:db8::42]::94 +PublishPort=[2001:db8::42]::94 + +## assert-podman-args --publish [::1]:1234:1234/udp +PublishPort=[::1]:1234:1234/udp + +## assert-podman-args --publish [::1]:1234:1234/tcp +PublishPort=[::1]:1234:1234/tcp + +## assert-podman-args --publish [2001:db8:c0:ff:ee::1]:1234:1234/udp +PublishPort=[2001:db8:c0:ff:ee::1]:1234:1234/udp diff --git a/test/e2e/quadlet_test.go b/test/e2e/quadlet_test.go index d969a5746c..a4a1b7f219 100644 --- a/test/e2e/quadlet_test.go +++ b/test/e2e/quadlet_test.go @@ -492,6 +492,8 @@ var _ = Describe("quadlet system generator", func() { Entry("Kube - Network", "network.kube"), Entry("Kube - Quadlet Network", "network.quadlet.kube"), Entry("Kube - ConfigMap", "configmap.kube"), + Entry("Kube - Publish IPv4 ports", "ports.kube"), + Entry("Kube - Publish IPv6 ports", "ports_ipv6.kube"), Entry("Network - Basic", "basic.network"), Entry("Network - Label", "label.network"),