Browse code

Allow user to choose the IP address for the container

Signed-off-by: Alessandro Boch <aboch@docker.com>

Alessandro Boch authored on 2016/01/08 09:18:34
Showing 26 changed files
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/docker/engine-api/client"
14 14
 	"github.com/docker/engine-api/types"
15 15
 	"github.com/docker/engine-api/types/container"
16
+	networktypes "github.com/docker/engine-api/types/network"
16 17
 )
17 18
 
18 19
 func (cli *DockerCli) pullImage(image string) error {
... ...
@@ -79,7 +80,7 @@ func newCIDFile(path string) (*cidFile, error) {
79 79
 	return &cidFile{path: path, file: f}, nil
80 80
 }
81 81
 
82
-func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
82
+func (cli *DockerCli) createContainer(config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
83 83
 	var containerIDFile *cidFile
84 84
 	if cidfile != "" {
85 85
 		var err error
... ...
@@ -107,7 +108,8 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
107 107
 	}
108 108
 
109 109
 	//create the container
110
-	response, err := cli.client.ContainerCreate(config, hostConfig, nil, name)
110
+	response, err := cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
111
+
111 112
 	//if image not found try to pull it
112 113
 	if err != nil {
113 114
 		if client.IsErrImageNotFound(err) {
... ...
@@ -124,7 +126,7 @@ func (cli *DockerCli) createContainer(config *container.Config, hostConfig *cont
124 124
 			}
125 125
 			// Retry
126 126
 			var retryErr error
127
-			response, retryErr = cli.client.ContainerCreate(config, hostConfig, nil, name)
127
+			response, retryErr = cli.client.ContainerCreate(config, hostConfig, networkingConfig, name)
128 128
 			if retryErr != nil {
129 129
 				return nil, retryErr
130 130
 			}
... ...
@@ -156,7 +158,8 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
156 156
 		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
157 157
 	)
158 158
 
159
-	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
159
+	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
160
+
160 161
 	if err != nil {
161 162
 		cmd.ReportError(err.Error(), true)
162 163
 		os.Exit(1)
... ...
@@ -165,7 +168,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
165 165
 		cmd.Usage()
166 166
 		return nil
167 167
 	}
168
-	response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
168
+	response, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
169 169
 	if err != nil {
170 170
 		return err
171 171
 	}
... ...
@@ -107,15 +107,22 @@ func (cli *DockerCli) CmdNetworkRm(args ...string) error {
107 107
 
108 108
 // CmdNetworkConnect connects a container to a network
109 109
 //
110
-// Usage: docker network connect <NETWORK> <CONTAINER>
110
+// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
111 111
 func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
112 112
 	cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
113
-	cmd.Require(flag.Exact, 2)
113
+	flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
114
+	flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
115
+	cmd.Require(flag.Min, 2)
114 116
 	if err := cmd.ParseFlags(args, true); err != nil {
115 117
 		return err
116 118
 	}
117
-
118
-	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), nil)
119
+	epConfig := &network.EndpointSettings{
120
+		IPAMConfig: &network.EndpointIPAMConfig{
121
+			IPv4Address: *flIPAddress,
122
+			IPv6Address: *flIPv6Address,
123
+		},
124
+	}
125
+	return cli.client.NetworkConnect(cmd.Arg(0), cmd.Arg(1), epConfig)
119 126
 }
120 127
 
121 128
 // CmdNetworkDisconnect disconnects a container from a network
... ...
@@ -82,7 +82,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
82 82
 		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
83 83
 	)
84 84
 
85
-	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
85
+	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
86
+
86 87
 	// just in case the Parse does not exit
87 88
 	if err != nil {
88 89
 		cmd.ReportError(err.Error(), true)
... ...
@@ -145,7 +146,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
145 145
 		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
146 146
 	}
147 147
 
148
-	createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
148
+	createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
149 149
 	if err != nil {
150 150
 		cmd.ReportError(err.Error(), true)
151 151
 		return runStartContainerErr(err)
... ...
@@ -332,7 +332,7 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
332 332
 		return err
333 333
 	}
334 334
 
335
-	_, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
335
+	_, hostConfig, _, err := runconfig.DecodeContainerConfig(r.Body)
336 336
 	if err != nil {
337 337
 		return err
338 338
 	}
... ...
@@ -358,7 +358,7 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
358 358
 
359 359
 	name := r.Form.Get("name")
360 360
 
361
-	config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
361
+	config, hostConfig, networkingConfig, err := runconfig.DecodeContainerConfig(r.Body)
362 362
 	if err != nil {
363 363
 		return err
364 364
 	}
... ...
@@ -366,10 +366,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
366 366
 	adjustCPUShares := version.LessThan("1.19")
367 367
 
368 368
 	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
369
-		Name:            name,
370
-		Config:          config,
371
-		HostConfig:      hostConfig,
372
-		AdjustCPUShares: adjustCPUShares,
369
+		Name:             name,
370
+		Config:           config,
371
+		HostConfig:       hostConfig,
372
+		NetworkingConfig: networkingConfig,
373
+		AdjustCPUShares:  adjustCPUShares,
373 374
 	})
374 375
 	if err != nil {
375 376
 		return err
... ...
@@ -39,7 +39,7 @@ func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.
39 39
 		pause = true
40 40
 	}
41 41
 
42
-	c, _, err := runconfig.DecodeContainerConfig(r.Body)
42
+	c, _, _, err := runconfig.DecodeContainerConfig(r.Body)
43 43
 	if err != nil && err != io.EOF { //Do not fail if body is empty.
44 44
 		return err
45 45
 	}
... ...
@@ -2,7 +2,6 @@ package network
2 2
 
3 3
 import (
4 4
 	"github.com/docker/engine-api/types/network"
5
-
6 5
 	"github.com/docker/libnetwork"
7 6
 )
8 7
 
... ...
@@ -15,7 +14,7 @@ type Backend interface {
15 15
 	GetAllNetworks() []libnetwork.Network
16 16
 	CreateNetwork(name, driver string, ipam network.IPAM,
17 17
 		options map[string]string) (libnetwork.Network, error)
18
-	ConnectContainerToNetwork(containerName, networkName string) error
18
+	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
19 19
 	DisconnectContainerFromNetwork(containerName string,
20 20
 		network libnetwork.Network) error
21 21
 	NetworkControllerEnabled() bool
... ...
@@ -122,7 +122,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
122 122
 		return err
123 123
 	}
124 124
 
125
-	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name())
125
+	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
126 126
 }
127 127
 
128 128
 func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -261,6 +261,14 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network) ([]
261 261
 		createOptions = append(createOptions, libnetwork.CreateOptionAnonymous())
262 262
 	}
263 263
 
264
+	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
265
+		ipam := epConfig.IPAMConfig
266
+		if ipam != nil && (ipam.IPv4Address != "" || ipam.IPv6Address != "") {
267
+			createOptions = append(createOptions,
268
+				libnetwork.CreateOptionIpam(net.ParseIP(ipam.IPv4Address), net.ParseIP(ipam.IPv6Address), nil))
269
+		}
270
+	}
271
+
264 272
 	// Other configs are applicable only for the endpoint in the network
