Skip to content

Commit

Permalink
Merge pull request #1829 from baude/enableportbindinginpods
Browse files Browse the repository at this point in the history
Allow users to expose ports from the pod to the host
  • Loading branch information
openshift-merge-robot committed Nov 20, 2018
2 parents 21a7607 + 690c52a commit fe4f094
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 7 deletions.
59 changes: 59 additions & 0 deletions cmd/podman/pod_create.go
Expand Up @@ -3,11 +3,15 @@ package main
import (
"fmt"
"os"
"strconv"
"strings"

"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/rootless"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
Expand Down Expand Up @@ -58,6 +62,10 @@ var podCreateFlags = []cli.Flag{
Name: "pod-id-file",
Usage: "Write the pod ID to the file",
},
cli.StringSliceFlag{
Name: "publish, p",
Usage: "Publish a container's port, or a range of ports, to the host (default [])",
},
cli.StringFlag{
Name: "share",
Usage: "A comma delimited list of kernel namespaces the pod will share",
Expand Down Expand Up @@ -102,6 +110,16 @@ func podCreateCmd(c *cli.Context) error {
defer podIdFile.Close()
defer podIdFile.Sync()
}

if len(c.StringSlice("publish")) > 0 {
if !c.BoolT("infra") {
return errors.Errorf("you must have an infra container to publish port bindings to the host")
}
if rootless.IsRootless() {
return errors.Errorf("rootless networking does not allow port binding to the host")
}
}

if !c.BoolT("infra") && c.IsSet("share") && c.String("share") != "none" && c.String("share") != "" {
return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container")
}
Expand Down Expand Up @@ -131,6 +149,14 @@ func podCreateCmd(c *cli.Context) error {
options = append(options, nsOptions...)
}

if len(c.StringSlice("publish")) > 0 {
portBindings, err := CreatePortBindings(c.StringSlice("publish"))
if err != nil {
return err
}
options = append(options, libpod.WithInfraContainerPorts(portBindings))

}
// always have containers use pod cgroups
// User Opt out is not yet supported
options = append(options, libpod.WithPodCgroups())
Expand All @@ -152,3 +178,36 @@ func podCreateCmd(c *cli.Context) error {

return nil
}

// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands
func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) {
var portBindings []ocicni.PortMapping
// The conversion from []string to natBindings is temporary while mheon reworks the port
// deduplication code. Eventually that step will not be required.
_, natBindings, err := nat.ParsePortSpecs(ports)
if err != nil {
return nil, err
}
for containerPb, hostPb := range natBindings {
var pm ocicni.PortMapping
pm.ContainerPort = int32(containerPb.Int())
for _, i := range hostPb {
var hostPort int
var err error
pm.HostIP = i.HostIP
if i.HostPort == "" {
hostPort = containerPb.Int()
} else {
hostPort, err = strconv.Atoi(i.HostPort)
if err != nil {
return nil, errors.Wrapf(err, "unable to convert host port to integer")
}
}

pm.HostPort = int32(hostPort)
pm.Protocol = containerPb.Proto()
portBindings = append(portBindings, pm)
}
}
return portBindings, nil
}
6 changes: 4 additions & 2 deletions completions/bash/podman
Expand Up @@ -2178,12 +2178,14 @@ _podman_pod_create() {
--cgroup-parent
--infra-command
--infra-image
--share
--podidfile
--label-file
--label
-l
--name
--podidfile
--publish
-p
--share
"

local boolean_options="
Expand Down
9 changes: 9 additions & 0 deletions docs/podman-pod-create.1.md
Expand Up @@ -51,6 +51,15 @@ Assign a name to the pod

Write the pod ID to the file

**-p**, **--publish**=[]

Publish a port or range of ports from the pod to the host

Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort`
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.
Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT`

**--share**=""

A comma deliminated list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts.
Expand Down
11 changes: 11 additions & 0 deletions libpod/options.go
Expand Up @@ -1295,3 +1295,14 @@ func WithInfraContainer() PodCreateOption {
return nil
}
}

// WithInfraContainerPorts tells the pod to add port bindings to the pause container
func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption {
return func(pod *Pod) error {
if pod.valid {
return ErrPodFinalized
}
pod.config.InfraContainer.PortBindings = bindings
return nil
}
}
4 changes: 3 additions & 1 deletion libpod/pod.go
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -96,7 +97,8 @@ type PodContainerInfo struct {

// InfraContainerConfig is the configuration for the pod's infra container
type InfraContainerConfig struct {
HasInfraContainer bool `json:"makeInfraContainer"`
HasInfraContainer bool `json:"makeInfraContainer"`
PortBindings []ocicni.PortMapping `json:"infraPortBindings"`
}

// ID retrieves the pod's ID
Expand Down
128 changes: 128 additions & 0 deletions libpod/pod_easyjson.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions libpod/runtime_pod_infra_linux.go
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
)
Expand Down Expand Up @@ -50,9 +49,8 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
options = append(options, withIsInfra())

// Since user namespace sharing is not implemented, we only need to check if it's rootless
portMappings := make([]ocicni.PortMapping, 0)
networks := make([]string, 0)
options = append(options, WithNetNS(portMappings, isRootless, networks))
options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, networks))

return r.newContainer(ctx, g.Config, options...)
}
Expand Down
1 change: 0 additions & 1 deletion pkg/spec/createconfig.go
Expand Up @@ -335,7 +335,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
}
options = append(options, runtime.WithPod(pod))
}

if len(c.PortBindings) > 0 {
portBindings, err = c.CreatePortBindings()
if err != nil {
Expand Down
39 changes: 39 additions & 0 deletions test/e2e/pod_create_test.go
Expand Up @@ -80,4 +80,43 @@ var _ = Describe("Podman pod create", func() {
check.WaitWithDefaultTimeout()
Expect(len(check.OutputToStringArray())).To(Equal(0))
})

It("podman create pod without network portbindings", func() {
name := "test"
session := podmanTest.Podman([]string{"pod", "create", "--name", name})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
pod := session.OutputToString()

webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx})
webserver.WaitWithDefaultTimeout()
Expect(webserver.ExitCode()).To(Equal(0))

check := SystemExec("nc", []string{"-z", "localhost", "80"})
check.WaitWithDefaultTimeout()
Expect(check.ExitCode()).To(Equal(1))
})

It("podman create pod with network portbindings", func() {
name := "test"
session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "80:80"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
pod := session.OutputToString()

webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx})
webserver.WaitWithDefaultTimeout()
Expect(webserver.ExitCode()).To(Equal(0))

check := SystemExec("nc", []string{"-z", "localhost", "80"})
check.WaitWithDefaultTimeout()
Expect(check.ExitCode()).To(Equal(0))
})

It("podman create pod with no infra but portbindings should fail", func() {
name := "test"
session := podmanTest.Podman([]string{"pod", "create", "--infra=false", "--name", name, "-p", "80:80"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125))
})
})

0 comments on commit fe4f094

Please sign in to comment.