1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Allow user to choose the IP address for the container

Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
Alessandro Boch 2016-01-07 16:18:34 -08:00
parent 19b063e740
commit 2bb3fc1bc5
26 changed files with 412 additions and 157 deletions

View file

@ -13,6 +13,7 @@ import (
"github.com/docker/engine-api/client" "github.com/docker/engine-api/client"
"github.com/docker/engine-api/types" "github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
) )
func (cli *DockerCli) pullImage(image string) error { func (cli *DockerCli) pullImage(image string) error {
@ -79,7 +80,7 @@ func newCIDFile(path string) (*cidFile, error) {
return &cidFile{path: path, file: f}, nil return &cidFile{path: path, file: f}, nil
} }
func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) { func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
var containerIDFile *cidFile var containerIDFile *cidFile
if cidfile != "" { if cidfile != "" {
var err error var err error
@ -107,7 +108,8 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
} }
//create the container //create the container
response, err := cli.client.ContainerCreate(config, hostConfig, nil, name) response, err := cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
//if image not found try to pull it //if image not found try to pull it
if err != nil { if err != nil {
if client.IsErrImageNotFound(err) { if client.IsErrImageNotFound(err) {
@ -124,7 +126,7 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
} }
// Retry // Retry
var retryErr error var retryErr error
response, retryErr = cli.client.ContainerCreate(config, hostConfig, nil, name) response, retryErr = cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
if retryErr != nil { if retryErr != nil {
return nil, retryErr return nil, retryErr
} }
@ -156,7 +158,8 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
) )
config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args) config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
if err != nil { if err != nil {
cmd.ReportError(err.Error(), true) cmd.ReportError(err.Error(), true)
os.Exit(1) os.Exit(1)
@ -165,7 +168,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
cmd.Usage() cmd.Usage()
return nil return nil
} }
response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) response, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil { if err != nil {
return err return err
} }

View file

@ -107,15 +107,22 @@ func (cli *DockerCli) CmdNetworkRm(args ...string) error {
// CmdNetworkConnect connects a container to a network // CmdNetworkConnect connects a container to a network
// //
// Usage: docker network connect <NETWORK> <CONTAINER> // Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
func (cli *DockerCli) CmdNetworkConnect(args ...string) error { func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false) cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
cmd.Require(flag.Exact, 2) flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
cmd.Require(flag.Min, 2)
if err := cmd.ParseFlags(args, true); err != nil { if err := cmd.ParseFlags(args, true); err != nil {
return err return err
} }
epConfig := &network.EndpointSettings{
return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), nil) IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: *flIPAddress,
IPv6Address: *flIPv6Address,
},
}
return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
} }
// CmdNetworkDisconnect disconnects a container from a network // CmdNetworkDisconnect disconnects a container from a network

View file

@ -82,7 +82,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
) )
config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args) config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
// just in case the Parse does not exit // just in case the Parse does not exit
if err != nil { if err != nil {
cmd.ReportError(err.Error(), true) cmd.ReportError(err.Error(), true)
@ -145,7 +146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize() hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
} }
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
if err != nil { if err != nil {
cmd.ReportError(err.Error(), true) cmd.ReportError(err.Error(), true)
return runStartContainerErr(err) return runStartContainerErr(err)

View file

@ -332,7 +332,7 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
return err return err
} }
_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) _, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil { if err != nil {
return err return err
} }
@ -358,7 +358,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
name := r.Form.Get("name") name := r.Form.Get("name")
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil { if err != nil {
return err return err
} }
@ -369,6 +369,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
Name: name, Name: name,
Config: config, Config: config,
HostConfig: hostConfig, HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
AdjustCPUShares: adjustCPUShares, AdjustCPUShares: adjustCPUShares,
}) })
if err != nil { if err != nil {

View file

@ -39,7 +39,7 @@ func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.
pause = true pause = true
} }
c, _, err := runconfig.DecodeContainerConfig(r.Body) c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil && err != io.EOF { //Do not fail if body is empty. if err != nil && err != io.EOF { //Do not fail if body is empty.
return err return err
} }

View file

@ -2,7 +2,6 @@ package network
import ( import (
"github.com/docker/engine-api/types/network" "github.com/docker/engine-api/types/network"
"github.com/docker/libnetwork" "github.com/docker/libnetwork"
) )
@ -15,7 +14,7 @@ type Backend interface {
GetAllNetworks() []libnetwork.Network GetAllNetworks() []libnetwork.Network
CreateNetwork(name, driver string, ipam network.IPAM, CreateNetwork(name, driver string, ipam network.IPAM,
options map[string]string) (libnetwork.Network, error) options map[string]string) (libnetwork.Network, error)
ConnectContainerToNetwork(containerName, networkName string) error ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
DisconnectContainerFromNetwork(containerName string, DisconnectContainerFromNetwork(containerName string,
network libnetwork.Network) error network libnetwork.Network) error
NetworkControllerEnabled() bool NetworkControllerEnabled() bool

View file

@ -122,7 +122,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
return err return err
} }
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name()) return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
} }
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View file

@ -261,6 +261,14 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]
createOptions = append(createOptions, libnetwork.CreateOptionAnonymous()) createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
} }
if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
ipam := epConfig.IPAMConfig
if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
createOptions = append(createOptions,
libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
}
}
// Other configs are applicable only for the endpoint in the network // Other configs are applicable only for the endpoint in the network
// to which container was connected to on docker run. // to which container was connected to on docker run.
if n.Name() != container.HostConfig.NetworkMode.NetworkName() && if n.Name() != container.HostConfig.NetworkMode.NetworkName() &&

View file

