Browse Source

Allow user to choose the IP address for the container

Signed-off-by: Alessandro Boch <aboch@docker.com>
Alessandro Boch 9 years ago
parent
commit
2bb3fc1bc5

+ 8 - 5
api/client/create.go

@@ -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
 	}
 	}

+ 11 - 4
api/client/network.go

@@ -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
 	}
 	}
-
-	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), nil)
+	epConfig := &network.EndpointSettings{
+		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

+ 3 - 2
api/client/run.go

@@ -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)

+ 7 - 6
api/server/router/container/container_routes.go

@@ -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
 	}
 	}
@@ -366,10 +366,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 	adjustCPUShares := version.LessThan("1.19")
 	adjustCPUShares := version.LessThan("1.19")
 
 
 	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
 	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
-		Name:            name,
-		Config:          config,
-		HostConfig:      hostConfig,
-		AdjustCPUShares: adjustCPUShares,
+		Name:             name,
+		Config:           config,
+		HostConfig:       hostConfig,
+		NetworkingConfig: networkingConfig,
+		AdjustCPUShares:  adjustCPUShares,
 	})
 	})
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 1 - 1
api/server/router/local/image.go

@@ -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
 	}
 	}

+ 1 - 2
api/server/router/network/backend.go

@@ -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

+ 1 - 1
api/server/router/network/network_routes.go

@@ -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 {

+ 8 - 0
container/container_unix.go

@@ -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() &&

+ 111 - 19
daemon/container_operations_unix.go

@@ -503,7 +503,10 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
 			return runconfig.ErrConflictNoNetwork
 			return runconfig.ErrConflictNoNetwork
 		}
 		}
 	}
 	}
-	container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
+
+	if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
+		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()
 	}
 	}
-	container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
-	container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
+	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[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 {
-		if err := daemon.connectToNetwork(container, n, updateSettings); err != nil {
+	for n, nConf := range container.NetworkSettings.Networks {
+		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{}
-	}
-
-	container.NetworkSettings = &network.Settings{Networks: settings}
-
-	if sid == "" || len(settings) == 0 {
-		return
+		cleanOperationalData(epSettings)
 	}
 	}
 
 
 	sb, err := daemon.netController.SandboxByID(sid)
 	sb, err := daemon.netController.SandboxByID(sid)

+ 3 - 2
daemon/container_operations_windows.go

@@ -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
 }
 }
 
 

+ 7 - 1
daemon/create.go

@@ -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
 	}
 	}
 
 

+ 2 - 2
daemon/network.go

@@ -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

+ 2 - 0
docs/reference/api/docker_remote_api.md

@@ -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
 
 

+ 7 - 1
docs/reference/api/docker_remote_api_v1.22.md

@@ -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"
+    }
+  }
 }
 }
 ```
 ```
 
 

+ 8 - 1
docs/reference/commandline/network_connect.md

@@ -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.
 
 

+ 2 - 0
docs/reference/commandline/run.md

@@ -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.

+ 2 - 0
docs/reference/run.md

@@ -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

+ 56 - 38
docs/userguide/networking/work-with-networks.md

@@ -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
-f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a
+$ docker network create -d bridge --subnet 172.25.0.0/16 isolated_nw
+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": {
-                "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
-                "MacAddress": "02:42:ac:15:00:02",
-                "IPv4Address": "172.21.0.2/16",
+            "90e1f3ec71caf82ae776a827e0712a68a110a3f175954e5bd4222fd142ac9428": {
+                "Name": "container2",
+                "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
+                "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
-would have used that addressing. Now, start a third container and connect it to
+Given we specified a `--subnet` when creating the network, Engine picked
+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
-c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c
+$ docker run --net=isolated_nw --ip=172.25.3.3 -itd --name=container3 busybox
+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",
-        "Gateway": "172.21.0.1",
+        "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
+        "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
-          inet6 addr: fe80::42:acff:fe15:2/64 Scope:Link
+          inet addr:172.25.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
+          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.0.3	container3.isolated_nw
+172.21.3.3	container3
+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
-64 bytes from 172.21.0.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.21.0.3: seq=2 ttl=64 time=0.080 ms
-64 bytes from 172.21.0.3: seq=3 ttl=64 time=0.097 ms
+PING container3 (172.25.3.3): 56 data bytes
+64 bytes from 172.25.3.3: seq=0 ttl=64 time=0.070 ms
+64 bytes from 172.25.3.3: seq=1 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.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": {
-                "EndpointID": "e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847",
-                "MacAddress": "02:42:ac:15:00:03",
-                "IPv4Address": "172.21.0.3/16",
+            "467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551": {
+                "Name": "container3",
+                "EndpointID": "dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103",
+                "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": {},

+ 68 - 0
integration-cli/docker_cli_network_unix_test.go

@@ -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)
+}

+ 7 - 6
runconfig/config.go

@@ -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

+ 9 - 8
runconfig/config_test.go

@@ -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))
 }
 }

+ 8 - 4
runconfig/config_unix.go

@@ -2,15 +2,19 @@
 
 
 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
-	InnerHostConfig       *container.HostConfig `json:"HostConfig,omitempty"`
-	Cpuset                string                `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
-	*container.HostConfig                       // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
+	InnerHostConfig       *container.HostConfig          `json:"HostConfig,omitempty"`
+	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.
 }
 }
 
 
 // getHostConfig gets the HostConfig of the Config.
 // getHostConfig gets the HostConfig of the Config.

+ 6 - 2
runconfig/config_windows.go

@@ -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.

+ 4 - 0
runconfig/errors.go

@@ -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")
 )
 )

+ 40 - 23
runconfig/opts/parse.go

@@ -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

+ 30 - 29
runconfig/opts/parse_test.go

@@ -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)
 	}
 	}