265 273
 	// to which container was connected to on docker run.
266 274
 	if n.Name() != container.HostConfig.NetworkMode.NetworkName() &&
... ...
@@ -503,7 +503,10 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
503 503
 			return runconfig.ErrConflictNoNetwork
504 504
 		}
505 505
 	}
506
-	container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
506
+
507
+	if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
508
+		container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
509
+	}
507 510
 
508 511
 	return nil
509 512
 }
... ...
@@ -562,7 +565,12 @@ func (daemon *Daemon) updateNetwork(container *container.Container) error {
562 562
 }
563 563
 
564 564
 // updateContainerNetworkSettings update the network settings
565
-func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
565
+func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
566
+	var (
567
+		n   libnetwork.Network
568
+		err error
569
+	)
570
+
566 571
 	mode := container.HostConfig.NetworkMode
567 572
 	if container.Config.NetworkDisabled || mode.IsContainer() {
568 573
 		return nil
... ...
@@ -573,14 +581,35 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
573 573
 		networkName = daemon.netController.Config().Daemon.DefaultNetwork
574 574
 	}
575 575
 	if mode.IsUserDefined() {
576
-		n, err := daemon.FindNetwork(networkName)
576
+		n, err = daemon.FindNetwork(networkName)
577 577
 		if err != nil {
578 578
 			return err
579 579
 		}
580 580
 		networkName = n.Name()
581 581
 	}
582
-	container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
583
-	container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
582
+	if container.NetworkSettings == nil {
583
+		container.NetworkSettings = &network.Settings{}
584
+	}
585
+	if endpointsConfig != nil {
586
+		container.NetworkSettings.Networks = endpointsConfig
587
+	}
588
+	if container.NetworkSettings.Networks == nil {
589
+		container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
590
+		container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
591
+	}
592
+	if !mode.IsUserDefined() {
593
+		return nil
594
+	}
595
+	// Make sure to internally store the per network endpoint config by network name
596
+	if _, ok := container.NetworkSettings.Networks[networkName]; ok {
597
+		return nil
598
+	}
599
+	if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
600
+		container.NetworkSettings.Networks[networkName] = nwConfig
601
+		delete(container.NetworkSettings.Networks, n.ID())
602
+		return nil
603
+	}
604
+
584 605
 	return nil
585 606
 }
586 607
 
... ...
@@ -598,15 +627,15 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
598 598
 			return nil
599 599
 		}
600 600
 
601
-		err := daemon.updateContainerNetworkSettings(container)
601
+		err := daemon.updateContainerNetworkSettings(container, nil)
602 602
 		if err != nil {
603 603
 			return err
604 604
 		}
605 605
 		updateSettings = true
606 606
 	}
607 607
 
608
-	for n := range container.NetworkSettings.Networks {
609
-		if err := daemon.connectToNetwork(container, n, updateSettings); err != nil {
608
+	for n, nConf := range container.NetworkSettings.Networks {
609
+		if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
610 610
 			return err
611 611
 		}
612 612
 	}
... ...
@@ -626,12 +655,65 @@ func (daemon *Daemon) getNetworkSandbox(container *container.Container) libnetwo
626 626
 	return sb
627 627
 }
628 628
 
629
+// hasUserDefinedIPAddress returns whether the passed endpoint configuration contains IP address configuration
630
+func hasUserDefinedIPAddress(epConfig *networktypes.EndpointSettings) bool {
631
+	return epConfig != nil && epConfig.IPAMConfig != nil && (len(epConfig.IPAMConfig.IPv4Address) > 0 || len(epConfig.IPAMConfig.IPv6Address) > 0)
632
+}
633
+
634
+// User specified ip address is acceptable only for networks with user specified subnets.
635
+func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
636
+	if !hasUserDefinedIPAddress(epConfig) {
637
+		return nil
638
+	}
639
+	_, nwIPv4Configs, nwIPv6Configs := n.Info().IpamConfig()
640
+	for _, s := range []struct {
641
+		ipConfigured  bool
642
+		subnetConfigs []*libnetwork.IpamConf
643
+	}{
644
+		{
645
+			ipConfigured:  len(epConfig.IPAMConfig.IPv4Address) > 0,
646
+			subnetConfigs: nwIPv4Configs,
647
+		},
648
+		{
649
+			ipConfigured:  len(epConfig.IPAMConfig.IPv6Address) > 0,
650
+			subnetConfigs: nwIPv6Configs,
651
+		},
652
+	} {
653
+		if s.ipConfigured {
654
+			foundSubnet := false
655
+			for _, cfg := range s.subnetConfigs {
656
+				if len(cfg.PreferredPool) > 0 {
657
+					foundSubnet = true
658
+					break
659
+				}
660
+			}
661
+			if !foundSubnet {
662
+				return runconfig.ErrUnsupportedNetworkNoSubnetAndIP
663
+			}
664
+		}
665
+	}
666
+
667
+	return nil
668
+}
669
+
670
+// cleanOperationalData resets the operational data from the passed endpoint settings
671
+func cleanOperationalData(es *networktypes.EndpointSettings) {
672
+	es.EndpointID = ""
673
+	es.Gateway = ""
674
+	es.IPAddress = ""
675
+	es.IPPrefixLen = 0
676
+	es.IPv6Gateway = ""
677
+	es.GlobalIPv6Address = ""
678
+	es.GlobalIPv6PrefixLen = 0
679
+	es.MacAddress = ""
680
+}
681
+
629 682
 // ConnectToNetwork connects a container to a network
630
-func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
683
+func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
631 684
 	if !container.Running {
632 685
 		return derr.ErrorCodeNotRunning.WithArgs(container.ID)
633 686
 	}
634
-	if err := daemon.connectToNetwork(container, idOrName, true); err != nil {
687
+	if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
635 688
 		return err
636 689
 	}
637 690
 	if err := container.ToDiskLocking(); err != nil {
... ...
@@ -640,11 +722,15 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
640 640
 	return nil
641 641
 }
642 642
 
643
-func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, updateSettings bool) (err error) {
643
+func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
644 644
 	if container.HostConfig.NetworkMode.IsContainer() {
645 645
 		return runconfig.ErrConflictSharedNetwork
646 646
 	}
647 647
 
648
+	if !containertypes.NetworkMode(idOrName).IsUserDefined() && hasUserDefinedIPAddress(endpointConfig) {
649
+		return runconfig.ErrUnsupportedNetworkAndIP
650
+	}
651
+
648 652
 	if containertypes.NetworkMode(idOrName).IsBridge() &&
649 653
 		daemon.configStore.DisableBridge {
650 654
 		container.Config.NetworkDisabled = true
... ...
@@ -658,12 +744,20 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
658 658
 		return err
659 659
 	}
660 660
 
661
+	if err := validateNetworkingConfig(n, endpointConfig); err != nil {
662
+		return err
663
+	}
664
+
661 665
 	if updateSettings {
662 666
 		if err := daemon.updateNetworkSettings(container, n); err != nil {
663 667
 			return err
664 668
 		}
665 669
 	}
666 670
 
671
+	if endpointConfig != nil {
672
+		container.NetworkSettings.Networks[n.Name()] = endpointConfig
673
+	}
674
+
667 675
 	ep, err := container.GetEndpointInNetwork(n)
668 676
 	if err == nil {
669 677
 		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) {
869 869
 
870 870
 	sid := container.NetworkSettings.SandboxID
871 871
 	settings := container.NetworkSettings.Networks
872
+	if sid == "" || len(settings) == 0 {
873
+		return
874
+	}
875
+
872 876
 	var networks []libnetwork.Network
873
-	for n := range settings {
877
+	for n, epSettings := range settings {
874 878
 		if nw, err := daemon.FindNetwork(n); err == nil {
875 879
 			networks = append(networks, nw)
876 880
 		}
877
-		settings[n] = &networktypes.EndpointSettings{}
878
-	}
879
-
880
-	container.NetworkSettings = &network.Settings{Networks: settings}
881
-
882
-	if sid == "" || len(settings) == 0 {
883
-		return
881
+		cleanOperationalData(epSettings)
884 882
 	}
885 883
 
886 884
 	sb, err := daemon.netController.SandboxByID(sid)
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/daemon/execdriver/windows"
11 11
 	derr "github.com/docker/docker/errors"
12 12
 	"github.com/docker/docker/layer"
13
+	networktypes "github.com/docker/engine-api/types/network"
13 14
 	"github.com/docker/libnetwork"
14 15
 )
15 16
 
... ...
@@ -18,7 +19,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
18 18
 }
19 19
 
20 20
 // updateContainerNetworkSettings update the network settings
21
-func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container) error {
21
+func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
22 22
 	return nil
23 23
 }
24 24
 
... ...
@@ -27,7 +28,7 @@ func (daemon *Daemon) initializeNetworking(container *container.Container) error
27 27
 }
28 28
 
29 29
 // ConnectToNetwork connects a container to the network
30
-func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string) error {
30
+func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings) error {
31 31
 	return nil
32 32
 }
33 33
 
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	volumestore "github.com/docker/docker/volume/store"
12 12
 	"github.com/docker/engine-api/types"
13 13
 	containertypes "github.com/docker/engine-api/types/container"
14
+	networktypes "github.com/docker/engine-api/types/network"
14 15
 	"github.com/opencontainers/runc/libcontainer/label"
15 16
 )
16 17
 
... ...
@@ -108,7 +109,12 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe
108 108
 		return nil, err
109 109
 	}
110 110
 
