summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common.go4
-rw-r--r--completions/bash/podman1
-rw-r--r--docs/podman-create.1.md4
-rw-r--r--docs/podman-run.1.md4
-rw-r--r--libpod/container.go8
-rw-r--r--libpod/container_easyjson.go14
-rw-r--r--libpod/networking_linux.go19
-rw-r--r--libpod/options.go25
-rw-r--r--libpod/pod_easyjson.go2
-rw-r--r--pkg/spec/createconfig.go13
-rw-r--r--test/e2e/run_staticip_test.go58
-rw-r--r--vendor.conf2
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go41
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go17
14 files changed, 189 insertions, 23 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 9ab0e57e5..c1e15e2fb 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -249,6 +249,10 @@ var createFlags = []cli.Flag{
Usage: "Keep STDIN open even if not attached",
},
cli.StringFlag{
+ Name: "ip",
+ Usage: "Specify a static IPv4 address for the container",
+ },
+ cli.StringFlag{
Name: "ipc",
Usage: "IPC namespace to use",
},
diff --git a/completions/bash/podman b/completions/bash/podman
index 604a25f5d..5cfed348f 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1512,6 +1512,7 @@ _podman_container_run() {
--hostname -h
--image-volume
--init-path
+ --ip
--ipc
--kernel-memory
--label-file
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index 167fce5b9..509d8820f 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -286,7 +286,9 @@ Not implemented
**--ip**=""
-Not implemented
+Specify a static IP address for the container, for example '10.88.64.128'.
+Can only be used if no additional CNI networks to join were specified via '--network=<network-name>', and if the container is not joining another container's network namespace via '--network=container:<name|id>'.
+The address must be within the default CNI network's pool (default 10.88.0.0/16).
**--ipc**=""
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index b70396a6d..c303492e7 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -297,7 +297,9 @@ Not implemented
**--ip**=""
-Not implemented
+Specify a static IP address for the container, for example '10.88.64.128'.
+Can only be used if no additional CNI networks to join were specified via '--network=<network-name>', and if the container is not joining another container's network namespace via '--network=container:<name|id>'.
+The address must be within the default CNI network's pool (default 10.88.0.0/16).
**--ipc**=""
diff --git a/libpod/container.go b/libpod/container.go
index 55a0f3a2c..5997c0b66 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -269,9 +269,13 @@ type ContainerConfig struct {
// Network Config
// CreateNetNS indicates that libpod should create and configure a new
- // network namespace for the container
- // This cannot be set if NetNsCtr is also set
+ // network namespace for the container.
+ // This cannot be set if NetNsCtr is also set.
CreateNetNS bool `json:"createNetNS"`
+ // StaticIP is a static IP to request for the container.
+ // This cannot be set unless CreateNetNS is set.
+ // If not set, the container will be dynamically assigned an IP by CNI.
+ StaticIP net.IP `json:"staticIP"`
// PortMappings are the ports forwarded to the container's network
// namespace
// These are not used unless CreateNetNS is true
diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go
index 916118aec..f78366065 100644
--- a/libpod/container_easyjson.go
+++ b/libpod/container_easyjson.go
@@ -1383,6 +1383,10 @@ func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, ou
}
case "createNetNS":
out.CreateNetNS = bool(in.Bool())
+ case "staticIP":
+ if data := in.UnsafeBytes(); in.Ok() {
+ in.AddError((out.StaticIP).UnmarshalText(data))
+ }
case "portMappings":
if in.IsNull() {
in.Skip()
@@ -2005,6 +2009,16 @@ func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer,
}
out.Bool(bool(in.CreateNetNS))
}
+ {
+ const prefix string = ",\"staticIP\":"
+ if first {
+ first = false
+ out.RawString(prefix[1:])
+ } else {
+ out.RawString(prefix)
+ }
+ out.RawText((in.StaticIP).MarshalText())
+ }
if len(in.PortMappings) != 0 {
const prefix string = ",\"portMappings\":"
if first {
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 17e79aa62..acb4e2a90 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -5,6 +5,7 @@ package libpod
import (
"crypto/rand"
"fmt"
+ "net"
"os"
"os/exec"
"path/filepath"
@@ -25,8 +26,8 @@ import (
)
// Get an OCICNI network config
-func getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping) ocicni.PodNetwork {
- return ocicni.PodNetwork{
+func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP) ocicni.PodNetwork {
+ network := ocicni.PodNetwork{
Name: name,
Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces
ID: id,
@@ -34,11 +35,21 @@ func getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.Po
PortMappings: ports,
Networks: networks,
}
+
+ if staticIP != nil {
+ defaultNetwork := r.netPlugin.GetDefaultNetworkName()
+
+ network.Networks = []string{defaultNetwork}
+ network.NetworkConfig = make(map[string]ocicni.NetworkConfig)
+ network.NetworkConfig[defaultNetwork] = ocicni.NetworkConfig{IP: staticIP.String()}
+ }
+
+ return network
}
// Create and configure a new network namespace for a container
func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) {
- podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings)
+ podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP)
results, err := r.netPlugin.SetUpPod(podNetwork)
if err != nil {
@@ -216,7 +227,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error {
logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())
- podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings)
+ podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP)
// The network may have already been torn down, so don't fail here, just log
if err := r.netPlugin.TearDownPod(podNetwork); err != nil {
diff --git a/libpod/options.go b/libpod/options.go
index 977f3f4c2..9f966cead 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -828,6 +828,31 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netwo
}
}
+// WithStaticIP indicates that the container should request a static IP from
+// the CNI plugins.
+// It cannot be set unless WithNetNS has already been passed.
+// Further, it cannot be set if additional CNI networks to join have been
+// specified.
+func WithStaticIP(ip net.IP) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return ErrCtrFinalized
+ }
+
+ if !ctr.config.CreateNetNS {
+ return errors.Wrapf(ErrInvalidArg, "cannot set a static IP if the container is not creating a network namespace")
+ }
+
+ if len(ctr.config.Networks) != 0 {
+ return errors.Wrapf(ErrInvalidArg, "cannot set a static IP if joining additional CNI networks")
+ }
+
+ ctr.config.StaticIP = ip
+
+ return nil
+ }
+}
+
// WithLogPath sets the path to the log file.
func WithLogPath(path string) CtrCreateOption {
return func(ctr *Container) error {
diff --git a/libpod/pod_easyjson.go b/libpod/pod_easyjson.go
index 2891e51f2..6c1c939f3 100644
--- a/libpod/pod_easyjson.go
+++ b/libpod/pod_easyjson.go
@@ -1,3 +1,5 @@
+// +build seccomp ostree selinux varlink exclude_graphdriver_devicemapper
+
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package libpod
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 887ef8e95..e9a5dc9dc 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -2,6 +2,7 @@ package createconfig
import (
"encoding/json"
+ "net"
"os"
"strconv"
"strings"
@@ -311,9 +312,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
var pod *libpod.Pod
var err error
- // Uncomment after talking to mheon about unimplemented funcs
- // options = append(options, libpod.WithLabels(c.labels))
-
if c.Interactive {
options = append(options, libpod.WithStdin())
}
@@ -442,6 +440,15 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
if logPath != "" {
options = append(options, libpod.WithLogPath(logPath))
}
+ if c.IPAddress != "" {
+ ip := net.ParseIP(c.IPAddress)
+ if ip == nil {
+ return nil, errors.Wrapf(libpod.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress)
+ } else if ip.To4() == nil {
+ return nil, errors.Wrapf(libpod.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress)
+ }
+ options = append(options, libpod.WithStaticIP(ip))
+ }
options = append(options, libpod.WithPrivileged(c.Privileged))
diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go
new file mode 100644
index 000000000..b69d15cee
--- /dev/null
+++ b/test/e2e/run_staticip_test.go
@@ -0,0 +1,58 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman run with --ip flag", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest PodmanTest
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+ })
+
+ It("Podman run --ip with garbage address", func() {
+ result := podmanTest.Podman([]string{"run", "-ti", "--ip", "114232346", ALPINE, "ls"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("Podman run --ip with v6 address", func() {
+ result := podmanTest.Podman([]string{"run", "-ti", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("Podman run --ip with non-allocatable IP", func() {
+ result := podmanTest.Podman([]string{"run", "-ti", "--ip", "203.0.113.124", ALPINE, "ls"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("Podman run with specified static IP has correct IP", func() {
+ result := podmanTest.Podman([]string{"run", "-ti", "--ip", "10.88.64.128", ALPINE, "ip", "addr"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(result.OutputToString()).To(ContainSubstring("10.88.64.128/16"))
+ })
+})
diff --git a/vendor.conf b/vendor.conf
index d65014992..75b92e846 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -14,7 +14,7 @@ github.com/containers/image 918dbb93e6e099b196b498c38d079f6bb924d0c8
github.com/containers/storage 41294c85d97bef688e18f710402895dbecde3308
github.com/containers/psgo 5dde6da0bc8831b35243a847625bcf18183bd1ee
github.com/coreos/go-systemd v14
-github.com/cri-o/ocicni master
+github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c
github.com/cyphar/filepath-securejoin v0.2.1
github.com/davecgh/go-spew v1.1.0
github.com/docker/distribution 7a8efe719e55bbfaff7bc5718cdf0ed51ca821df
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
index 33a3ae063..dfc216389 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
@@ -3,6 +3,7 @@ package ocicni
import (
"errors"
"fmt"
+ "net"
"os"
"path"
"sort"
@@ -351,14 +352,14 @@ func (plugin *cniNetworkPlugin) getNetwork(name string) (*cniNetwork, error) {
return net, nil
}
-func (plugin *cniNetworkPlugin) getDefaultNetworkName() string {
+func (plugin *cniNetworkPlugin) GetDefaultNetworkName() string {
plugin.RLock()
defer plugin.RUnlock()
return plugin.defaultNetName
}
func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {
- defaultNetName := plugin.getDefaultNetworkName()
+ defaultNetName := plugin.GetDefaultNetworkName()
if defaultNetName == "" {
return nil
}
@@ -383,7 +384,7 @@ func (plugin *cniNetworkPlugin) Name() string {
func (plugin *cniNetworkPlugin) forEachNetwork(podNetwork *PodNetwork, forEachFunc func(*cniNetwork, string, *PodNetwork) error) error {
networks := podNetwork.Networks
if len(networks) == 0 {
- networks = append(networks, plugin.getDefaultNetworkName())
+ networks = append(networks, plugin.GetDefaultNetworkName())
}
for i, netName := range networks {
// Interface names start at "eth0" and count up for each network
@@ -408,7 +409,7 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu
plugin.podLock(podNetwork).Lock()
defer plugin.podUnlock(podNetwork)
- _, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo")
+ _, err := plugin.loNetwork.addToNetwork(plugin.cacheDir, &podNetwork, "lo", "")
if err != nil {
logrus.Errorf("Error while adding to cni lo network: %s", err)
return nil, err
@@ -416,7 +417,12 @@ func (plugin *cniNetworkPlugin) SetUpPod(podNetwork PodNetwork) ([]cnitypes.Resu
results := make([]cnitypes.Result, 0)
if err := plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error {
- result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName)
+ ip := ""
+ if conf, ok := podNetwork.NetworkConfig[network.name]; ok {
+ ip = conf.IP
+ }
+
+ result, err := network.addToNetwork(plugin.cacheDir, podNetwork, ifName, ip)
if err != nil {
logrus.Errorf("Error while adding pod to CNI network %q: %s", network.name, err)
return err
@@ -439,7 +445,12 @@ func (plugin *cniNetworkPlugin) TearDownPod(podNetwork PodNetwork) error {
defer plugin.podUnlock(podNetwork)
return plugin.forEachNetwork(&podNetwork, func(network *cniNetwork, ifName string, podNetwork *PodNetwork) error {
- if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName); err != nil {
+ ip := ""
+ if conf, ok := podNetwork.NetworkConfig[network.name]; ok {
+ ip = conf.IP
+ }
+
+ if err := network.deleteFromNetwork(plugin.cacheDir, podNetwork, ifName, ip); err != nil {
logrus.Errorf("Error while removing pod from CNI network %q: %s", network.name, err)
return err
}
@@ -491,8 +502,8 @@ func (plugin *cniNetworkPlugin) GetPodNetworkStatus(podNetwork PodNetwork) ([]cn
return results, nil
}
-func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) (cnitypes.Result, error) {
- rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName)
+func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (cnitypes.Result, error) {
+ rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip)
if err != nil {
logrus.Errorf("Error adding network: %v", err)
return nil, err
@@ -509,8 +520,8 @@ func (network *cniNetwork) addToNetwork(cacheDir string, podNetwork *PodNetwork,
return res, nil
}
-func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName string) error {
- rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName)
+func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNetwork, ifName, ip string) error {
+ rt, err := buildCNIRuntimeConf(cacheDir, podNetwork, ifName, ip)
if err != nil {
logrus.Errorf("Error deleting network: %v", err)
return err
@@ -526,7 +537,7 @@ func (network *cniNetwork) deleteFromNetwork(cacheDir string, podNetwork *PodNet
return nil
}
-func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string) (*libcni.RuntimeConf, error) {
+func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName, ip string) (*libcni.RuntimeConf, error) {
logrus.Infof("Got pod network %+v", podNetwork)
rt := &libcni.RuntimeConf{
@@ -542,6 +553,14 @@ func buildCNIRuntimeConf(cacheDir string, podNetwork *PodNetwork, ifName string)
},
}
+ // Add requested static IP to CNI_ARGS
+ if ip != "" {
+ if tstIP := net.ParseIP(ip); tstIP == nil {
+ return nil, fmt.Errorf("unable to parse IP address %q", ip)
+ }
+ rt.Args = append(rt.Args, [2]string{"IP", ip})
+ }
+
if len(podNetwork.PortMappings) == 0 {
return rt, nil
}
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
index 8ca61657a..d76094292 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/types.go
@@ -24,6 +24,14 @@ type PortMapping struct {
HostIP string `json:"hostIP"`
}
+// NetworkConfig is additional configuration for a single CNI network.
+type NetworkConfig struct {
+ // IP is a static IP to be specified in the network. Can only be used
+ // with the hostlocal IP allocator. If left unset, an IP will be
+ // dynamically allocated.
+ IP string
+}
+
// PodNetwork configures the network of a pod sandbox.
type PodNetwork struct {
// Name is the name of the sandbox.
@@ -40,6 +48,11 @@ type PodNetwork struct {
// Networks is a list of CNI network names to attach to the sandbox
// Leave this list empty to attach the default network to the sandbox
Networks []string
+
+ // NetworkConfig is configuration specific to a single CNI network.
+ // It is optional, and can be omitted for some or all specified networks
+ // without issue.
+ NetworkConfig map[string]NetworkConfig
}
// CNIPlugin is the interface that needs to be implemented by a plugin
@@ -48,6 +61,10 @@ type CNIPlugin interface {
// for a plugin by name, e.g.
Name() string
+ // GetDefaultNetworkName returns the name of the plugin's default
+ // network.
+ GetDefaultNetworkName() string
+
// SetUpPod is the method called after the sandbox container of
// the pod has been created but before the other containers of the
// pod are launched.