@ -503,7 +503,10 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
return runconfig.ErrConflictNoNetwork return runconfig.ErrConflictNoNetwork
} }
} }
if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings) container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
}
return nil return nil
} }
@ -562,7 +565,12 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
} }
// updateContainerNetworkSettings update the network settings // updateContainerNetworkSettings update the network settings
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error { func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
var (
n libnetwork.Network
err error
)
mode := container.HostConfig.NetworkMode mode := container.HostConfig.NetworkMode
if container.Config.NetworkDisabled || mode.IsContainer() { if container.Config.NetworkDisabled || mode.IsContainer() {
return nil return nil
@ -573,14 +581,35 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
networkName = daemon.netController.Config().Daemon.DefaultNetwork networkName = daemon.netController.Config().Daemon.DefaultNetwork
} }
if mode.IsUserDefined() { if mode.IsUserDefined() {
n, err := daemon.FindNetwork(networkName) n, err = daemon.FindNetwork(networkName)
if err != nil { if err != nil {
return err return err
} }
networkName = n.Name() networkName = n.Name()
} }
if container.NetworkSettings == nil {
container.NetworkSettings = &network.Settings{}
}
if endpointsConfig != nil {
container.NetworkSettings.Networks = endpointsConfig
}
if container.NetworkSettings.Networks == nil {
container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings) container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings) container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
}
if !mode.IsUserDefined() {
return nil
}
// Make sure to internally store the per network endpoint config by network name
if _, ok := container.NetworkSettings.Networks[networkName]; ok {
return nil
}
if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
container.NetworkSettings.Networks[networkName] = nwConfig
delete(container.NetworkSettings.Networks, n.ID())
return nil
}
return nil return nil
} }
@ -598,15 +627,15 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
return nil return nil
} }
err := daemon.updateContainerNetworkSettings(container) err := daemon.updateContainerNetworkSettings(container, nil)
if err != nil { if err != nil {
return err return err
} }
updateSettings = true updateSettings = true
} }
for n := range container.NetworkSettings.Networks { for n, nConf := range container.NetworkSettings.Networks {
if err := daemon.connectToNetwork(container, n, updateSettings); err != nil { if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
return err return err
} }
} }
@ -626,12 +655,65 @@ func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwo
return sb return sb
} }
// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration
func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool {
return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0)
}
// User specified ip address is acceptable only for networks with user specified subnets.
func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
if !hasUserDefinedIPAddress(epConfig) {
return nil
}
_, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig()
for _, s := range []struct {
ipConfigured bool
subnetConfigs []*libnetwork.IpamConf
}{
{
ipConfigured: len(epConfig.IPAMConfig.IPv4Address) > 0,
subnetConfigs: nwIPv4Configs,
},
{
ipConfigured: len(epConfig.IPAMConfig.IPv6Address) > 0,
subnetConfigs: nwIPv6Configs,
},
} {
if s.ipConfigured {
foundSubnet := false
for _, cfg := range s.subnetConfigs {
if len(cfg.PreferredPool) > 0 {
foundSubnet = true
break
}
}
if !foundSubnet {
return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
}
}
}
return nil
}
// cleanOperationalData resets the operational data from the passed endpoint settings
func cleanOperationalData(es *networktypes.EndpointSettings) {
es.EndpointID = ""
es.Gateway = ""
es.IPAddress = ""
es.IPPrefixLen = 0
es.IPv6Gateway = ""
es.GlobalIPv6Address = ""
es.GlobalIPv6PrefixLen = 0
es.MacAddress = ""
}
// ConnectToNetwork connects a container to a network // ConnectToNetwork connects a container to a network
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error { func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
if !container.Running { if !container.Running {
return derr.ErrorCodeNotRunning.WithArgs(container.ID) return derr.ErrorCodeNotRunning.WithArgs(container.ID)
} }
if err := daemon.connectToNetwork(container, idOrName, true); err != nil { if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
return err return err
} }
if err := container.ToDiskLocking(); err != nil { if err := container.ToDiskLocking(); err != nil {
@ -640,11 +722,15 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
return nil return nil
} }
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, updateSettings bool) (err error) { func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
if container.HostConfig.NetworkMode.IsContainer() { if container.HostConfig.NetworkMode.IsContainer() {
return runconfig.ErrConflictSharedNetwork return runconfig.ErrConflictSharedNetwork
} }
if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
return runconfig.ErrUnsupportedNetworkAndIP
}
if containertypes.NetworkMode(idOrName).IsBridge() && if containertypes.NetworkMode(idOrName).IsBridge() &&
daemon.configStore.DisableBridge { daemon.configStore.DisableBridge {
container.Config.NetworkDisabled = true container.Config.NetworkDisabled = true
@ -658,12 +744,20 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
return err return err
} }
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
return err
}
if updateSettings { if updateSettings {
if err := daemon.updateNetworkSettings(container, n); err != nil { if err := daemon.updateNetworkSettings(container, n); err != nil {
return err return err
} }
} }
if endpointConfig != nil {
container.NetworkSettings.Networks[n.Name()] = endpointConfig
}
ep, err := container.GetEndpointInNetwork(n) ep, err := container.GetEndpointInNetwork(n)
if err == nil { if err == nil {
return fmt.Errorf("Conflict. A container with name %q is already connected to network %s.", strings.TrimPrefix(container.Name, "/"), idOrName) return fmt.Errorf("Conflict. A container with name %q is already connected to network %s.", strings.TrimPrefix(container.Name, "/"), idOrName)
@ -869,18 +963,16 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
sid := container.NetworkSettings.SandboxID sid := container.NetworkSettings.SandboxID
settings := container.NetworkSettings.Networks settings := container.NetworkSettings.Networks
if sid == "" || len(settings) == 0 {
return
}
var networks []libnetwork.Network var networks []libnetwork.Network
for n := range settings { for n, epSettings := range settings {
if nw, err := daemon.FindNetwork(n); err == nil { if nw, err := daemon.FindNetwork(n); err == nil {
networks = append(networks, nw) networks = append(networks, nw)
} }
settings[n] = &networktypes.EndpointSettings{} cleanOperationalData(epSettings)
}
container.NetworkSettings = &network.Settings{Networks: settings}
if sid == "" || len(settings) == 0 {
return
} }
sb, err := daemon.netController.SandboxByID(sid) sb, err := daemon.netController.SandboxByID(sid)

View file

@ -10,6 +10,7 @@ import (
"github.com/docker/docker/daemon/execdriver/windows" "github.com/docker/docker/daemon/execdriver/windows"
derr "github.com/docker/docker/errors" derr "github.com/docker/docker/errors"
"github.com/docker/docker/layer" "github.com/docker/docker/layer"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/libnetwork" "github.com/docker/libnetwork"
) )
@ -18,7 +19,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
} }
// updateContainerNetworkSettings update the network settings // updateContainerNetworkSettings update the network settings
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error { func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
return nil return nil
} }
@ -27,7 +28,7 @@ func (daemon *Daemon) initializeNetworking(container *container.Container) error
} }
// ConnectToNetwork connects a container to the network // ConnectToNetwork connects a container to the network
func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error { func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
return nil return nil
} }

View file

@ -11,6 +11,7 @@ import (
volumestore "github.com/docker/docker/volume/store" volumestore "github.com/docker/docker/volume/store"
"github.com/docker/engine-api/types" "github.com/docker/engine-api/types"
containertypes "github.com/docker/engine-api/types/container" containertypes "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/opencontainers/runc/libcontainer/label" "github.com/opencontainers/runc/libcontainer/label"
) )
@ -108,7 +109,12 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe
return nil, err return nil, err
} }
if err := daemon.updateContainerNetworkSettings(container); err != nil { var endpointsConfigs map[string]*networktypes.EndpointSettings
if params.NetworkingConfig != nil {
endpointsConfigs = params.NetworkingConfig.EndpointsConfig
}
if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
return nil, err return nil, err
} }

View file

@ -150,12 +150,12 @@ func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnet
// ConnectContainerToNetwork connects the given container to the given // ConnectContainerToNetwork connects the given container to the given
// network. If either cannot be found, an err is returned. If the // network. If either cannot be found, an err is returned. If the
// network cannot be set up, an err is returned. // network cannot be set up, an err is returned.
func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string) error { func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error {
container, err := daemon.GetContainer(containerName) container, err := daemon.GetContainer(containerName)
if err != nil { if err != nil {
return err return err
} }
return daemon.ConnectToNetwork(container, networkName) return daemon.ConnectToNetwork(container, networkName, endpointConfig)
} }
// DisconnectContainerFromNetwork disconnects the given container from // DisconnectContainerFromNetwork disconnects the given container from

View file

@ -110,6 +110,8 @@ This section lists each version from latest to oldest. Each listing includes a
* `POST /containers/create` now allows you to set a read/write rate limit for a * `POST /containers/create` now allows you to set a read/write rate limit for a
device (in bytes per second or IO per second). device (in bytes per second or IO per second).
* `GET /networks` now supports filtering by `name`, `id` and `type`. * `GET /networks` now supports filtering by `name`, `id` and `type`.
* `POST /containers/create` now allows you to set the static IPv4 and/or IPv6 address for the container.
* `POST /networks/(id)/connect` now allows you to set the static IPv4 and/or IPv6 address for the container.
### v1.21 API changes ### v1.21 API changes

View file

@ -3031,7 +3031,13 @@ POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/
Content-Type: application/json Content-Type: application/json
{ {
"Container":"3613f73ba0e4" "Container":"3613f73ba0e4",
"endpoint_config": {
"test_nw": {
"IPv4Address":"172.24.56.89",
"IPv6Address":"2001:db8::5689"
}
}
} }
``` ```

View file

@ -30,11 +30,18 @@ You can also use the `docker run --net=<network-name>` option to start a contain
$ docker run -itd --net=multi-host-network busybox $ docker run -itd --net=multi-host-network busybox
``` ```
You can specify the IP address you want to be assigned to the container's interface.
```bash
$ docker network connect multi-host-network --ip 10.10.36.122 container2
```
You can pause, restart, and stop containers that are connected to a network. You can pause, restart, and stop containers that are connected to a network.
Paused containers remain connected and a revealed by a `network inspect`. When Paused containers remain connected and a revealed by a `network inspect`. When
the container is stopped, it does not appear on the network until you restart the container is stopped, it does not appear on the network until you restart
it. The container's IP address is not guaranteed to remain the same when a it. The container's IP address is not guaranteed to remain the same when a
stopped container rejoins the network. stopped container rejoins the network, unless you specified one when you run
`docker network connect` command.
To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network. To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network.

View file

@ -56,6 +56,8 @@ parent = "smn_cli"
--log-opt=[] Log driver specific options --log-opt=[] Log driver specific options
-m, --memory="" Memory limit -m, --memory="" Memory limit
--mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33) --mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33)
--ip="" Container IPv4 address (e.g. 172.30.100.104)
--ip6="" Container IPv6 address (e.g. 2001:db8::33)
--memory-reservation="" Memory soft limit --memory-reservation="" Memory soft limit
--memory-swap="" A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap. --memory-swap="" A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap.
--memory-swappiness="" Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. --memory-swappiness="" Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.

View file

@ -275,6 +275,8 @@ of the containers.
'<network-name>|<network-id>': connect to a user-defined network '<network-name>|<network-id>': connect to a user-defined network
--add-host="" : Add a line to /etc/hosts (host:IP) --add-host="" : Add a line to /etc/hosts (host:IP)
--mac-address="" : Sets the container's Ethernet device's MAC address --mac-address="" : Sets the container's Ethernet device's MAC address
--ip="" : Sets the container's Ethernet device's IPv4 address
--ip6="" : Sets the container's Ethernet device's IPv6 address
By default, all containers have networking enabled and they can make any By default, all containers have networking enabled and they can make any
outgoing connections. The operator can completely disable networking outgoing connections. The operator can completely disable networking

View file

