diff --git a/cli/command/network/create.go b/cli/command/network/create.go index 69374c5880..e43b183190 100644 --- a/cli/command/network/create.go +++ b/cli/command/network/create.go @@ -24,6 +24,7 @@ type createOptions struct { driverOpts opts.MapOpts labels opts.ListOpts internal bool + ipv4 *bool ipv6 *bool attachable bool ingress bool @@ -42,7 +43,7 @@ type ipamOptions struct { } func newCreateCommand(dockerCLI command.Cli) *cobra.Command { - var ipv6 bool + var ipv4, ipv6 bool options := createOptions{ driverOpts: *opts.NewMapOpts(nil, nil), labels: opts.NewListOpts(opts.ValidateLabel), @@ -59,6 +60,9 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { options.name = args[0] + if cmd.Flag("ipv4").Changed { + options.ipv4 = &ipv4 + } if cmd.Flag("ipv6").Changed { options.ipv6 = &ipv6 } @@ -73,7 +77,8 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command { flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options") flags.Var(&options.labels, "label", "Set metadata on a network") flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network") - flags.BoolVar(&ipv6, "ipv6", false, "Enable or disable IPv6 networking") + flags.BoolVar(&ipv4, "ipv4", true, "Enable or disable IPv4 address assignment") + flags.BoolVar(&ipv6, "ipv6", false, "Enable or disable IPv6 address assignment") flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment") flags.SetAnnotation("attachable", "version", []string{"1.25"}) flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network") @@ -113,6 +118,7 @@ func runCreate(ctx context.Context, apiClient client.NetworkAPIClient, output io Options: options.driverOpts.GetAll(), IPAM: ipamCfg, Internal: options.internal, + EnableIPv4: options.ipv4, EnableIPv6: options.ipv6, Attachable: options.attachable, Ingress: options.ingress, diff --git a/cli/command/network/create_test.go b/cli/command/network/create_test.go index 651767d65e..a6eea97d82 100644 --- a/cli/command/network/create_test.go +++ b/cli/command/network/create_test.go @@ -182,6 +182,60 @@ func TestNetworkCreateWithFlags(t *testing.T) { assert.Check(t, is.Equal("banana", strings.TrimSpace(cli.OutBuffer().String()))) } +// TestNetworkCreateIPv4 verifies behavior of the "--ipv4" option. This option +// is an optional bool, and must default to "nil", not "true" or "false". +func TestNetworkCreateIPv4(t *testing.T) { + strPtr := func(val bool) *bool { return &val } + + tests := []struct { + doc, name string + flags []string + expected *bool + }{ + { + doc: "IPv4 default", + name: "ipv4-default", + expected: nil, + }, + { + doc: "IPv4 enabled", + name: "ipv4-enabled", + flags: []string{"--ipv4=true"}, + expected: strPtr(true), + }, + { + doc: "IPv4 enabled (shorthand)", + name: "ipv4-enabled-shorthand", + flags: []string{"--ipv4"}, + expected: strPtr(true), + }, + { + doc: "IPv4 disabled", + name: "ipv4-disabled", + flags: []string{"--ipv4=false"}, + expected: strPtr(false), + }, + } + + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{ + networkCreateFunc: func(ctx context.Context, name string, createBody network.CreateOptions) (network.CreateResponse, error) { + assert.Check(t, is.DeepEqual(createBody.EnableIPv4, tc.expected)) + return network.CreateResponse{ID: name}, nil + }, + }) + cmd := newCreateCommand(cli) + cmd.SetArgs([]string{tc.name}) + if tc.expected != nil { + assert.Check(t, cmd.ParseFlags(tc.flags)) + } + assert.NilError(t, cmd.Execute()) + assert.Check(t, is.Equal(tc.name, strings.TrimSpace(cli.OutBuffer().String()))) + }) + } +} + // TestNetworkCreateIPv6 verifies behavior of the "--ipv6" option. This option // is an optional bool, and must default to "nil", not "true" or "false". func TestNetworkCreateIPv6(t *testing.T) { diff --git a/cli/command/network/formatter.go b/cli/command/network/formatter.go index 3e62463361..fb2177e7ab 100644 --- a/cli/command/network/formatter.go +++ b/cli/command/network/formatter.go @@ -13,6 +13,7 @@ const ( defaultNetworkTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.Scope}}" networkIDHeader = "NETWORK ID" + ipv4Header = "IPV4" ipv6Header = "IPV6" internalHeader = "INTERNAL" ) @@ -51,6 +52,7 @@ func FormatWrite(ctx formatter.Context, networks []network.Summary) error { "Name": formatter.NameHeader, "Driver": formatter.DriverHeader, "Scope": formatter.ScopeHeader, + "IPv4": ipv4Header, "IPv6": ipv6Header, "Internal": internalHeader, "Labels": formatter.LabelsHeader, @@ -88,6 +90,10 @@ func (c *networkContext) Scope() string { return c.n.Scope } +func (c *networkContext) IPv4() string { + return strconv.FormatBool(c.n.EnableIPv4) +} + func (c *networkContext) IPv6() string { return strconv.FormatBool(c.n.EnableIPv6) } diff --git a/cli/command/network/formatter_test.go b/cli/command/network/formatter_test.go index 018f789b17..42afe43a1e 100644 --- a/cli/command/network/formatter_test.go +++ b/cli/command/network/formatter_test.go @@ -42,6 +42,9 @@ func TestNetworkContext(t *testing.T) { {networkContext{ n: network.Summary{Driver: "driver_name"}, }, "driver_name", ctx.Driver}, + {networkContext{ + n: network.Summary{EnableIPv4: true}, + }, "true", ctx.IPv4}, {networkContext{ n: network.Summary{EnableIPv6: true}, }, "true", ctx.IPv6}, @@ -180,8 +183,8 @@ func TestNetworkContextWriteJSON(t *testing.T) { {ID: "networkID2", Name: "foobar_bar"}, } expectedJSONs := []map[string]any{ - {"Driver": "", "ID": "networkID1", "IPv6": "false", "Internal": "false", "Labels": "", "Name": "foobar_baz", "Scope": "", "CreatedAt": "0001-01-01 00:00:00 +0000 UTC"}, - {"Driver": "", "ID": "networkID2", "IPv6": "false", "Internal": "false", "Labels": "", "Name": "foobar_bar", "Scope": "", "CreatedAt": "0001-01-01 00:00:00 +0000 UTC"}, + {"Driver": "", "ID": "networkID1", "IPv4": "false", "IPv6": "false", "Internal": "false", "Labels": "", "Name": "foobar_baz", "Scope": "", "CreatedAt": "0001-01-01 00:00:00 +0000 UTC"}, + {"Driver": "", "ID": "networkID2", "IPv4": "false", "IPv6": "false", "Internal": "false", "Labels": "", "Name": "foobar_bar", "Scope": "", "CreatedAt": "0001-01-01 00:00:00 +0000 UTC"}, } out := bytes.NewBufferString("") diff --git a/docs/reference/commandline/network_create.md b/docs/reference/commandline/network_create.md index e76baa31b2..4d3fa3c4a9 100644 --- a/docs/reference/commandline/network_create.md +++ b/docs/reference/commandline/network_create.md @@ -18,7 +18,8 @@ Create a network | `--ip-range` | `stringSlice` | | Allocate container ip from a sub-range | | `--ipam-driver` | `string` | `default` | IP Address Management Driver | | `--ipam-opt` | `map` | `map[]` | Set IPAM driver specific options | -| `--ipv6` | `bool` | | Enable or disable IPv6 networking | +| `--ipv4` | `bool` | `true` | Enable or disable IPv4 address assignment | +| `--ipv6` | `bool` | | Enable or disable IPv6 address assignment | | `--label` | `list` | | Set metadata on a network | | `-o`, `--opt` | `map` | `map[]` | Set driver specific options | | `--scope` | `string` | | Control the network's scope | @@ -167,11 +168,14 @@ flags used for the docker0 bridge: | Network create option | Daemon option for `docker0` | Description | |-----------------------|-----------------------------|--------------------------------------------| -| `--gateway` | - | IPv4 or IPv6 Gateway for the master subnet | -| `--ip-range` | `--fixed-cidr` | Allocate IPs from a range | -| `--internal` | - | Restrict external access to the network | -| `--ipv6` | `--ipv6` | Enable or disable IPv6 networking | -| `--subnet` | `--bip` | Subnet for network | +| Network create option | Daemon option for `docker0` | Description | +|-----------------------|-----------------------------------|--------------------------------------------| +| `--gateway` | - | IPv4 or IPv6 Gateway for the master subnet | +| `--ip-range` | `--fixed-cidr`, `--fixed-cidr-v6` | Allocate IP addresses from a range | +| `--internal` | - | Restrict external access to the network | +| `--ipv4` | - | Enable or disable IPv4 address assignment | +| `--ipv6` | `--ipv6` | Enable or disable IPv6 address assignment | +| `--subnet` | `--bip`, `--bip6` | Subnet for network | For example, let's use `-o` or `--opt` options to specify an IP address binding when publishing ports: