summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/kube.go22
-rw-r--r--cmd/podman/kube_generate.go93
-rw-r--r--cmd/podman/main.go1
-rw-r--r--cmd/podman/pod_create.go59
-rw-r--r--completions/bash/podman6
-rw-r--r--contrib/python/podman/podman/libs/containers.py52
-rw-r--r--docs/podman-pod-create.1.md9
-rw-r--r--libpod/kube.go270
-rw-r--r--libpod/options.go11
-rw-r--r--libpod/pod.go4
-rw-r--r--libpod/pod_easyjson.go128
-rw-r--r--libpod/runtime_pod_infra_linux.go4
-rw-r--r--pkg/spec/createconfig.go1
-rw-r--r--test/e2e/pod_create_test.go39
14 files changed, 663 insertions, 36 deletions
diff --git a/cmd/podman/kube.go b/cmd/podman/kube.go
new file mode 100644
index 000000000..ced87e2bd
--- /dev/null
+++ b/cmd/podman/kube.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ "github.com/urfave/cli"
+)
+
+var (
+ kubeSubCommands = []cli.Command{
+ containerKubeCommand,
+ }
+
+ kubeDescription = "Work with Kubernetes objects"
+ kubeCommand = cli.Command{
+ Name: "kube",
+ Usage: "Import and export Kubernetes objections from and to Podman",
+ Description: containerDescription,
+ ArgsUsage: "",
+ Subcommands: kubeSubCommands,
+ UseShortOptionHandling: true,
+ OnUsageError: usageErrorHandler,
+ }
+)
diff --git a/cmd/podman/kube_generate.go b/cmd/podman/kube_generate.go
new file mode 100644
index 000000000..a18912668
--- /dev/null
+++ b/cmd/podman/kube_generate.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/ghodss/yaml"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+var (
+ containerKubeFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "service, s",
+ Usage: "only generate YAML for kubernetes service object",
+ },
+ LatestFlag,
+ }
+ containerKubeDescription = "Generate Kubernetes Pod YAML"
+ containerKubeCommand = cli.Command{
+ Name: "generate",
+ Usage: "Generate Kubernetes pod YAML for a container",
+ Description: containerKubeDescription,
+ Flags: sortFlags(containerKubeFlags),
+ Action: generateKubeYAMLCmd,
+ ArgsUsage: "CONTAINER-NAME",
+ UseShortOptionHandling: true,
+ OnUsageError: usageErrorHandler,
+ }
+)
+
+// generateKubeYAMLCmdgenerates or replays kube
+func generateKubeYAMLCmd(c *cli.Context) error {
+ var (
+ container *libpod.Container
+ err error
+ output []byte
+ )
+
+ if rootless.IsRootless() {
+ return errors.Wrapf(libpod.ErrNotImplemented, "rootless users")
+ }
+ args := c.Args()
+ if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) {
+ return errors.Errorf("you must provide one container ID or name or --latest")
+ }
+ if c.Bool("service") {
+ return errors.Wrapf(libpod.ErrNotImplemented, "service generation")
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ // Get the container in question
+ if c.Bool("latest") {
+ container, err = runtime.GetLatestContainer()
+ } else {
+ container, err = runtime.LookupContainer(args[0])
+ }
+ if err != nil {
+ return err
+ }
+
+ if len(container.Dependencies()) > 0 {
+ return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies")
+ }
+
+ podYAML, err := container.InspectForKube()
+ if err != nil {
+ return err
+ }
+
+ developmentComment := []byte("# Generation of Kubenetes YAML is still under development!\n")
+ logrus.Warn("This function is still under heavy development.")
+ // Marshall the results
+ b, err := yaml.Marshal(podYAML)
+ if err != nil {
+ return err
+ }
+ output = append(output, developmentComment...)
+ output = append(output, b...)
+ // Output the v1.Pod with the v1.Container
+ fmt.Println(string(output))
+
+ return nil
+}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 38eac4504..6be192593 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -77,6 +77,7 @@ func main() {
infoCommand,
inspectCommand,
killCommand,
+ kubeCommand,
loadCommand,
loginCommand,
logoutCommand,
diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go
index 63fa6b294..a3364ac4b 100644
--- a/cmd/podman/pod_create.go
+++ b/cmd/podman/pod_create.go
@@ -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"
@@ -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",
@@ -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")
}
@@ -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())
@@ -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
+}
diff --git a/completions/bash/podman b/completions/bash/podman
index c029f893a..222511a3c 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -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="
diff --git a/contrib/python/podman/podman/libs/containers.py b/contrib/python/podman/podman/libs/containers.py
index e211a284e..21a94557a 100644
--- a/contrib/python/podman/podman/libs/containers.py
+++ b/contrib/python/podman/podman/libs/containers.py
@@ -1,12 +1,12 @@
"""Models for manipulating containers and storage."""
import collections
-import functools
import getpass
import json
import logging
import signal
import time
+from . import fold_keys
from ._containers_attach import Mixin as AttachMixin
from ._containers_start import Mixin as StartMixin
@@ -14,25 +14,27 @@ from ._containers_start import Mixin as StartMixin
class Container(AttachMixin, StartMixin, collections.UserDict):
"""Model for a container."""
- def __init__(self, client, id, data):
+ def __init__(self, client, ident, data, refresh=True):
"""Construct Container Model."""
super(Container, self).__init__(data)
-
self._client = client
- self._id = id
+ self._id = ident
- with client() as podman:
- self._refresh(podman)
+ if refresh:
+ with client() as podman:
+ self._refresh(podman)
+ else:
+ for k, v in self.data.items():
+ setattr(self, k, v)
+ if 'containerrunning' in self.data:
+ setattr(self, 'running', self.data['containerrunning'])
+ self.data['running'] = self.data['containerrunning']
assert self._id == data['id'],\
'Requested container id({}) does not match store id({})'.format(
self._id, data['id']
)
- def __getitem__(self, key):
- """Get items from parent dict."""
- return super().__getitem__(key)
-
def _refresh(self, podman, tries=1):
try:
ctnr = podman.GetContainer(self._id)
@@ -71,18 +73,18 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
results = podman.ListContainerChanges(self._id)
return results['container']
- def kill(self, signal=signal.SIGTERM, wait=25):
+ def kill(self, sig=signal.SIGTERM, wait=25):
"""Send signal to container.
default signal is signal.SIGTERM.
wait n of seconds, 0 waits forever.
"""
with self._client() as podman:
- podman.KillContainer(self._id, signal)
+ podman.KillContainer(self._id, sig)
timeout = time.time() + wait
while True:
self._refresh(podman)
- if self.status != 'running':
+ if self.status != 'running': # pylint: disable=no-member
return self
if wait and timeout < time.time():
@@ -90,20 +92,11 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
time.sleep(0.5)
- def _lower_hook(self):
- """Convert all keys to lowercase."""
-
- @functools.wraps(self._lower_hook)
- def wrapped(input_):
- return {k.lower(): v for (k, v) in input_.items()}
-
- return wrapped
-
def inspect(self):
"""Retrieve details about containers."""
with self._client() as podman:
results = podman.InspectContainer(self._id)
- obj = json.loads(results['container'], object_hook=self._lower_hook())
+ obj = json.loads(results['container'], object_hook=fold_keys())
return collections.namedtuple('ContainerInspect', obj.keys())(**obj)
def export(self, target):
@@ -121,7 +114,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
changes=[],
message='',
pause=True,
- **kwargs):
+ **kwargs): # pylint: disable=unused-argument
"""Create image from container.
All changes overwrite existing values.
@@ -175,7 +168,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
podman.RestartContainer(self._id, timeout)
return self._refresh(podman)
- def rename(self, target):
+ def rename(self, target): # pylint: disable=unused-argument
"""Rename container, return id on success."""
with self._client() as podman:
# TODO: Need arguments
@@ -183,7 +176,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
# TODO: fixup objects cached information
return results['container']
- def resize_tty(self, width, height):
+ def resize_tty(self, width, height): # pylint: disable=unused-argument
"""Resize container tty."""
with self._client() as podman:
# TODO: magic re: attach(), arguments
@@ -201,7 +194,8 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
podman.UnpauseContainer(self._id)
return self._refresh(podman)
- def update_container(self, *args, **kwargs):
+ def update_container(self, *args, **kwargs): \
+ # pylint: disable=unused-argument
"""TODO: Update container..., return id on success."""
with self._client() as podman:
podman.UpdateContainer()
@@ -220,7 +214,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
obj = results['container']
return collections.namedtuple('StatDetail', obj.keys())(**obj)
- def logs(self, *args, **kwargs):
+ def logs(self, *args, **kwargs): # pylint: disable=unused-argument
"""Retrieve container logs."""
with self._client() as podman:
results = podman.GetContainerLogs(self._id)
@@ -239,7 +233,7 @@ class Containers():
with self._client() as podman:
results = podman.ListContainers()
for cntr in results['containers']:
- yield Container(self._client, cntr['id'], cntr)
+ yield Container(self._client, cntr['id'], cntr, refresh=False)
def delete_stopped(self):
"""Delete all stopped containers."""
diff --git a/docs/podman-pod-create.1.md b/docs/podman-pod-create.1.md
index 673ad9a8c..a63b12d73 100644
--- a/docs/podman-pod-create.1.md
+++ b/docs/podman-pod-create.1.md
@@ -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.
diff --git a/libpod/kube.go b/libpod/kube.go
new file mode 100644
index 000000000..00db0033b
--- /dev/null
+++ b/libpod/kube.go
@@ -0,0 +1,270 @@
+package libpod
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/containers/libpod/pkg/lookup"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+ v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// InspectForKube takes a slice of libpod containers and generates
+// one v1.Pod description that includes just a single container.
+func (c *Container) InspectForKube() (*v1.Pod, error) {
+ // Generate the v1.Pod yaml description
+ return simplePodWithV1Container(c)
+}
+
+// simplePodWithV1Container is a function used by inspect when kube yaml needs to be generated
+// for a single container. we "insert" that container description in a pod.
+func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) {
+ var containers []v1.Container
+ result, err := containerToV1Container(ctr)
+ if err != nil {
+ return nil, err
+ }
+ containers = append(containers, result)
+
+ tm := v12.TypeMeta{
+ Kind: "Pod",
+ APIVersion: "v1",
+ }
+
+ // Add a label called "app" with the containers name as a value
+ labels := make(map[string]string)
+ labels["app"] = removeUnderscores(ctr.Name())
+ om := v12.ObjectMeta{
+ // The name of the pod is container_name-libpod
+ Name: fmt.Sprintf("%s-libpod", removeUnderscores(ctr.Name())),
+ Labels: labels,
+ // CreationTimestamp seems to be required, so adding it; in doing so, the timestamp
+ // will reflect time this is run (not container create time) because the conversion
+ // of the container create time to v1 Time is probably not warranted nor worthwhile.
+ CreationTimestamp: v12.Now(),
+ }
+ ps := v1.PodSpec{
+ Containers: containers,
+ }
+ p := v1.Pod{
+ TypeMeta: tm,
+ ObjectMeta: om,
+ Spec: ps,
+ }
+ return &p, nil
+}
+
+// containerToV1Container converts information we know about a libpod container
+// to a V1.Container specification.
+func containerToV1Container(c *Container) (v1.Container, error) {
+ kubeContainer := v1.Container{}
+ kubeSec, err := generateKubeSecurityContext(c)
+ if err != nil {
+ return kubeContainer, err
+ }
+
+ if len(c.config.Spec.Linux.Devices) > 0 {
+ // TODO Enable when we can support devices and their names
+ devices, err := generateKubeVolumeDeviceFromLinuxDevice(c.Spec().Linux.Devices)
+ if err != nil {
+ return kubeContainer, err
+ }
+ kubeContainer.VolumeDevices = devices
+ return kubeContainer, errors.Wrapf(ErrNotImplemented, "linux devices")
+ }
+
+ if len(c.config.UserVolumes) > 0 {
+ // TODO When we until we can resolve what the volume name should be, this is disabled
+ // Volume names need to be coordinated "globally" in the kube files.
+ volumes, err := libpodMountsToKubeVolumeMounts(c)
+ if err != nil {
+ return kubeContainer, err
+ }
+ kubeContainer.VolumeMounts = volumes
+ return kubeContainer, errors.Wrapf(ErrNotImplemented, "volume names")
+ }
+
+ envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env)
+ if err != nil {
+ return kubeContainer, nil
+ }
+
+ ports, err := ocicniPortMappingToContainerPort(c.PortMappings())
+ if err != nil {
+ return kubeContainer, nil
+ }
+
+ containerCommands := c.Command()
+ kubeContainer.Name = removeUnderscores(c.Name())
+
+ _, image := c.Image()
+ kubeContainer.Image = image
+ kubeContainer.Stdin = c.Stdin()
+ kubeContainer.Command = containerCommands
+ // TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint.
+ // right now we just take the container's command
+ //container.Args = args
+ kubeContainer.WorkingDir = c.WorkingDir()
+ kubeContainer.Ports = ports
+ // This should not be applicable
+ //container.EnvFromSource =
+ kubeContainer.Env = envVariables
+ // TODO enable resources when we can support naming conventions
+ //container.Resources
+ kubeContainer.SecurityContext = kubeSec
+ kubeContainer.StdinOnce = false
+ kubeContainer.TTY = c.config.Spec.Process.Terminal
+
+ return kubeContainer, nil
+}
+
+// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts
+// it to a v1.ContainerPort format for kube output
+func ocicniPortMappingToContainerPort(portMappings []ocicni.PortMapping) ([]v1.ContainerPort, error) {
+ var containerPorts []v1.ContainerPort
+ for _, p := range portMappings {
+ var protocol v1.Protocol
+ switch strings.ToUpper(p.Protocol) {
+ case "TCP":
+ protocol = v1.ProtocolTCP
+ case "UDP":
+ protocol = v1.ProtocolUDP
+ default:
+ return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol)
+ }
+ cp := v1.ContainerPort{
+ // Name will not be supported
+ HostPort: p.HostPort,
+ HostIP: p.HostIP,
+ ContainerPort: p.ContainerPort,
+ Protocol: protocol,
+ }
+ containerPorts = append(containerPorts, cp)
+ }
+ return containerPorts, nil
+}
+
+// libpodEnvVarsToKubeEnvVars converts a key=value string slice to []v1.EnvVar
+func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) {
+ var envVars []v1.EnvVar
+ for _, e := range envs {
+ splitE := strings.SplitN(e, "=", 2)
+ if len(splitE) != 2 {
+ return envVars, errors.Errorf("environment variable %s is malformed; should be key=value", e)
+ }
+ ev := v1.EnvVar{
+ Name: splitE[0],
+ Value: splitE[1],
+ }
+ envVars = append(envVars, ev)
+ }
+ return envVars, nil
+}
+
+// Is this worth it?
+func libpodMaxAndMinToResourceList(c *Container) (v1.ResourceList, v1.ResourceList) { //nolint
+ // It does not appear we can properly calculate CPU resources from the information
+ // we know in libpod. Libpod knows CPUs by time, shares, etc.
+
+ // We also only know about a memory limit; no memory minimum
+ maxResources := make(map[v1.ResourceName]resource.Quantity)
+ minResources := make(map[v1.ResourceName]resource.Quantity)
+ config := c.Config()
+ maxMem := config.Spec.Linux.Resources.Memory.Limit
+
+ _ = maxMem
+
+ return maxResources, minResources
+}
+
+func generateKubeVolumeMount(hostSourcePath string, mounts []specs.Mount) (v1.VolumeMount, error) {
+ vm := v1.VolumeMount{}
+ for _, m := range mounts {
+ if m.Source == hostSourcePath {
+ // TODO Name is not provided and is required by Kube; therefore, this is disabled earlier
+ //vm.Name =
+ vm.MountPath = m.Source
+ vm.SubPath = m.Destination
+ if util.StringInSlice("ro", m.Options) {
+ vm.ReadOnly = true
+ }
+ return vm, nil
+ }
+ }
+ return vm, errors.New("unable to find mount source")
+}
+
+// libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands
+func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, error) {
+ // At this point, I dont think we can distinguish between the default
+ // volume mounts and user added ones. For now, we pass them all.
+ var vms []v1.VolumeMount
+ for _, hostSourcePath := range c.config.UserVolumes {
+ vm, err := generateKubeVolumeMount(hostSourcePath, c.config.Spec.Mounts)
+ if err != nil {
+ return vms, err
+ }
+ vms = append(vms, vm)
+ }
+ return vms, nil
+}
+
+// generateKubeSecurityContext generates a securityContext based on the existing container
+func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) {
+ priv := c.Privileged()
+ ro := c.IsReadOnly()
+ allowPrivEscalation := !c.Spec().Process.NoNewPrivileges
+
+ // TODO enable use of capabilities when we can figure out how to extract cap-add|remove
+ //caps := v1.Capabilities{
+ // //Add: c.config.Spec.Process.Capabilities
+ //}
+ sc := v1.SecurityContext{
+ // TODO enable use of capabilities when we can figure out how to extract cap-add|remove
+ //Capabilities: &caps,
+ Privileged: &priv,
+ // TODO How do we know if selinux were passed into podman
+ //SELinuxOptions:
+ // RunAsNonRoot is an optional parameter; our first implementations should be root only; however
+ // I'm leaving this as a bread-crumb for later
+ //RunAsNonRoot: &nonRoot,
+ ReadOnlyRootFilesystem: &ro,
+ AllowPrivilegeEscalation: &allowPrivEscalation,
+ }
+
+ if c.User() != "" {
+ // It is *possible* that
+ logrus.Debug("Looking in container for user: %s", c.User())
+ u, err := lookup.GetUser(c.state.Mountpoint, c.User())
+ if err != nil {
+ return nil, err
+ }
+ user := int64(u.Uid)
+ sc.RunAsUser = &user
+ }
+ return &sc, nil
+}
+
+// generateKubeVolumeDeviceFromLinuxDevice takes a list of devices and makes a VolumeDevice struct for kube
+func generateKubeVolumeDeviceFromLinuxDevice(devices []specs.LinuxDevice) ([]v1.VolumeDevice, error) {
+ var volumeDevices []v1.VolumeDevice
+ for _, d := range devices {
+ vd := v1.VolumeDevice{
+ // TBD How are we going to sync up these names
+ //Name:
+ DevicePath: d.Path,
+ }
+ volumeDevices = append(volumeDevices, vd)
+ }
+ return volumeDevices, nil
+}
+
+func removeUnderscores(s string) string {
+ return strings.Replace(s, "_", "", -1)
+}
diff --git a/libpod/options.go b/libpod/options.go
index 8d044313b..507847d65 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -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
+ }
+}
diff --git a/libpod/pod.go b/libpod/pod.go
index 8ac976f6a..07f41f5c6 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -4,6 +4,7 @@ import (
"time"
"github.com/containers/storage"
+ "github.com/cri-o/ocicni/pkg/ocicni"
"github.com/pkg/errors"
)
@@ -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
diff --git a/libpod/pod_easyjson.go b/libpod/pod_easyjson.go
index 6c1c939f3..8ea9a5e72 100644
--- a/libpod/pod_easyjson.go
+++ b/libpod/pod_easyjson.go
@@ -6,6 +6,7 @@ package libpod
import (
json "encoding/json"
+ ocicni "github.com/cri-o/ocicni/pkg/ocicni"
easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter"
@@ -721,6 +722,29 @@ func easyjsonBe091417DecodeGithubComContainersLibpodLibpod5(in *jlexer.Lexer, ou
switch key {
case "makeInfraContainer":
out.HasInfraContainer = bool(in.Bool())
+ case "infraPortBindings":
+ if in.IsNull() {
+ in.Skip()
+ out.PortBindings = nil
+ } else {
+ in.Delim('[')
+ if out.PortBindings == nil {
+ if !in.IsDelim(']') {
+ out.PortBindings = make([]ocicni.PortMapping, 0, 1)
+ } else {
+ out.PortBindings = []ocicni.PortMapping{}
+ }
+ } else {
+ out.PortBindings = (out.PortBindings)[:0]
+ }
+ for !in.IsDelim(']') {
+ var v6 ocicni.PortMapping
+ easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in, &v6)
+ out.PortBindings = append(out.PortBindings, v6)
+ in.WantComma()
+ }
+ in.Delim(']')
+ }
default:
in.SkipRecursive()
}
@@ -745,5 +769,109 @@ func easyjsonBe091417EncodeGithubComContainersLibpodLibpod5(out *jwriter.Writer,
}
out.Bool(bool(in.HasInfraContainer))
}
+ {
+ const prefix string = ",\"infraPortBindings\":"
+ if first {
+ first = false
+ out.RawString(prefix[1:])
+ } else {
+ out.RawString(prefix)
+ }
+ if in.PortBindings == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
+ out.RawString("null")
+ } else {
+ out.RawByte('[')
+ for v7, v8 := range in.PortBindings {
+ if v7 > 0 {
+ out.RawByte(',')
+ }
+ easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out, v8)
+ }
+ out.RawByte(']')
+ }
+ }
+ out.RawByte('}')
+}
+func easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in *jlexer.Lexer, out *ocicni.PortMapping) {
+ isTopLevel := in.IsStart()
+ if in.IsNull() {
+ if isTopLevel {
+ in.Consumed()
+ }
+ in.Skip()
+ return
+ }
+ in.Delim('{')
+ for !in.IsDelim('}') {
+ key := in.UnsafeString()
+ in.WantColon()
+ if in.IsNull() {
+ in.Skip()
+ in.WantComma()
+ continue
+ }
+ switch key {
+ case "hostPort":
+ out.HostPort = int32(in.Int32())
+ case "containerPort":
+ out.ContainerPort = int32(in.Int32())
+ case "protocol":
+ out.Protocol = string(in.String())
+ case "hostIP":
+ out.HostIP = string(in.String())
+ default:
+ in.SkipRecursive()
+ }
+ in.WantComma()
+ }
+ in.Delim('}')
+ if isTopLevel {
+ in.Consumed()
+ }
+}
+func easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out *jwriter.Writer, in ocicni.PortMapping) {
+ out.RawByte('{')
+ first := true
+ _ = first
+ {
+ const prefix string = ",\"hostPort\":"
+ if first {
+ first = false
+ out.RawString(prefix[1:])
+ } else {
+ out.RawString(prefix)
+ }
+ out.Int32(int32(in.HostPort))
+ }
+ {
+ const prefix string = ",\"containerPort\":"
+ if first {
+ first = false
+ out.RawString(prefix[1:])
+ } else {
+ out.RawString(prefix)
+ }
+ out.Int32(int32(in.ContainerPort))
+ }
+ {
+ const prefix string = ",\"protocol\":"
+ if first {
+ first = false
+ out.RawString(prefix[1:])
+ } else {
+ out.RawString(prefix)
+ }
+ out.String(string(in.Protocol))
+ }
+ {
+ const prefix string = ",\"hostIP\":"
+ if first {
+ first = false
+ out.RawString(prefix[1:])
+ } else {
+ out.RawString(prefix)
+ }
+ out.String(string(in.HostIP))
+ }
out.RawByte('}')
}
diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go
index fea79e994..450a2fb32 100644
--- a/libpod/runtime_pod_infra_linux.go
+++ b/libpod/runtime_pod_infra_linux.go
@@ -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"
)
@@ -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...)
}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 6ac9d82da..6a0642ee7 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -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 {
diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go
index 51522ffd1..5abf9613b 100644
--- a/test/e2e/pod_create_test.go
+++ b/test/e2e/pod_create_test.go
@@ -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))
+ })
})