@ -115,8 +115,8 @@ $ docker run -itd --name=container2 busybox
Then create an isolated, `bridge` network to test with. Then create an isolated, `bridge` network to test with.
```bash ```bash
$ docker network create -d bridge isolated_nw $ docker network create -d bridge --subnet 172.25.0.0/16 isolated_nw
f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a 06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8
``` ```
Connect `container2` to the network and then `inspect` the network to verify the connection: Connect `container2` to the network and then `inspect` the network to verify the connection:
@ -124,23 +124,26 @@ Connect `container2` to the network and then `inspect` the network to verify the
``` ```
$ docker network connect isolated_nw container2 $ docker network connect isolated_nw container2
$ docker network inspect isolated_nw $ docker network inspect isolated_nw
[[ [
{ {
"Name": "isolated_nw", "Name": "isolated_nw",
"Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a", "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
"Scope": "local", "Scope": "local",
"Driver": "bridge", "Driver": "bridge",
"IPAM": { "IPAM": {
"Driver": "default", "Driver": "default",
"Config": [ "Config": [
{} {
"Subnet": "172.25.0.0/16"
}
] ]
}, },
"Containers": { "Containers": {
"498eaaaf328e1018042c04b2de04036fc04719a6e39a097a4f4866043a2c2152": { "90e1f3ec71caf82ae776a827e0712a68a110a3f175954e5bd4222fd142ac9428": {
"EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9", "Name": "container2",
"MacAddress": "02:42:ac:15:00:02", "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
"IPv4Address": "172.21.0.2/16", "MacAddress": "02:42:ac:19:00:02",
"IPv4Address": "172.25.0.2/16",
"IPv6Address": "" "IPv6Address": ""
} }
}, },
@ -150,20 +153,28 @@ $ docker network inspect isolated_nw
``` ```
You can see that the Engine automatically assigns an IP address to `container2`. You can see that the Engine automatically assigns an IP address to `container2`.
If you had specified a `--subnetwork` when creating your network, the network Given we specified a `--subnet` when creating the network, Engine picked
would have used that addressing. Now, start a third container and connect it to an address from that same subnet. Now, start a third container and connect it to
the network on launch using the `docker run` command's `--net` option: the network on launch using the `docker run` command's `--net` option:
```bash ```bash
$ docker run --net=isolated_nw -itd --name=container3 busybox $ docker run --net=isolated_nw --ip=172.25.3.3 -itd --name=container3 busybox
c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c 467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551
``` ```
As you can see you were able to specify the ip address for your container.
As long as the network to which the container is connecting was created with
a user specified subnet, you will be able to select the IPv4 and/or IPv6 address(es)
for your container when executing `docker run` and `docker network connect` commands.
The selected IP address is part of the container networking configuration and will be
preserved across container reload. The feature is only available on user defined networks,
because they guarantee their subnets configuration does not change across daemon reload.
Now, inspect the network resources used by `container3`. Now, inspect the network resources used by `container3`.
```bash ```bash
$ docker inspect --format='{{json .NetworkSettings.Networks}}' container3 $ docker inspect --format='{{json .NetworkSettings.Networks}}' container3
{"isolated_nw":{"EndpointID":"e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847","Gateway":"172.21.0.1","IPAddress":"172.21.0.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:15:00:03"}} {"isolated_nw":{"IPAMConfig":{"IPv4Address":"172.25.3.3"},"EndpointID":"dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103","Gateway":"172.25.0.1","IPAddress":"172.25.3.3","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:19:03:03"}}
``` ```
Repeat this command for `container2`. If you have Python installed, you can pretty print the output. Repeat this command for `container2`. If you have Python installed, you can pretty print the output.
@ -171,24 +182,26 @@ Repeat this command for `container2`. If you have Python installed, you can pret
$ docker inspect --format='{{json .NetworkSettings.Networks}}' container2 | python -m json.tool $ docker inspect --format='{{json .NetworkSettings.Networks}}' container2 | python -m json.tool
{ {
"bridge": { "bridge": {
"EndpointID": "281b5ead415cf48a6a84fd1a6504342c76e9091fe09b4fdbcc4a01c30b0d3c5b", "EndpointID": "0099f9efb5a3727f6a554f176b1e96fca34cae773da68b3b6a26d046c12cb365",
"Gateway": "172.17.0.1", "Gateway": "172.17.0.1",
"GlobalIPv6Address": "", "GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0, "GlobalIPv6PrefixLen": 0,
"IPAMConfig": null,
"IPAddress": "172.17.0.3", "IPAddress": "172.17.0.3",
"IPPrefixLen": 16, "IPPrefixLen": 16,
"IPv6Gateway": "", "IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:03" "MacAddress": "02:42:ac:11:00:03"
}, },
"isolated_nw": { "isolated_nw": {
"EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9", "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
"Gateway": "172.21.0.1", "Gateway": "172.25.0.1",
"GlobalIPv6Address": "", "GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0, "GlobalIPv6PrefixLen": 0,
"IPAddress": "172.21.0.2", "IPAMConfig": null,
"IPAddress": "172.25.0.2",
"IPPrefixLen": 16, "IPPrefixLen": 16,
"IPv6Gateway": "", "IPv6Gateway": "",
"MacAddress": "02:42:ac:15:00:02" "MacAddress": "02:42:ac:19:00:02"
} }
} }
``` ```
@ -223,8 +236,8 @@ eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03
RX bytes:648 (648.0 B) TX bytes:648 (648.0 B) RX bytes:648 (648.0 B) TX bytes:648 (648.0 B)
eth1 Link encap:Ethernet HWaddr 02:42:AC:15:00:02 eth1 Link encap:Ethernet HWaddr 02:42:AC:15:00:02
inet addr:172.21.0.2 Bcast:0.0.0.0 Mask:255.255.0.0 inet addr:172.25.0.2 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:acff:fe15:2/64 Scope:Link inet6 addr: fe80::42:acff:fe19:2/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0 RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
@ -252,19 +265,19 @@ fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes ff02::1 ip6-allnodes
ff02::2 ip6-allrouters ff02::2 ip6-allrouters
172.21.0.3 container3 172.21.3.3 container3
172.21.0.3 container3.isolated_nw 172.21.3.3 container3.isolated_nw
``` ```
On the `isolated_nw` which was user defined, the Docker network feature updated the `/etc/hosts` with the proper name resolution. Inside of `container2` it is possible to ping `container3` by name. On the `isolated_nw` which was user defined, the Docker network feature updated the `/etc/hosts` with the proper name resolution. Inside of `container2` it is possible to ping `container3` by name.
```bash ```bash
/ # ping -w 4 container3 / # ping -w 4 container3
PING container3 (172.21.0.3): 56 data bytes PING container3 (172.25.3.3): 56 data bytes
64 bytes from 172.21.0.3: seq=0 ttl=64 time=0.070 ms 64 bytes from 172.25.3.3: seq=0 ttl=64 time=0.070 ms
64 bytes from 172.21.0.3: seq=1 ttl=64 time=0.080 ms 64 bytes from 172.25.3.3: seq=1 ttl=64 time=0.080 ms
64 bytes from 172.21.0.3: seq=2 ttl=64 time=0.080 ms 64 bytes from 172.25.3.3: seq=2 ttl=64 time=0.080 ms
64 bytes from 172.21.0.3: seq=3 ttl=64 time=0.097 ms 64 bytes from 172.25.3.3: seq=3 ttl=64 time=0.097 ms
--- container3 ping statistics --- --- container3 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss 4 packets transmitted, 4 packets received, 0% packet loss
@ -342,23 +355,26 @@ docker inspect --format='{{json .NetworkSettings.Networks}}' container2 | pytho
$ docker network inspect isolated_nw $ docker network inspect isolated_nw
[[ [
{ {
"Name": "isolated_nw", "Name": "isolated_nw",
"Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a", "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
"Scope": "local", "Scope": "local",
"Driver": "bridge", "Driver": "bridge",
"IPAM": { "IPAM": {
"Driver": "default", "Driver": "default",
"Config": [ "Config": [
{} {
"Subnet": "172.25.0.0/16"
}
] ]
}, },
"Containers": { "Containers": {
"c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c": { "467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551": {
"EndpointID": "e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847", "Name": "container3",
"MacAddress": "02:42:ac:15:00:03", "EndpointID": "dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103",
"IPv4Address": "172.21.0.3/16", "MacAddress": "02:42:ac:19:03:03",
"IPv4Address": "172.25.3.3/16",
"IPv6Address": "" "IPv6Address": ""
} }
}, },
@ -393,7 +409,7 @@ lo Link encap:Local Loopback
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
/ # ping container3 / # ping container3
PING container3 (172.20.0.1): 56 data bytes PING container3 (172.25.3.3): 56 data bytes
^C ^C
--- container3 ping statistics --- --- container3 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss 2 packets transmitted, 0 packets received, 100% packet loss
@ -426,13 +442,15 @@ docker network inspect isolated_nw
[ [
{ {
"Name": "isolated_nw", "Name": "isolated_nw",
"Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a", "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
"Scope": "local", "Scope": "local",
"Driver": "bridge", "Driver": "bridge",
"IPAM": { "IPAM": {
"Driver": "default", "Driver": "default",
"Config": [ "Config": [
{} {
"Subnet": "172.25.0.0/16"
}
] ]
}, },
"Containers": {}, "Containers": {},

View file

@ -962,3 +962,71 @@ func (s *DockerNetworkSuite) TestDockerNetworkRestartWithMulipleNetworks(c *chec
c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network")) c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network"))
c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr")) c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr"))
} }
func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *check.C) {
// create two networks
dockerCmd(c, "network", "create", "--subnet=172.28.0.0/16", "--subnet=2001:db8:1234::/64", "n0")
assertNwIsAvailable(c, "n0")
dockerCmd(c, "network", "create", "--subnet=172.30.0.0/16", "--ip-range=172.30.5.0/24", "--subnet=2001:db8:abcd::/64", "--ip-range=2001:db8:abcd::/80", "n1")
assertNwIsAvailable(c, "n1")
// run a container on first network specifying the ip addresses
dockerCmd(c, "run", "-d", "--name", "c0", "--net=n0", "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
c.Assert(waitRun("c0"), check.IsNil)
verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
// connect the container to the second network specifying the preferred ip addresses
dockerCmd(c, "network", "connect", "--ip", "172.30.55.44", "--ip6", "2001:db8:abcd::5544", "n1", "c0")
verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
// Stop and restart the container
dockerCmd(c, "stop", "c0")
dockerCmd(c, "start", "c0")
// verify preferred addresses are applied
verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
// Still it should fail to connect to the default network with a specified IP (whatever ip)
out, _, err := dockerCmdWithError("network", "connect", "--ip", "172.21.55.44", "bridge", "c0")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
}
func (s *DockerNetworkSuite) TestDockerNetworkUnsupportedPreferredIP(c *check.C) {
// preferred IP is not supported on predefined networks
for _, mode := range []string{"none", "host", "bridge"} {
checkUnsupportedNetworkAndIP(c, mode)
}
// preferred IP is not supported on networks with no user defined subnets
dockerCmd(c, "network", "create", "n0")
assertNwIsAvailable(c, "n0")
out, _, err := dockerCmdWithError("run", "-d", "--ip", "172.28.99.88", "--net", "n0", "busybox", "top")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
out, _, err = dockerCmdWithError("run", "-d", "--ip6", "2001:db8:1234::9988", "--net", "n0", "busybox", "top")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
dockerCmd(c, "network", "rm", "n0")
assertNwNotAvailable(c, "n0")
}
func checkUnsupportedNetworkAndIP(c *check.C, nwMode string) {
out, _, err := dockerCmdWithError("run", "-d", "--net", nwMode, "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
}
func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) {
out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", nwname), cName)
c.Assert(strings.TrimSpace(out), check.Equals, ipv4)
out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName)
c.Assert(strings.TrimSpace(out), check.Equals, ipv6)
}

View file

@ -7,18 +7,19 @@ import (
"github.com/docker/docker/volume" "github.com/docker/docker/volume"
"github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
) )
// DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
// struct and returns both a Config and an HostConfig struct // struct and returns both a Config and an HostConfig struct
// Be aware this function is not checking whether the resulted structs are nil, // Be aware this function is not checking whether the resulted structs are nil,
// it's your business to do so // it's your business to do so
func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, error) { func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
var w ContainerConfigWrapper var w ContainerConfigWrapper
decoder := json.NewDecoder(src) decoder := json.NewDecoder(src)
if err := decoder.Decode(&w); err != nil { if err := decoder.Decode(&w); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
hc := w.getHostConfig() hc := w.getHostConfig()
@ -33,21 +34,21 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
// Now validate all the volumes and binds // Now validate all the volumes and binds
if err := validateVolumesAndBindSettings(w.Config, hc); err != nil { if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
} }
// Certain parameters need daemon-side validation that cannot be done // Certain parameters need daemon-side validation that cannot be done
// on the client, as only the daemon knows what is valid for the platform. // on the client, as only the daemon knows what is valid for the platform.
if err := ValidateNetMode(w.Config, hc); err != nil { if err := ValidateNetMode(w.Config, hc); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
// Validate the isolation level // Validate the isolation level
if err := ValidateIsolationLevel(hc); err != nil { if err := ValidateIsolationLevel(hc); err != nil {
return nil, nil, err return nil, nil, nil, err
} }
return w.Config, hc, nil return w.Config, hc, w.NetworkingConfig, nil
} }
// validateVolumesAndBindSettings validates each of the volumes and bind settings // validateVolumesAndBindSettings validates each of the volumes and bind settings

View file

@ -10,6 +10,7 @@ import (
"testing" "testing"
"github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/strslice" "github.com/docker/engine-api/types/strslice"
) )
@ -45,7 +46,7 @@ func TestDecodeContainerConfig(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
c, h, err := DecodeContainerConfig(bytes.NewReader(b)) c, h, _, err := DecodeContainerConfig(bytes.NewReader(b))
if err != nil { if err != nil {
t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err)) t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
} }
@ -70,29 +71,29 @@ func TestDecodeContainerConfig(t *testing.T) {
func TestDecodeContainerConfigIsolation(t *testing.T) { func TestDecodeContainerConfigIsolation(t *testing.T) {
// An invalid isolation level // An invalid isolation level
if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil { if _, _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) { if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
t.Fatal(err) t.Fatal(err)
} }
} }
// Blank isolation level (== default) // Blank isolation level (== default)
if _, _, err := callDecodeContainerConfigIsolation(""); err != nil { if _, _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
t.Fatal("Blank isolation should have succeeded") t.Fatal("Blank isolation should have succeeded")
} }
// Default isolation level // Default isolation level
if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil { if _, _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
t.Fatal("default isolation should have succeeded") t.Fatal("default isolation should have succeeded")
} }
// Hyper-V Containers isolation level (Valid on Windows only) // Hyper-V Containers isolation level (Valid on Windows only)
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil { if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
t.Fatal("hyperv isolation should have succeeded") t.Fatal("hyperv isolation should have succeeded")
} }
} else { } else {
if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil { if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) { if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
t.Fatal(err) t.Fatal(err)
} }
@ -102,7 +103,7 @@ func TestDecodeContainerConfigIsolation(t *testing.T) {
// callDecodeContainerConfigIsolation is a utility function to call // callDecodeContainerConfigIsolation is a utility function to call
// DecodeContainerConfig for validating isolation levels // DecodeContainerConfig for validating isolation levels
func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, error) { func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
var ( var (
b []byte b []byte
err error err error
@ -114,7 +115,7 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
Isolation: container.IsolationLevel(isolation)}, Isolation: container.IsolationLevel(isolation)},
} }
if b, err = json.Marshal(w); err != nil { if b, err = json.Marshal(w); err != nil {
return nil, nil, fmt.Errorf("Error on marshal %s", err.Error()) return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
} }
return DecodeContainerConfig(bytes.NewReader(b)) return DecodeContainerConfig(bytes.NewReader(b))
} }

