diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index fa17a8859f..e58624474b 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -720,6 +720,12 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption if len(n.Links) > 0 && copts.links.Len() > 0 { return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) } + if n.IPv4Address != "" && copts.ipv4Address != "" { + return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) + } + if n.IPv6Address != "" && copts.ipv6Address != "" { + return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) + } if copts.aliases.Len() > 0 { n.Aliases = make([]string, copts.aliases.Len()) copy(n.Aliases, copts.aliases.GetAll()) @@ -728,10 +734,12 @@ func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOption n.Links = make([]string, copts.links.Len()) copy(n.Links, copts.links.GetAll()) } - - // TODO add IPv4/IPv6 options to the csv notation for --network, and error-out in case of conflicting options - n.IPv4Address = copts.ipv4Address - n.IPv6Address = copts.ipv6Address + if copts.ipv4Address != "" { + n.IPv4Address = copts.ipv4Address + } + if copts.ipv6Address != "" { + n.IPv6Address = copts.ipv6Address + } // TODO should linkLocalIPs be added to the _first_ network only, or to _all_ networks? (should this be a per-network option as well?) if copts.linkLocalIPs.Len() > 0 { diff --git a/cli/command/container/opts_test.go b/cli/command/container/opts_test.go index bf6387162c..28083911ea 100644 --- a/cli/command/container/opts_test.go +++ b/cli/command/container/opts_test.go @@ -449,7 +449,7 @@ func TestParseNetworkConfig(t *testing.T) { "--network-alias", "web1", "--network-alias", "web2", "--network", "net2", - "--network", "name=net3,alias=web3,driver-opt=field3=value3", + "--network", "name=net3,alias=web3,driver-opt=field3=value3,ip=172.20.88.22,ip6=2001:db8::8822", }, expected: map[string]*networktypes.EndpointSettings{ "net1": { @@ -465,20 +465,28 @@ func TestParseNetworkConfig(t *testing.T) { "net2": {}, "net3": { DriverOpts: map[string]string{"field3": "value3"}, - Aliases: []string{"web3"}, + IPAMConfig: &networktypes.EndpointIPAMConfig{ + IPv4Address: "172.20.88.22", + IPv6Address: "2001:db8::8822", + }, + Aliases: []string{"web3"}, }, }, expectedCfg: container.HostConfig{NetworkMode: "net1"}, }, { name: "single-network-advanced-with-options", - flags: []string{"--network", "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2"}, + flags: []string{"--network", "name=net1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2,ip=172.20.88.22,ip6=2001:db8::8822"}, expected: map[string]*networktypes.EndpointSettings{ "net1": { DriverOpts: map[string]string{ "field1": "value1", "field2": "value2", }, + IPAMConfig: &networktypes.EndpointIPAMConfig{ + IPv4Address: "172.20.88.22", + IPv6Address: "2001:db8::8822", + }, Aliases: []string{"web1", "web2"}, }, }, @@ -496,10 +504,20 @@ func TestParseNetworkConfig(t *testing.T) { expectedErr: `network "duplicate" is specified multiple times`, }, { - name: "conflict-options", + name: "conflict-options-alias", flags: []string{"--network", "name=net1,alias=web1", "--network-alias", "web1"}, expectedErr: `conflicting options: cannot specify both --network-alias and per-network alias`, }, + { + name: "conflict-options-ip", + flags: []string{"--network", "name=net1,ip=172.20.88.22,ip6=2001:db8::8822", "--ip", "172.20.88.22"}, + expectedErr: `conflicting options: cannot specify both --ip and per-network IPv4 address`, + }, + { + name: "conflict-options-ip6", + flags: []string{"--network", "name=net1,ip=172.20.88.22,ip6=2001:db8::8822", "--ip6", "2001:db8::8822"}, + expectedErr: `conflicting options: cannot specify both --ip6 and per-network IPv6 address`, + }, { name: "invalid-mixed-network-types", flags: []string{"--network", "name=host", "--network", "net1"}, diff --git a/opts/network.go b/opts/network.go index 4f5b53b1b6..ce7370ee0e 100644 --- a/opts/network.go +++ b/opts/network.go @@ -8,9 +8,11 @@ import ( ) const ( - networkOptName = "name" - networkOptAlias = "alias" - driverOpt = "driver-opt" + networkOptName = "name" + networkOptAlias = "alias" + networkOptIPv4Address = "ip" + networkOptIPv6Address = "ip6" + driverOpt = "driver-opt" ) // NetworkAttachmentOpts represents the network options for endpoint creation @@ -19,8 +21,8 @@ type NetworkAttachmentOpts struct { Aliases []string DriverOpts map[string]string Links []string // TODO add support for links in the csv notation of `--network` - IPv4Address string // TODO add support for IPv4-address in the csv notation of `--network` - IPv6Address string // TODO add support for IPv6-address in the csv notation of `--network` + IPv4Address string + IPv6Address string LinkLocalIPs []string // TODO add support for LinkLocalIPs in the csv notation of `--network` ? } @@ -60,6 +62,10 @@ func (n *NetworkOpt) Set(value string) error { netOpt.Target = value case networkOptAlias: netOpt.Aliases = append(netOpt.Aliases, value) + case networkOptIPv4Address: + netOpt.IPv4Address = value + case networkOptIPv6Address: + netOpt.IPv6Address = value case driverOpt: key, value, err = parseDriverOpt(value) if err == nil { diff --git a/opts/network_test.go b/opts/network_test.go index 1867d8ac8b..00a1ff1937 100644 --- a/opts/network_test.go +++ b/opts/network_test.go @@ -58,6 +58,17 @@ func TestNetworkOptAdvancedSyntax(t *testing.T) { }, }, }, + { + value: "name=docknet1,ip=172.20.88.22,ip6=2001:db8::8822", + expected: []NetworkAttachmentOpts{ + { + Target: "docknet1", + Aliases: []string{}, + IPv4Address: "172.20.88.22", + IPv6Address: "2001:db8::8822", + }, + }, + }, { value: "name=docknet1", expected: []NetworkAttachmentOpts{