111
-	if err := daemon.updateContainerNetworkSettings(container); err != nil {
111
+	var endpointsConfigs map[string]*networktypes.EndpointSettings
112
+	if params.NetworkingConfig != nil {
113
+		endpointsConfigs = params.NetworkingConfig.EndpointsConfig
114
+	}
115
+
116
+	if err := daemon.updateContainerNetworkSettings(container, endpointsConfigs); err != nil {
112 117
 		return nil, err
113 118
 	}
114 119
 
... ...
@@ -150,12 +150,12 @@ func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnet
150 150
 // ConnectContainerToNetwork connects the given container to the given
151 151
 // network. If either cannot be found, an err is returned. If the
152 152
 // network cannot be set up, an err is returned.
153
-func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string) error {
153
+func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error {
154 154
 	container, err := daemon.GetContainer(containerName)
155 155
 	if err != nil {
156 156
 		return err
157 157
 	}
158
-	return daemon.ConnectToNetwork(container, networkName)
158
+	return daemon.ConnectToNetwork(container, networkName, endpointConfig)
159 159
 }
160 160
 
161 161
 // DisconnectContainerFromNetwork disconnects the given container from
... ...
@@ -110,6 +110,8 @@ This section lists each version from latest to oldest.  Each listing includes a
110 110
 * `POST /containers/create` now allows you to set a read/write rate limit for a 
111 111
   device (in bytes per second or IO per second).
112 112
 * `GET /networks` now supports filtering by `name`, `id` and `type`.
113
+* `POST /containers/create` now allows you to set the static IPv4 and/or IPv6 address for the container.
114
+* `POST /networks/(id)/connect` now allows you to set the static IPv4 and/or IPv6 address for the container.
113 115
 
114 116
 ### v1.21 API changes
115 117
 
... ...
@@ -3031,7 +3031,13 @@ POST /networks/22be93d5babb089c5aab8dbc369042fad48ff791584ca2da2100db837a1c7c30/
3031 3031
 Content-Type: application/json
3032 3032
 
3033 3033
 {
3034
-  "Container":"3613f73ba0e4"
3034
+  "Container":"3613f73ba0e4",
3035
+  "endpoint_config": {
3036
+    "test_nw": {
3037
+        "IPv4Address":"172.24.56.89",
3038
+        "IPv6Address":"2001:db8::5689"
3039
+    }
3040
+  }
3035 3041
 }
3036 3042
 ```
3037 3043
 
... ...
@@ -30,11 +30,18 @@ You can also use the `docker run --net=<network-name>` option to start a contain
30 30
 $ docker run -itd --net=multi-host-network busybox
31 31
 ```
32 32
 
33
+You can specify the IP address you want to be assigned to the container's interface.
34
+
35
+```bash
36
+$ docker network connect multi-host-network --ip 10.10.36.122 container2
37
+```
38
+
33 39
 You can pause, restart, and stop containers that are connected to a network.
34 40
 Paused containers remain connected and a revealed by a `network inspect`. When
35 41
 the container is stopped, it does not appear on the network until you restart
36 42
 it. The container's IP address is not guaranteed to remain the same when a
37
-stopped container rejoins the network.
43
+stopped container rejoins the network, unless you specified one when you run
44
+`docker network connect` command.
38 45
 
39 46
 To verify the container is connected, use the `docker network inspect` command. Use `docker network disconnect` to remove a container from the network.
40 47
 
... ...
@@ -56,6 +56,8 @@ parent = "smn_cli"
56 56
       --log-opt=[]                  Log driver specific options
57 57
       -m, --memory=""               Memory limit
58 58
       --mac-address=""              Container MAC address (e.g. 92:d0:c6:0a:29:33)
59
+      --ip=""                       Container IPv4 address (e.g. 172.30.100.104)
60
+      --ip6=""                      Container IPv6 address (e.g. 2001:db8::33)
59 61
       --memory-reservation=""       Memory soft limit
60 62
       --memory-swap=""              A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap.
61 63
       --memory-swappiness=""        Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
... ...
@@ -275,6 +275,8 @@ of the containers.
275 275
                         '<network-name>|<network-id>': connect to a user-defined network
276 276
     --add-host=""    : Add a line to /etc/hosts (host:IP)
277 277
     --mac-address="" : Sets the container's Ethernet device's MAC address
278
+    --ip=""          : Sets the container's Ethernet device's IPv4 address
279
+    --ip6=""          : Sets the container's Ethernet device's IPv6 address
278 280
 
279 281
 By default, all containers have networking enabled and they can make any
280 282
 outgoing connections. The operator can completely disable networking
... ...
@@ -115,8 +115,8 @@ $ docker run -itd --name=container2 busybox
115 115
 Then create an isolated, `bridge` network to test with.
116 116
 
117 117
 ```bash
118
-$ docker network create -d bridge isolated_nw
119
-f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a
118
+$ docker network create -d bridge --subnet 172.25.0.0/16 isolated_nw
119
+06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8
120 120
 ```
121 121
 
122 122
 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
124 124
 ```
125 125
 $ docker network connect isolated_nw container2
126 126
 $ docker network inspect isolated_nw
127
-[[
127
+[
128 128
     {
129 129
         "Name": "isolated_nw",
130
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
130
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
131 131
         "Scope": "local",
132 132
         "Driver": "bridge",
133 133
         "IPAM": {
134 134
             "Driver": "default",
135 135
             "Config": [
136
-                {}
136
+                {
137
+                    "Subnet": "172.25.0.0/16"
138
+                }
137 139
             ]
138 140
         },
139 141
         "Containers": {
140
-            "498eaaaf328e1018042c04b2de04036fc04719a6e39a097a4f4866043a2c2152": {
141
-                "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
142
-                "MacAddress": "02:42:ac:15:00:02",
143
-                "IPv4Address": "172.21.0.2/16",
142
+            "90e1f3ec71caf82ae776a827e0712a68a110a3f175954e5bd4222fd142ac9428": {
143
+                "Name": "container2",
144
+                "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
145
+                "MacAddress": "02:42:ac:19:00:02",
146
+                "IPv4Address": "172.25.0.2/16",
144 147
                 "IPv6Address": ""
145 148
             }
146 149
         },
... ...
@@ -150,20 +153,28 @@ $ docker network inspect isolated_nw
150 150
 ```
151 151
 
152 152
 You can see that the Engine automatically assigns an IP address to `container2`.
153
-If you had specified a `--subnetwork` when creating your network, the network
154
-would have used that addressing. Now, start a third container and connect it to
153
+Given we specified a `--subnet` when creating the network, Engine picked
154
+an address from that same subnet. Now, start a third container and connect it to
155 155
 the network on launch using the `docker run` command's `--net` option:
156 156
 
157 157
 ```bash
158
-$ docker run --net=isolated_nw -itd --name=container3 busybox
159
-c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c
158
+$ docker run --net=isolated_nw --ip=172.25.3.3 -itd --name=container3 busybox
159
+467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551
160 160
 ```
161 161
 
162
+As you can see you were able to specify the ip address for your container.
163
+As long as the network to which the container is connecting was created with
164
+a user specified subnet, you will be able to select the IPv4 and/or IPv6 address(es)
165
+for your container when executing `docker run` and `docker network connect` commands.
166
+The selected IP address is part of the container networking configuration and will be
167
+preserved across container reload. The feature is only available on user defined networks,
168
+because they guarantee their subnets configuration does not change across daemon reload.
169
+
162 170
 Now, inspect the network resources used by `container3`.
163 171
 
164 172
 ```bash
165 173
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container3
166
-{"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"}}
174
+{"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"}}
167 175
 ```
168 176
 Repeat this command for `container2`. If you have Python installed, you can pretty print the output.
169 177
 
... ...
@@ -171,24 +182,26 @@ Repeat this command for `container2`. If you have Python installed, you can pret
171 171
 $ docker inspect --format='{{json .NetworkSettings.Networks}}'  container2 | python -m json.tool
172 172
 {
173 173
     "bridge": {
174
-        "EndpointID": "281b5ead415cf48a6a84fd1a6504342c76e9091fe09b4fdbcc4a01c30b0d3c5b",
174
+        "EndpointID": "0099f9efb5a3727f6a554f176b1e96fca34cae773da68b3b6a26d046c12cb365",
175 175
         "Gateway": "172.17.0.1",
176 176
         "GlobalIPv6Address": "",
177 177
         "GlobalIPv6PrefixLen": 0,
178
+        "IPAMConfig": null,
178 179
         "IPAddress": "172.17.0.3",
179 180
         "IPPrefixLen": 16,
180 181
         "IPv6Gateway": "",
181 182
         "MacAddress": "02:42:ac:11:00:03"
182 183
     },
183 184
     "isolated_nw": {
184
-        "EndpointID": "0e24479cfaafb029104999b4e120858a07b19b1b6d956ae56811033e45d68ad9",
185
-        "Gateway": "172.21.0.1",
185
+        "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d",
186
+        "Gateway": "172.25.0.1",
186 187
         "GlobalIPv6Address": "",
187 188
         "GlobalIPv6PrefixLen": 0,
188
-        "IPAddress": "172.21.0.2",
189
+        "IPAMConfig": null,
190
+        "IPAddress": "172.25.0.2",
189 191
         "IPPrefixLen": 16,
190 192
         "IPv6Gateway": "",
191
-        "MacAddress": "02:42:ac:15:00:02"
193
+        "MacAddress": "02:42:ac:19:00:02"
192 194
     }
193 195
 }
194 196
 ```
... ...
@@ -223,8 +236,8 @@ eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:03
223 223
           RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)
224 224
 
225 225
 eth1      Link encap:Ethernet  HWaddr 02:42:AC:15:00:02  
226
-          inet addr:172.21.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
227
-          inet6 addr: fe80::42:acff:fe15:2/64 Scope:Link
226
+          inet addr:172.25.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
227
+          inet6 addr: fe80::42:acff:fe19:2/64 Scope:Link
228 228
           UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
229 229
           RX packets:8 errors:0 dropped:0 overruns:0 frame:0
230 230
           TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
... ...
@@ -252,19 +265,19 @@ fe00::0	ip6-localnet
252 252
 ff00::0	ip6-mcastprefix
253 253
 ff02::1	ip6-allnodes
254 254
 ff02::2	ip6-allrouters
255
-172.21.0.3	container3
256
-172.21.0.3	container3.isolated_nw
255
+172.21.3.3	container3
256
+172.21.3.3	container3.isolated_nw
257 257
 ```
258 258
 
259 259
 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.
260 260
 
261 261
 ```bash
262 262
 / # ping -w 4 container3
263
-PING container3 (172.21.0.3): 56 data bytes
264
-64 bytes from 172.21.0.3: seq=0 ttl=64 time=0.070 ms
265
-64 bytes from 172.21.0.3: seq=1 ttl=64 time=0.080 ms
266
-64 bytes from 172.21.0.3: seq=2 ttl=64 time=0.080 ms
267
-64 bytes from 172.21.0.3: seq=3 ttl=64 time=0.097 ms
263
+PING container3 (172.25.3.3): 56 data bytes
264
+64 bytes from 172.25.3.3: seq=0 ttl=64 time=0.070 ms
265
+64 bytes from 172.25.3.3: seq=1 ttl=64 time=0.080 ms
266
+64 bytes from 172.25.3.3: seq=2 ttl=64 time=0.080 ms
267
+64 bytes from 172.25.3.3: seq=3 ttl=64 time=0.097 ms
268 268
 
269 269
 --- container3 ping statistics ---
270 270
 4 packets transmitted, 4 packets received, 0% packet loss
... ...
@@ -342,23 +355,26 @@ docker inspect --format='{{json .NetworkSettings.Networks}}'  container2 | pytho
342 342
 
343 343
 
344 344
 $ docker network inspect isolated_nw
345
-[[
345
+[
346 346
     {
347 347
         "Name": "isolated_nw",
348
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
348
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
349 349
         "Scope": "local",
350 350
         "Driver": "bridge",
351 351
         "IPAM": {
352 352
             "Driver": "default",
353 353
             "Config": [
354
-                {}
354
+                {
355
+                    "Subnet": "172.25.0.0/16"
356
+                }
355 357
             ]
356 358
         },
357 359
         "Containers": {
358
-            "c282ca437ee7e926a7303a64fc04109740208d2c20e442366139322211a6481c": {
359
-                "EndpointID": "e5d077f9712a69c6929fdd890df5e7c1c649771a50df5b422f7e68f0ae61e847",
360
-                "MacAddress": "02:42:ac:15:00:03",
361
-                "IPv4Address": "172.21.0.3/16",
360
+            "467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551": {
361
+                "Name": "container3",
362
+                "EndpointID": "dffc7ec2915af58cc827d995e6ebdc897342be0420123277103c40ae35579103",
363
+                "MacAddress": "02:42:ac:19:03:03",
364
+                "IPv4Address": "172.25.3.3/16",
362 365
                 "IPv6Address": ""
363 366
             }
364 367
         },
... ...
@@ -393,7 +409,7 @@ lo        Link encap:Local Loopback
393 393
           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
394 394
 
395 395
 / # ping container3
396
-PING container3 (172.20.0.1): 56 data bytes
396
+PING container3 (172.25.3.3): 56 data bytes
397 397
 ^C
398 398
 --- container3 ping statistics ---
399 399
 2 packets transmitted, 0 packets received, 100% packet loss
... ...
@@ -426,13 +442,15 @@ docker network inspect isolated_nw
426 426
 [
427 427
     {
428 428
         "Name": "isolated_nw",
429
-        "Id": "f836c8deb6282ee614eade9d2f42d590e603d0b1efa0d99bd88b88c503e6ba7a",
429
+        "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8",
430 430
         "Scope": "local",
431 431
         "Driver": "bridge",
432 432
         "IPAM": {
433 433
             "Driver": "default",
434 434
             "Config": [
435
-                {}
435
+                {
436
+                    "Subnet": "172.25.0.0/16"
437
+                }
436 438
             ]
437 439
         },
438 440
         "Containers": {},
... ...
@@ -962,3 +962,71 @@ func (s *DockerNetworkSuite) TestDockerNetworkRestartWithMulipleNetworks(c *chec
962 962
 	c.Assert(networks, checker.Contains, "bridge", check.Commentf("Should contain 'bridge' network"))
963 963
 	c.Assert(networks, checker.Contains, "test", check.Commentf("Should contain 'test' netwokr"))
964 964
 }
965
+
966
+func (s *DockerNetworkSuite) TestDockerNetworkConnectPreferredIP(c *check.C) {
967
+	// create two networks
968
+	dockerCmd(c, "network", "create", "--subnet=172.28.0.0/16", "--subnet=2001:db8:1234::/64", "n0")
969
+	assertNwIsAvailable(c, "n0")
970
+
971
+	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")
972
+	assertNwIsAvailable(c, "n1")
973
+
974
+	// run a container on first network specifying the ip addresses
975
+	dockerCmd(c, "run", "-d", "--name", "c0", "--net=n0", "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
976
+	c.Assert(waitRun("c0"), check.IsNil)
977
+	verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
978
+
979
+	// connect the container to the second network specifying the preferred ip addresses
980
+	dockerCmd(c, "network", "connect", "--ip", "172.30.55.44", "--ip6", "2001:db8:abcd::5544", "n1", "c0")
981
+	verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
982
+
983
+	// Stop and restart the container
984
+	dockerCmd(c, "stop", "c0")
985
+	dockerCmd(c, "start", "c0")
986
+
987
+	// verify preferred addresses are applied
988
+	verifyIPAddresses(c, "c0", "n0", "172.28.99.88", "2001:db8:1234::9988")
989
+	verifyIPAddresses(c, "c0", "n1", "172.30.55.44", "2001:db8:abcd::5544")
990
+
991
+	// Still it should fail to connect to the default network with a specified IP (whatever ip)
992
+	out, _, err := dockerCmdWithError("network", "connect", "--ip", "172.21.55.44", "bridge", "c0")
993
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
994
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
995
+
996
+}
997
+
998
+func (s *DockerNetworkSuite) TestDockerNetworkUnsupportedPreferredIP(c *check.C) {
999
+	// preferred IP is not supported on predefined networks
1000
+	for _, mode := range []string{"none", "host", "bridge"} {
1001
+		checkUnsupportedNetworkAndIP(c, mode)
1002
+	}
1003
+
1004
+	// preferred IP is not supported on networks with no user defined subnets
1005
+	dockerCmd(c, "network", "create", "n0")
1006
+	assertNwIsAvailable(c, "n0")
1007
+
1008
+	out, _, err := dockerCmdWithError("run", "-d", "--ip", "172.28.99.88", "--net", "n0", "busybox", "top")
1009
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
1010
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
1011
+
1012
+	out, _, err = dockerCmdWithError("run", "-d", "--ip6", "2001:db8:1234::9988", "--net", "n0", "busybox", "top")
1013
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
1014
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkNoSubnetAndIP.Error())
1015
+
1016
+	dockerCmd(c, "network", "rm", "n0")
1017
+	assertNwNotAvailable(c, "n0")
1018
+}
1019
+
1020
+func checkUnsupportedNetworkAndIP(c *check.C, nwMode string) {
1021
+	out, _, err := dockerCmdWithError("run", "-d", "--net", nwMode, "--ip", "172.28.99.88", "--ip6", "2001:db8:1234::9988", "busybox", "top")
1022
+	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
1023
+	c.Assert(out, checker.Contains, runconfig.ErrUnsupportedNetworkAndIP.Error())
1024
+}
1025
+
1026
+func verifyIPAddresses(c *check.C, cName, nwname, ipv4, ipv6 string) {
1027
+	out, _ := dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.IPAddress }}'", nwname), cName)
1028
+	c.Assert(strings.TrimSpace(out), check.Equals, ipv4)
1029
+
1030
+	out, _ = dockerCmd(c, "inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.%s.GlobalIPv6Address }}'", nwname), cName)
1031
+	c.Assert(strings.TrimSpace(out), check.Equals, ipv6)
1032
+}
... ...
@@ -7,18 +7,19 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/volume"
9 9
 	"github.com/docker/engine-api/types/container"
10
+	networktypes "github.com/docker/engine-api/types/network"
10 11
 )
11 12
 
12 13
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
13 14
 // struct and returns both a Config and an HostConfig struct
14 15
 // Be aware this function is not checking whether the resulted structs are nil,
15 16
 // it's your business to do so
16
-func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, error) {
17
+func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
17 18
 	var w ContainerConfigWrapper
18 19
 
19 20
 	decoder := json.NewDecoder(src)
20 21
 	if err := decoder.Decode(&w); err != nil {
21
-		return nil, nil, err
22
+		return nil, nil, nil, err
22 23
 	}
23 24
 
24 25
 	hc := w.getHostConfig()
... ...
@@ -33,21 +34,21 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
33 33
 
34 34
 		// Now validate all the volumes and binds
35 35
 		if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
36
-			return nil, nil, err
36
+			return nil, nil, nil, err
37 37
 		}
38 38
 	}
39 39
 
40 40
 	// Certain parameters need daemon-side validation that cannot be done
41 41
 	// on the client, as only the daemon knows what is valid for the platform.
42 42
 	if err := ValidateNetMode(w.Config, hc); err != nil {
43
-		return nil, nil, err
43
+		return nil, nil, nil, err
44 44
 	}
45 45
 
46 46
 	// Validate the isolation level
47 47
 	if err := ValidateIsolationLevel(hc); err != nil {
48
-		return nil, nil, err
48
+		return nil, nil, nil, err
49 49
 	}
50
-	return w.Config, hc, nil
50
+	return w.Config, hc, w.NetworkingConfig, nil
51 51
 }
52 52
 
53 53
 // validateVolumesAndBindSettings validates each of the volumes and bind settings
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	"github.com/docker/engine-api/types/container"
13
+	networktypes "github.com/docker/engine-api/types/network"
13 14
 	"github.com/docker/engine-api/types/strslice"
14 15
 )
15 16
 
... ...
@@ -45,7 +46,7 @@ func TestDecodeContainerConfig(t *testing.T) {
45 45
 			t.Fatal(err)
46 46
 		}
47 47
 
48
-		c, h, err := DecodeContainerConfig(bytes.NewReader(b))
48
+		c, h, _, err := DecodeContainerConfig(bytes.NewReader(b))
49 49
 		if err != nil {
50 50
 			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
51 51
 		}
... ...
@@ -70,29 +71,29 @@ func TestDecodeContainerConfig(t *testing.T) {
70 70
 func TestDecodeContainerConfigIsolation(t *testing.T) {
71 71
 
72 72
 	// An invalid isolation level
73
-	if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
73
+	if _, _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
74 74
 		if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
75 75
 			t.Fatal(err)
76 76
 		}
77 77
 	}
78 78
 
79 79
 	// Blank isolation level (== default)
80
-	if _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
80
+	if _, _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
81 81
 		t.Fatal("Blank isolation should have succeeded")
82 82
 	}
83 83
 
84 84
 	// Default isolation level
85
-	if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
85
+	if _, _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
86 86
 		t.Fatal("default isolation should have succeeded")
87 87
 	}
88 88
 
89 89
 	// Hyper-V Containers isolation level (Valid on Windows only)
90 90
 	if runtime.GOOS == "windows" {
91
-		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
91
+		if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
92 92
 			t.Fatal("hyperv isolation should have succeeded")
93 93
 		}
94 94
 	} else {
95
-		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
95
+		if _, _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
96 96
 			if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
97 97
 				t.Fatal(err)
98 98
 			}
... ...
@@ -102,7 +103,7 @@ func TestDecodeContainerConfigIsolation(t *testing.T) {
102 102
 
103 103
 // callDecodeContainerConfigIsolation is a utility function to call
104 104
 // DecodeContainerConfig for validating isolation levels
105
-func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, error) {
105
+func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
106 106
 	var (
107 107
 		b   []byte
108 108
 		err error
... ...
@@ -114,7 +115,7 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
114 114
 			Isolation:   container.IsolationLevel(isolation)},
115 115
 	}
116 116
 	if b, err = json.Marshal(w); err != nil {
117
-		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
117
+		return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
118 118
 	}
119 119
 	return DecodeContainerConfig(bytes.NewReader(b))
120 120
 }
... ...
@@ -2,15 +2,19 @@
2 2
 
3 3
 package runconfig
4 4
 
5
-import "github.com/docker/engine-api/types/container"
5
+import (
6
+	"github.com/docker/engine-api/types/container"
7
+	networktypes "github.com/docker/engine-api/types/network"
8
+)
6 9
 
7 10
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
8 11
 // and the corresponding HostConfig (non-portable).
9 12
 type ContainerConfigWrapper struct {
10 13
 	*container.Config
11
-	InnerHostConfig       *container.HostConfig `json:"HostConfig,omitempty"`
12
-	Cpuset                string                `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
13
-	*container.HostConfig                       // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
14
+	InnerHostConfig       *container.HostConfig          `json:"HostConfig,omitempty"`
15
+	Cpuset                string                         `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
16
+	NetworkingConfig      *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
17
+	*container.HostConfig                                // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
14 18
 }
15 19
 
16 20
 // getHostConfig gets the HostConfig of the Config.
... ...
@@ -1,12 +1,16 @@
1 1
 package runconfig
2 2
 
3
-import "github.com/docker/engine-api/types/container"
3
+import (
4
+	"github.com/docker/engine-api/types/container"
5
+	networktypes "github.com/docker/engine-api/types/network"
6
+)
4 7
 
5 8
 // ContainerConfigWrapper is a Config wrapper that hold the container Config (portable)
6 9
 // and the corresponding HostConfig (non-portable).
7 10
 type ContainerConfigWrapper struct {
8 11
 	*container.Config
9
-	HostConfig *container.HostConfig `json:"HostConfig,omitempty"`
12
+	HostConfig       *container.HostConfig          `json:"HostConfig,omitempty"`
13
+	NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
10 14
 }
11 15
 
12 16
 // getHostConfig gets the HostConfig of the Config.
... ...
@@ -29,4 +29,8 @@ var (
29 29
 	ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
30 30
 	// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
31 31
 	ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
32
+	// ErrUnsupportedNetworkAndIP conflict between network mode and preferred ip address
33
+	ErrUnsupportedNetworkAndIP = fmt.Errorf("User specified IP address is supported on user defined networks only")
34
+	// ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and preferred ip address
35
+	ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("User specified IP address is supported only when connecting to networks with user configured subnets")
32 36
 )
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/pkg/mount"
12 12
 	"github.com/docker/docker/pkg/signal"
13 13
 	"github.com/docker/engine-api/types/container"
14
+	networktypes "github.com/docker/engine-api/types/network"
14 15
 	"github.com/docker/engine-api/types/strslice"
15 16
 	"github.com/docker/go-connections/nat"
16 17
 	"github.com/docker/go-units"
... ...
@@ -19,7 +20,7 @@ import (
19 19
 // Parse parses the specified args for the specified command and generates a Config,
20 20
 // a HostConfig and returns them with the specified command.
21 21
 // If the specified args are not valid, it will return an error.
22
-func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
22
+func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
23 23
 	var (
24 24
 		// FIXME: use utils.ListOpts for attach and volumes?
25 25
 		flAttach            = opts.NewListOpts(ValidateAttach)
... ...
@@ -77,6 +78,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
77 77
 		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
78 78
 		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
79 79
 		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
80
+		flIPv4Address       = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
81
+		flIPv6Address       = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
80 82
 		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
81 83
 		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
82 84
 		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
119 119
 	cmd.Require(flag.Min, 1)
120 120
 
121 121
 	if err := cmd.ParseFlags(args, true); err != nil {
122
-		return nil, nil, cmd, err
122
+		return nil, nil, nil, cmd, err
123 123
 	}
124 124
 
125 125
 	var (
... ...
@@ -131,7 +134,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
131 131
 	// Validate the input mac address
132 132
 	if *flMacAddress != "" {
133 133
 		if _, err := ValidateMACAddress(*flMacAddress); err != nil {
134
-			return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
134
+			return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
135 135
 		}
136 136
 	}
137 137
 	if *flStdin {
... ...
@@ -149,7 +152,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
149 149
 	if *flMemoryString != "" {
150 150
 		flMemory, err = units.RAMInBytes(*flMemoryString)
151 151
 		if err != nil {
152
-			return nil, nil, cmd, err
152
+			return nil, nil, nil, cmd, err
153 153
 		}
154 154
 	}
155 155
 
... ...
@@ -157,7 +160,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
157 157
 	if *flMemoryReservation != "" {
158 158
 		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
159 159
 		if err != nil {
160
-			return nil, nil, cmd, err
160
+			return nil, nil, nil, cmd, err
161 161
 		}
162 162
 	}
163 163
 
... ...
@@ -168,7 +171,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
168 168
 		} else {
169 169
 			memorySwap, err = units.RAMInBytes(*flMemorySwap)
170 170
 			if err != nil {
171
-				return nil, nil, cmd, err
171
+				return nil, nil, nil, cmd, err
172 172
 			}
173 173
 		}
174 174
 	}
... ...
@@ -177,20 +180,20 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
177 177
 	if *flKernelMemory != "" {
178 178
 		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
179 179
 		if err != nil {
180
-			return nil, nil, cmd, err
180
+			return nil, nil, nil, cmd, err
181 181
 		}
182 182
 	}
183 183
 
184 184
 	swappiness := *flSwappiness
185 185
 	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
186
-		return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
186
+		return nil, nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
187 187
 	}
188 188
 
189 189
 	var shmSize int64
190 190
 	if *flShmSize != "" {
191 191
 		shmSize, err = units.RAMInBytes(*flShmSize)
192 192
 		if err != nil {
193
-			return nil, nil, cmd, err
193
+			return nil, nil, nil, cmd, err
194 194
 		}
195 195
 	}
196 196
 
... ...
@@ -210,7 +213,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
210 210
 	for _, t := range flTmpfs.GetAll() {
211 211
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
212 212
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
213
-				return nil, nil, cmd, err
213
+				return nil, nil, nil, cmd, err
214 214
 			}
215 215
 			tmpfs[arr[0]] = arr[1]
216 216
 		} else {
... ...
@@ -243,13 +246,13 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
243 243
 
244 244
 	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
245 245
 	if err != nil {
246
-		return nil, nil, cmd, err
246
+		return nil, nil, nil, cmd, err
247 247
 	}
248 248
 
249 249
 	// Merge in exposed ports to the map of published ports
250 250
 	for _, e := range flExpose.GetAll() {
251 251
 		if strings.Contains(e, ":") {
252
-			return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
252
+			return nil, nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
253 253
 		}
254 254
 		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
255 255
 		proto, port := nat.SplitProtoPort(e)
... ...
@@ -257,12 +260,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
257 257
 		//if expose a port, the start and end port are the same
258 258
 		start, end, err := nat.ParsePortRange(port)
259 259
 		if err != nil {
260
-			return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
260
+			return nil, nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
261 261
 		}
262 262
 		for i := start; i <= end; i++ {
263 263
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
264 264
 			if err != nil {
265
-				return nil, nil, cmd, err
265
+				return nil, nil, nil, cmd, err
266 266
 			}
267 267
 			if _, exists := ports[p]; !exists {
268 268
 				ports[p] = struct{}{}
... ...
@@ -275,7 +278,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
275 275
 	for _, device := range flDevices.GetAll() {
276 276
 		deviceMapping, err := ParseDevice(device)
277 277
 		if err != nil {
278
-			return nil, nil, cmd, err
278
+			return nil, nil, nil, cmd, err
279 279
 		}
280 280
 		deviceMappings = append(deviceMappings, deviceMapping)
281 281
 	}
... ...
@@ -283,38 +286,38 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
283 283
 	// collect all the environment variables for the container
284 284
 	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
285 285
 	if err != nil {
286
-		return nil, nil, cmd, err
286
+		return nil, nil, nil, cmd, err
287 287
 	}
288 288
 
289 289
 	// collect all the labels for the container
290 290
 	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
291 291
 	if err != nil {
292
-		return nil, nil, cmd, err
292
+		return nil, nil, nil, cmd, err
293 293
 	}
294 294
 
295 295
 	ipcMode := container.IpcMode(*flIpcMode)
296 296
 	if !ipcMode.Valid() {
297
-		return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
297
+		return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
298 298
 	}
299 299
 
300 300
 	pidMode := container.PidMode(*flPidMode)
301 301
 	if !pidMode.Valid() {
302
-		return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
302
+		return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
303 303
 	}
304 304
 
305 305
 	utsMode := container.UTSMode(*flUTSMode)
306 306
 	if !utsMode.Valid() {
307
-		return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
307
+		return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
308 308
 	}
309 309
 
310 310
 	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
311 311
 	if err != nil {
312
-		return nil, nil, cmd, err
312
+		return nil, nil, nil, cmd, err
313 313
 	}
314 314
 
315 315
 	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
316 316
 	if err != nil {
317
-		return nil, nil, cmd, err
317
+		return nil, nil, nil, cmd, err
318 318
 	}
319 319
 
320 320
 	resources := container.Resources{
... ...
@@ -405,7 +408,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
405 405
 	if config.OpenStdin && config.AttachStdin {
406 406
 		config.StdinOnce = true
407 407
 	}
408
-	return config, hostConfig, cmd, nil
408
+
409
+	var networkingConfig *networktypes.NetworkingConfig
410
+	if *flIPv4Address != "" || *flIPv6Address != "" {
411
+		networkingConfig = &networktypes.NetworkingConfig{
412
+			EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
413
+		}
414
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
415
+			IPAMConfig: &networktypes.EndpointIPAMConfig{
416
+				IPv4Address: *flIPv4Address,
417
+				IPv6Address: *flIPv6Address,
418
+			},
419
+		}
420
+	}
421
+
422
+	return config, hostConfig, networkingConfig, cmd, nil
409 423
 }
410 424
 
411 425
 // reads a file of line terminated key=value pairs and override that with override parameter
... ...
@@ -13,10 +13,11 @@ import (
13 13
 	flag "github.com/docker/docker/pkg/mflag"
14 14
 	"github.com/docker/docker/runconfig"
15 15
 	"github.com/docker/engine-api/types/container"
16
+	networktypes "github.com/docker/engine-api/types/network"
16 17
 	"github.com/docker/go-connections/nat"
17 18
 )
18 19
 
19
-func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
20
+func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
20 21
 	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
21 22
 	cmd.SetOutput(ioutil.Discard)
22 23
 	cmd.Usage = nil
... ...
@@ -24,7 +25,7 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.Fl
24 24
 }
25 25
 
26 26
 func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
27
-	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
27
+	config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
28 28
 	return config, hostConfig, err
29 29
 }
30 30
 
... ...
@@ -304,7 +305,7 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
304 304
 	if b, err = json.Marshal(w); err != nil {
305 305
 		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
306 306
 	}
307
-	c, h, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
307
+	c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
308 308
 	if err != nil {
309 309
 		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
310 310
 	}
... ...
@@ -349,7 +350,7 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
349 349
 func TestParseWithMacAddress(t *testing.T) {
350 350
 	invalidMacAddress := "--mac-address=invalidMacAddress"
351 351
 	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
352
-	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
352
+	if _, _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
353 353
 		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
354 354
 	}
355 355
 	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
... ...
@@ -360,7 +361,7 @@ func TestParseWithMacAddress(t *testing.T) {
360 360
 func TestParseWithMemory(t *testing.T) {
361 361
 	invalidMemory := "--memory=invalid"
362 362
 	validMemory := "--memory=1G"
363
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
363
+	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
364 364
 		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
365 365
 	}
366 366
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
... ...
@@ -372,7 +373,7 @@ func TestParseWithMemorySwap(t *testing.T) {
372 372
 	invalidMemory := "--memory-swap=invalid"
373 373
 	validMemory := "--memory-swap=1G"
374 374
 	anotherValidMemory := "--memory-swap=-1"
375
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
375
+	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
376 376
 		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
377 377
 	}
378 378
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
... ...
@@ -417,12 +418,12 @@ func TestParseWithExpose(t *testing.T) {
417 417
 		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
418 418
 	}
419 419
 	for expose, expectedError := range invalids {
420
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
420
+		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
421 421
 			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
422 422
 		}
423 423
 	}
424 424
 	for expose, exposedPorts := range valids {
425
-		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
425
+		config, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
426 426
 		if err != nil {
427 427
 			t.Fatal(err)
428 428
 		}
... ...
@@ -436,7 +437,7 @@ func TestParseWithExpose(t *testing.T) {
436 436
 		}
437 437
 	}
438 438
 	// Merge with actual published port
439
-	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
439
+	config, _, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
440 440
 	if err != nil {
441 441
 		t.Fatal(err)
442 442
 	}
... ...
@@ -475,7 +476,7 @@ func TestParseDevice(t *testing.T) {
475 475
 		},
476 476
 	}
477 477
 	for device, deviceMapping := range valids {
478
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
478
+		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
479 479
 		if err != nil {
480 480
 			t.Fatal(err)
481 481
 		}
... ...
@@ -491,11 +492,11 @@ func TestParseDevice(t *testing.T) {
491 491
 
492 492
 func TestParseModes(t *testing.T) {
493 493
 	// ipc ko
494
-	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
494
+	if _, _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
495 495
 		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
496 496
 	}
497 497
 	// ipc ok
498
-	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
498
+	_, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
499 499
 	if err != nil {
500 500
 		t.Fatal(err)
501 501
 	}
... ...
@@ -503,11 +504,11 @@ func TestParseModes(t *testing.T) {
503 503
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
504 504
 	}
505 505
 	// pid ko
506
-	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
506
+	if _, _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
507 507
 		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
508 508
 	}
509 509
 	// pid ok
510
-	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
510
+	_, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
511 511
 	if err != nil {
512 512
 		t.Fatal(err)
513 513
 	}
... ...
@@ -515,11 +516,11 @@ func TestParseModes(t *testing.T) {
515 515
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
516 516
 	}
517 517
 	// uts ko
518
-	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
518
+	if _, _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
519 519
 		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
520 520
 	}
521 521
 	// uts ok
522
-	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
522
+	_, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
523 523
 	if err != nil {
524 524
 		t.Fatal(err)
525 525
 	}
... ...
@@ -527,11 +528,11 @@ func TestParseModes(t *testing.T) {
527 527
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
528 528
 	}
529 529
 	// shm-size ko
530
-	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
530
+	if _, _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
531 531
 		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
532 532
 	}
533 533
 	// shm-size ok
534
-	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
534
+	_, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
535 535
 	if err != nil {
536 536
 		t.Fatal(err)
537 537
 	}
... ...
@@ -560,12 +561,12 @@ func TestParseRestartPolicy(t *testing.T) {
560 560
 		},
561 561
 	}
562 562
 	for restart, expectedError := range invalids {
563
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
563
+		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
564 564
 			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
565 565
 		}
566 566
 	}
567 567
 	for restart, expected := range valids {
568
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
568
+		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
569 569
 		if err != nil {
570 570
 			t.Fatal(err)
571 571
 		}
... ...
@@ -577,11 +578,11 @@ func TestParseRestartPolicy(t *testing.T) {
577 577
 
578 578
 func TestParseLoggingOpts(t *testing.T) {
579 579
 	// logging opts ko
580
-	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
580
+	if _, _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
581 581
 		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
582 582
 	}
583 583
 	// logging opts ok
584
-	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
584
+	_, hostconfig, _, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
585 585
 	if err != nil {
586 586
 		t.Fatal(err)
587 587
 	}
... ...
@@ -596,18 +597,18 @@ func TestParseEnvfileVariables(t *testing.T) {
596 596
 		e = "open nonexistent: The system cannot find the file specified."
597 597
 	}
598 598
 	// env ko
599
-	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
599
+	if _, _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
600 600
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
601 601
 	}
602 602
 	// env ok
603
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
603
+	config, _, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
604 604
 	if err != nil {
605 605
 		t.Fatal(err)
606 606
 	}
607 607
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
608 608
 		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
609 609
 	}
610
-	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
610
+	config, _, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
611 611
 	if err != nil {
612 612
 		t.Fatal(err)
613 613
 	}
... ...
@@ -622,18 +623,18 @@ func TestParseLabelfileVariables(t *testing.T) {
622 622
 		e = "open nonexistent: The system cannot find the file specified."
623 623
 	}
624 624
 	// label ko
625
-	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
625
+	if _, _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
626 626
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
627 627
 	}
628 628
 	// label ok
629
-	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
629
+	config, _, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
630 630
 	if err != nil {
631 631
 		t.Fatal(err)
632 632
 	}
633 633
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
634 634
 		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
635 635
 	}
636
-	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
636
+	config, _, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
637 637
 	if err != nil {
638 638
 		t.Fatal(err)
639 639
 	}
... ...
@@ -643,7 +644,7 @@ func TestParseLabelfileVariables(t *testing.T) {
643 643
 }
644 644
 
645 645
 func TestParseEntryPoint(t *testing.T) {
646
-	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
646
+	config, _, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
647 647
 	if err != nil {
648 648
 		t.Fatal(err)
649 649
 	}