View file

@ -2,7 +2,10 @@
package runconfig package runconfig
import "github.com/docker/engine-api/types/container" import (
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
)
// ContainerConfigWrapper is a Config wrapper that hold the container Config (portable) // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
// and the corresponding HostConfig (non-portable). // and the corresponding HostConfig (non-portable).
@ -10,6 +13,7 @@ type ContainerConfigWrapper struct {
*container.Config *container.Config
InnerHostConfig *container.HostConfig `json:"HostConfig,omitempty"` InnerHostConfig *container.HostConfig `json:"HostConfig,omitempty"`
Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility. Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
*container.HostConfig // Deprecated. Exported to read attributes from json that are not in the inner host config structure. *container.HostConfig // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
} }

View file

@ -1,12 +1,16 @@
package runconfig package runconfig
import "github.com/docker/engine-api/types/container" import (
"github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
)
// ContainerConfigWrapper is a Config wrapper that hold the container Config (portable) // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
// and the corresponding HostConfig (non-portable). // and the corresponding HostConfig (non-portable).
type ContainerConfigWrapper struct { type ContainerConfigWrapper struct {
*container.Config *container.Config
HostConfig *container.HostConfig `json:"HostConfig,omitempty"` HostConfig *container.HostConfig `json:"HostConfig,omitempty"`
NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
} }
// getHostConfig gets the HostConfig of the Config. // getHostConfig gets the HostConfig of the Config.

View file

@ -29,4 +29,8 @@ var (
ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode") ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
// ErrConflictNetworkExposePorts conflict between the expose option and the network mode // ErrConflictNetworkExposePorts conflict between the expose option and the network mode
ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode") ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
// ErrUnsupportedNetworkAndIP conflict between network mode and preferred ip address
ErrUnsupportedNetworkAndIP = fmt.Errorf("User specified IP address is supported on user defined networks only")
// ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and preferred ip address
ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("User specified IP address is supported only when connecting to networks with user configured subnets")
) )

View file

@ -11,6 +11,7 @@ import (
"github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/strslice" "github.com/docker/engine-api/types/strslice"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/docker/go-units" "github.com/docker/go-units"
@ -19,7 +20,7 @@ import (
// Parse parses the specified args for the specified command and generates a Config, // Parse parses the specified args for the specified command and generates a Config,
// a HostConfig and returns them with the specified command. // a HostConfig and returns them with the specified command.
// If the specified args are not valid, it will return an error. // If the specified args are not valid, it will return an error.
func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) { func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
var ( var (
// FIXME: use utils.ListOpts for attach and volumes? // FIXME: use utils.ListOpts for attach and volumes?
flAttach = opts.NewListOpts(ValidateAttach) flAttach = opts.NewListOpts(ValidateAttach)
@ -77,6 +78,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)") flSwappiness = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network") flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
flIPv4Address = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
flIPv6Address = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits") flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only") flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
@ -119,7 +122,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
cmd.Require(flag.Min, 1) cmd.Require(flag.Min, 1)
if err := cmd.ParseFlags(args, true); err != nil { if err := cmd.ParseFlags(args, true); err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
var ( var (
@ -131,7 +134,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
// Validate the input mac address // Validate the input mac address
if *flMacAddress != "" { if *flMacAddress != "" {
if _, err := ValidateMACAddress(*flMacAddress); err != nil { if _, err := ValidateMACAddress(*flMacAddress); err != nil {
return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress) return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
} }
} }
if *flStdin { if *flStdin {
@ -149,7 +152,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if *flMemoryString != "" { if *flMemoryString != "" {
flMemory, err = units.RAMInBytes(*flMemoryString) flMemory, err = units.RAMInBytes(*flMemoryString)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
} }
@ -157,7 +160,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if *flMemoryReservation != "" { if *flMemoryReservation != "" {
MemoryReservation, err = units.RAMInBytes(*flMemoryReservation) MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
} }
@ -168,7 +171,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
} else { } else {
memorySwap, err = units.RAMInBytes(*flMemorySwap) memorySwap, err = units.RAMInBytes(*flMemorySwap)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
} }
} }
@ -177,20 +180,20 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if *flKernelMemory != "" { if *flKernelMemory != "" {
KernelMemory, err = units.RAMInBytes(*flKernelMemory) KernelMemory, err = units.RAMInBytes(*flKernelMemory)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
} }
swappiness := *flSwappiness swappiness := *flSwappiness
if swappiness != -1 && (swappiness < 0 || swappiness > 100) { if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness) return nil, nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
} }
var shmSize int64 var shmSize int64
if *flShmSize != "" { if *flShmSize != "" {
shmSize, err = units.RAMInBytes(*flShmSize) shmSize, err = units.RAMInBytes(*flShmSize)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
} }
@ -210,7 +213,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
for _, t := range flTmpfs.GetAll() { for _, t := range flTmpfs.GetAll() {
if arr := strings.SplitN(t, ":", 2); len(arr) > 1 { if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil { if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
tmpfs[arr[0]] = arr[1] tmpfs[arr[0]] = arr[1]
} else { } else {
@ -243,13 +246,13 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
// Merge in exposed ports to the map of published ports // Merge in exposed ports to the map of published ports
for _, e := range flExpose.GetAll() { for _, e := range flExpose.GetAll() {
if strings.Contains(e, ":") { if strings.Contains(e, ":") {
return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e) return nil, nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
} }
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
proto, port := nat.SplitProtoPort(e) proto, port := nat.SplitProtoPort(e)
@ -257,12 +260,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
//if expose a port, the start and end port are the same //if expose a port, the start and end port are the same
start, end, err := nat.ParsePortRange(port) start, end, err := nat.ParsePortRange(port)
if err != nil { if err != nil {
return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err) return nil, nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
} }
for i := start; i <= end; i++ { for i := start; i <= end; i++ {
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
if _, exists := ports[p]; !exists { if _, exists := ports[p]; !exists {
ports[p] = struct{}{} ports[p] = struct{}{}
@ -275,7 +278,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
for _, device := range flDevices.GetAll() { for _, device := range flDevices.GetAll() {
deviceMapping, err := ParseDevice(device) deviceMapping, err := ParseDevice(device)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
deviceMappings = append(deviceMappings, deviceMapping) deviceMappings = append(deviceMappings, deviceMapping)
} }
@ -283,38 +286,38 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
// collect all the environment variables for the container // collect all the environment variables for the container
envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll()) envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
// collect all the labels for the container // collect all the labels for the container
labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll()) labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
ipcMode := container.IpcMode(*flIpcMode) ipcMode := container.IpcMode(*flIpcMode)
if !ipcMode.Valid() { if !ipcMode.Valid() {
return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode") return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
} }
pidMode := container.PidMode(*flPidMode) pidMode := container.PidMode(*flPidMode)
if !pidMode.Valid() { if !pidMode.Valid() {
return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode") return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
} }
utsMode := container.UTSMode(*flUTSMode) utsMode := container.UTSMode(*flUTSMode)
if !utsMode.Valid() { if !utsMode.Valid() {
return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode") return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
} }
restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll()) loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, nil, cmd, err
} }
resources := container.Resources{ resources := container.Resources{
@ -405,7 +408,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
if config.OpenStdin && config.AttachStdin { if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true config.StdinOnce = true
} }
return config, hostConfig, cmd, nil
var networkingConfig *networktypes.NetworkingConfig
if *flIPv4Address != "" || *flIPv6Address != "" {
networkingConfig = &networktypes.NetworkingConfig{
EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
}
networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
IPAMConfig: &networktypes.EndpointIPAMConfig{
IPv4Address: *flIPv4Address,
IPv6Address: *flIPv6Address,
},
}
}
return config, hostConfig, networkingConfig, cmd, nil
} }
// reads a file of line terminated key=value pairs and override that with override parameter // reads a file of line terminated key=value pairs and override that with override parameter

View file

@ -13,10 +13,11 @@ import (
flag "github.com/docker/docker/pkg/mflag" flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
) )
func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) { func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
cmd := flag.NewFlagSet("run", flag.ContinueOnError) cmd := flag.NewFlagSet("run", flag.ContinueOnError)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
cmd.Usage = nil cmd.Usage = nil
@ -24,7 +25,7 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.Fl
} }
func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) { func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " ")) config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
return config, hostConfig, err return config, hostConfig, err
} }
@ -304,7 +305,7 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
if b, err = json.Marshal(w); err != nil { if b, err = json.Marshal(w); err != nil {
return nil, nil, fmt.Errorf("Error on marshal %s", err.Error()) return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
} }
c, h, err = runconfig.DecodeContainerConfig(bytes.NewReader(b)) c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err) return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
} }
@ -349,7 +350,7 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
func TestParseWithMacAddress(t *testing.T) { func TestParseWithMacAddress(t *testing.T) {
invalidMacAddress := "--mac-address=invalidMacAddress" invalidMacAddress := "--mac-address=invalidMacAddress"
validMacAddress := "--mac-address=92:d0:c6:0a:29:33" validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" { if _, _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err) t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
} }
if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" { if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
@ -360,7 +361,7 @@ func TestParseWithMacAddress(t *testing.T) {
func TestParseWithMemory(t *testing.T) { func TestParseWithMemory(t *testing.T) {
invalidMemory := "--memory=invalid" invalidMemory := "--memory=invalid"
validMemory := "--memory=1G" validMemory := "--memory=1G"
if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" { if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err) t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
} }
if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 { if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
@ -372,7 +373,7 @@ func TestParseWithMemorySwap(t *testing.T) {
invalidMemory := "--memory-swap=invalid" invalidMemory := "--memory-swap=invalid"
validMemory := "--memory-swap=1G" validMemory := "--memory-swap=1G"
anotherValidMemory := "--memory-swap=-1" anotherValidMemory := "--memory-swap=-1"
if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" { if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err) t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
} }
if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 { if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
@ -417,12 +418,12 @@ func TestParseWithExpose(t *testing.T) {
"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"}, "8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
} }
for expose, expectedError := range invalids { for expose, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError { if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err) t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
} }
} }
for expose, exposedPorts := range valids { for expose, exposedPorts := range valids {
config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}) config, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -436,7 +437,7 @@ func TestParseWithExpose(t *testing.T) {
} }
} }
// Merge with actual published port // Merge with actual published port
config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"}) config, _, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -475,7 +476,7 @@ func TestParseDevice(t *testing.T) {
}, },
} }
for device, deviceMapping := range valids { for device, deviceMapping := range valids {
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"}) _, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -491,11 +492,11 @@ func TestParseDevice(t *testing.T) {
func TestParseModes(t *testing.T) { func TestParseModes(t *testing.T) {
// ipc ko // ipc ko
if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" { if _, _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err) t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
} }
// ipc ok // ipc ok
_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"}) _, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -503,11 +504,11 @@ func TestParseModes(t *testing.T) {
t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode) t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
} }
// pid ko // pid ko
if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" { if _, _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err) t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
} }
// pid ok // pid ok
_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"}) _, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -515,11 +516,11 @@ func TestParseModes(t *testing.T) {
t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode) t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
} }
// uts ko // uts ko
if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" { if _, _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err) t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
} }
// uts ok // uts ok
_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"}) _, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -527,11 +528,11 @@ func TestParseModes(t *testing.T) {
t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode) t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
} }
// shm-size ko // shm-size ko
if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" { if _, _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err) t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
} }
// shm-size ok // shm-size ok
_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"}) _, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -560,12 +561,12 @@ func TestParseRestartPolicy(t *testing.T) {
}, },
} }
for restart, expectedError := range invalids { for restart, expectedError := range invalids {
if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError { if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err) t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
} }
} }
for restart, expected := range valids { for restart, expected := range valids {
_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"}) _, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -577,11 +578,11 @@ func TestParseRestartPolicy(t *testing.T) {
func TestParseLoggingOpts(t *testing.T) { func TestParseLoggingOpts(t *testing.T) {
// logging opts ko // logging opts ko
if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" { if _, _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err) t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
} }
// logging opts ok // logging opts ok
_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"}) _, hostconfig, _, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -596,18 +597,18 @@ func TestParseEnvfileVariables(t *testing.T) {
e = "open nonexistent: The system cannot find the file specified." e = "open nonexistent: The system cannot find the file specified."
} }
// env ko // env ko
if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e { if _, _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
t.Fatalf("Expected an error with message '%s', got %v", e, err) t.Fatalf("Expected an error with message '%s', got %v", e, err)
} }
// env ok // env ok
config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"}) config, _, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" { if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env) t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
} }
config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"}) config, _, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -622,18 +623,18 @@ func TestParseLabelfileVariables(t *testing.T) {
e = "open nonexistent: The system cannot find the file specified." e = "open nonexistent: The system cannot find the file specified."
} }
// label ko // label ko
if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e { if _, _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
t.Fatalf("Expected an error with message '%s', got %v", e, err) t.Fatalf("Expected an error with message '%s', got %v", e, err)
} }
// label ok // label ok
config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"}) config, _, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" { if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels) t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
} }
config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"}) config, _, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -643,7 +644,7 @@ func TestParseLabelfileVariables(t *testing.T) {
} }
func TestParseEntryPoint(t *testing.T) { func TestParseEntryPoint(t *testing.T) {
config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"}) config, _, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }