Browse code

Migrate network command to cobra

- Migrates network command and subcommands (connect, create, disconnect,
inspect, list and remove) to spf13/cobra
- Create a RequiredExactArgs helper function for command that require an
exact number of arguments.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Vincent Demeester authored on 2016/06/06 17:28:52
Showing 14 changed files
... ...
@@ -3,42 +3,35 @@ package client
3 3
 // Command returns a cli command handler if one exists
4 4
 func (cli *DockerCli) Command(name string) func(...string) error {
5 5
 	return map[string]func(...string) error{
6
-		"attach":             cli.CmdAttach,
7
-		"build":              cli.CmdBuild,
8
-		"commit":             cli.CmdCommit,
9
-		"cp":                 cli.CmdCp,
10
-		"events":             cli.CmdEvents,
11
-		"exec":               cli.CmdExec,
12
-		"history":            cli.CmdHistory,
13
-		"images":             cli.CmdImages,
14
-		"import":             cli.CmdImport,
15
-		"info":               cli.CmdInfo,
16
-		"inspect":            cli.CmdInspect,
17
-		"kill":               cli.CmdKill,
18
-		"load":               cli.CmdLoad,
19
-		"login":              cli.CmdLogin,
20
-		"logout":             cli.CmdLogout,
21
-		"network":            cli.CmdNetwork,
22
-		"network create":     cli.CmdNetworkCreate,
23
-		"network connect":    cli.CmdNetworkConnect,
24
-		"network disconnect": cli.CmdNetworkDisconnect,
25
-		"network inspect":    cli.CmdNetworkInspect,
26
-		"network ls":         cli.CmdNetworkLs,
27
-		"network rm":         cli.CmdNetworkRm,
28
-		"pause":              cli.CmdPause,
29
-		"port":               cli.CmdPort,
30
-		"ps":                 cli.CmdPs,
31
-		"pull":               cli.CmdPull,
32
-		"push":               cli.CmdPush,
33
-		"rename":             cli.CmdRename,
34
-		"restart":            cli.CmdRestart,
35
-		"rm":                 cli.CmdRm,
36
-		"save":               cli.CmdSave,
37
-		"stats":              cli.CmdStats,
38
-		"tag":                cli.CmdTag,
39
-		"top":                cli.CmdTop,
40
-		"update":             cli.CmdUpdate,
41
-		"version":            cli.CmdVersion,
42
-		"wait":               cli.CmdWait,
6
+		"attach":  cli.CmdAttach,
7
+		"build":   cli.CmdBuild,
8
+		"commit":  cli.CmdCommit,
9
+		"cp":      cli.CmdCp,
10
+		"events":  cli.CmdEvents,
11
+		"exec":    cli.CmdExec,
12
+		"history": cli.CmdHistory,
13
+		"images":  cli.CmdImages,
14
+		"import":  cli.CmdImport,
15
+		"info":    cli.CmdInfo,
16
+		"inspect": cli.CmdInspect,
17
+		"kill":    cli.CmdKill,
18
+		"load":    cli.CmdLoad,
19
+		"login":   cli.CmdLogin,
20
+		"logout":  cli.CmdLogout,
21
+		"pause":   cli.CmdPause,
22
+		"port":    cli.CmdPort,
23
+		"ps":      cli.CmdPs,
24
+		"pull":    cli.CmdPull,
25
+		"push":    cli.CmdPush,
26
+		"rename":  cli.CmdRename,
27
+		"restart": cli.CmdRestart,
28
+		"rm":      cli.CmdRm,
29
+		"save":    cli.CmdSave,
30
+		"stats":   cli.CmdStats,
31
+		"tag":     cli.CmdTag,
32
+		"top":     cli.CmdTop,
33
+		"update":  cli.CmdUpdate,
34
+		"version": cli.CmdVersion,
35
+		"wait":    cli.CmdWait,
43 36
 	}[name]
44 37
 }
45 38
deleted file mode 100644
... ...
@@ -1,397 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"fmt"
5
-	"net"
6
-	"sort"
7
-	"strings"
8
-	"text/tabwriter"
9
-
10
-	"golang.org/x/net/context"
11
-
12
-	"github.com/docker/docker/api/client/inspect"
13
-	Cli "github.com/docker/docker/cli"
14
-	"github.com/docker/docker/opts"
15
-	flag "github.com/docker/docker/pkg/mflag"
16
-	"github.com/docker/docker/pkg/stringid"
17
-	runconfigopts "github.com/docker/docker/runconfig/opts"
18
-	"github.com/docker/engine-api/types"
19
-	"github.com/docker/engine-api/types/filters"
20
-	"github.com/docker/engine-api/types/network"
21
-)
22
-
23
-// CmdNetwork is the parent subcommand for all network commands
24
-//
25
-// Usage: docker network <COMMAND> [OPTIONS]
26
-func (cli *DockerCli) CmdNetwork(args ...string) error {
27
-	cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
28
-	cmd.Require(flag.Min, 1)
29
-	err := cmd.ParseFlags(args, true)
30
-	cmd.Usage()
31
-	return err
32
-}
33
-
34
-// CmdNetworkCreate creates a new network with a given name
35
-//
36
-// Usage: docker network create [OPTIONS] <NETWORK-NAME>
37
-func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
38
-	cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
39
-	flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network")
40
-	flOpts := opts.NewMapOpts(nil, nil)
41
-
42
-	flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver")
43
-	flIpamSubnet := opts.NewListOpts(nil)
44
-	flIpamIPRange := opts.NewListOpts(nil)
45
-	flIpamGateway := opts.NewListOpts(nil)
46
-	flIpamAux := opts.NewMapOpts(nil, nil)
47
-	flIpamOpt := opts.NewMapOpts(nil, nil)
48
-	flLabels := opts.NewListOpts(nil)
49
-
50
-	cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
51
-	cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
52
-	cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet")
53
-	cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
54
-	cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
55
-	cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options")
56
-	cmd.Var(&flLabels, []string{"-label"}, "set metadata on a network")
57
-
58
-	flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network")
59
-	flIPv6 := cmd.Bool([]string{"-ipv6"}, false, "enable IPv6 networking")
60
-
61
-	cmd.Require(flag.Exact, 1)
62
-	err := cmd.ParseFlags(args, true)
63
-	if err != nil {
64
-		return err
65
-	}
66
-
67
-	// Set the default driver to "" if the user didn't set the value.
68
-	// That way we can know whether it was user input or not.
69
-	driver := *flDriver
70
-	if !cmd.IsSet("-driver") && !cmd.IsSet("d") {
71
-		driver = ""
72
-	}
73
-
74
-	ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll())
75
-	if err != nil {
76
-		return err
77
-	}
78
-
79
-	// Construct network create request body
80
-	nc := types.NetworkCreate{
81
-		Driver:         driver,
82
-		IPAM:           network.IPAM{Driver: *flIpamDriver, Config: ipamCfg, Options: flIpamOpt.GetAll()},
83
-		Options:        flOpts.GetAll(),
84
-		CheckDuplicate: true,
85
-		Internal:       *flInternal,
86
-		EnableIPv6:     *flIPv6,
87
-		Labels:         runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()),
88
-	}
89
-
90
-	resp, err := cli.client.NetworkCreate(context.Background(), cmd.Arg(0), nc)
91
-	if err != nil {
92
-		return err
93
-	}
94
-	fmt.Fprintf(cli.out, "%s\n", resp.ID)
95
-	return nil
96
-}
97
-
98
-// CmdNetworkRm deletes one or more networks
99
-//
100
-// Usage: docker network rm NETWORK-NAME|NETWORK-ID [NETWORK-NAME|NETWORK-ID...]
101
-func (cli *DockerCli) CmdNetworkRm(args ...string) error {
102
-	cmd := Cli.Subcmd("network rm", []string{"NETWORK [NETWORK...]"}, "Deletes one or more networks", false)
103
-	cmd.Require(flag.Min, 1)
104
-	if err := cmd.ParseFlags(args, true); err != nil {
105
-		return err
106
-	}
107
-
108
-	ctx := context.Background()
109
-
110
-	status := 0
111
-	for _, net := range cmd.Args() {
112
-		if err := cli.client.NetworkRemove(ctx, net); err != nil {
113
-			fmt.Fprintf(cli.err, "%s\n", err)
114
-			status = 1
115
-			continue
116
-		}
117
-		fmt.Fprintf(cli.out, "%s\n", net)
118
-	}
119
-	if status != 0 {
120
-		return Cli.StatusError{StatusCode: status}
121
-	}
122
-	return nil
123
-}
124
-
125
-// CmdNetworkConnect connects a container to a network
126
-//
127
-// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
128
-func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
129
-	cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
130
-	flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
131
-	flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
132
-	flLinks := opts.NewListOpts(runconfigopts.ValidateLink)
133
-	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
134
-	flAliases := opts.NewListOpts(nil)
135
-	cmd.Var(&flAliases, []string{"-alias"}, "Add network-scoped alias for the container")
136
-	cmd.Require(flag.Min, 2)
137
-	if err := cmd.ParseFlags(args, true); err != nil {
138
-		return err
139
-	}
140
-	epConfig := &network.EndpointSettings{
141
-		IPAMConfig: &network.EndpointIPAMConfig{
142
-			IPv4Address: *flIPAddress,
143
-			IPv6Address: *flIPv6Address,
144
-		},
145
-		Links:   flLinks.GetAll(),
146
-		Aliases: flAliases.GetAll(),
147
-	}
148
-	return cli.client.NetworkConnect(context.Background(), cmd.Arg(0), cmd.Arg(1), epConfig)
149
-}
150
-
151
-// CmdNetworkDisconnect disconnects a container from a network
152
-//
153
-// Usage: docker network disconnect <NETWORK> <CONTAINER>
154
-func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
155
-	cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
156
-	force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a network")
157
-	cmd.Require(flag.Exact, 2)
158
-	if err := cmd.ParseFlags(args, true); err != nil {
159
-		return err
160
-	}
161
-
162
-	return cli.client.NetworkDisconnect(context.Background(), cmd.Arg(0), cmd.Arg(1), *force)
163
-}
164
-
165
-// CmdNetworkLs lists all the networks managed by docker daemon
166
-//
167
-// Usage: docker network ls [OPTIONS]
168
-func (cli *DockerCli) CmdNetworkLs(args ...string) error {
169
-	cmd := Cli.Subcmd("network ls", nil, "Lists networks", true)
170
-	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
171
-	noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
172
-
173
-	flFilter := opts.NewListOpts(nil)
174
-	cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
175
-
176
-	cmd.Require(flag.Exact, 0)
177
-	err := cmd.ParseFlags(args, true)
178
-	if err != nil {
179
-		return err
180
-	}
181
-
182
-	// Consolidate all filter flags, and sanity check them early.
183
-	// They'll get process after get response from server.
184
-	netFilterArgs := filters.NewArgs()
185
-	for _, f := range flFilter.GetAll() {
186
-		if netFilterArgs, err = filters.ParseFlag(f, netFilterArgs); err != nil {
187
-			return err
188
-		}
189
-	}
190
-
191
-	options := types.NetworkListOptions{
192
-		Filters: netFilterArgs,
193
-	}
194
-
195
-	networkResources, err := cli.client.NetworkList(context.Background(), options)
196
-	if err != nil {
197
-		return err
198
-	}
199
-
200
-	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
201
-
202
-	// unless quiet (-q) is specified, print field titles
203
-	if !*quiet {
204
-		fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
205
-	}
206
-	sort.Sort(byNetworkName(networkResources))
207
-	for _, networkResource := range networkResources {
208
-		ID := networkResource.ID
209
-		netName := networkResource.Name
210
-		if !*noTrunc {
211
-			ID = stringid.TruncateID(ID)
212
-		}
213
-		if *quiet {
214
-			fmt.Fprintln(wr, ID)
215
-			continue
216
-		}
217
-		driver := networkResource.Driver
218
-		fmt.Fprintf(wr, "%s\t%s\t%s\t",
219
-			ID,
220
-			netName,
221
-			driver)
222
-		fmt.Fprint(wr, "\n")
223
-	}
224
-	wr.Flush()
225
-	return nil
226
-}
227
-
228
-type byNetworkName []types.NetworkResource
229
-
230
-func (r byNetworkName) Len() int           { return len(r) }
231
-func (r byNetworkName) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
232
-func (r byNetworkName) Less(i, j int) bool { return r[i].Name < r[j].Name }
233
-
234
-// CmdNetworkInspect inspects the network object for more details
235
-//
236
-// Usage: docker network inspect [OPTIONS] <NETWORK> [NETWORK...]
237
-func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
238
-	cmd := Cli.Subcmd("network inspect", []string{"NETWORK [NETWORK...]"}, "Displays detailed information on one or more networks", false)
239
-	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
240
-	cmd.Require(flag.Min, 1)
241
-
242
-	if err := cmd.ParseFlags(args, true); err != nil {
243
-		return err
244
-	}
245
-
246
-	ctx := context.Background()
247
-
248
-	inspectSearcher := func(name string) (interface{}, []byte, error) {
249
-		i, err := cli.client.NetworkInspect(ctx, name)
250
-		return i, nil, err
251
-	}
252
-
253
-	return inspect.Inspect(cli.out, cmd.Args(), *tmplStr, inspectSearcher)
254
-}
255
-
256
-// Consolidates the ipam configuration as a group from different related configurations
257
-// user can configure network with multiple non-overlapping subnets and hence it is
258
-// possible to correlate the various related parameters and consolidate them.
259
-// consoidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
260
-// structured ipam data.
261
-func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
262
-	if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
263
-		return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
264
-	}
265
-	iData := map[string]*network.IPAMConfig{}
266
-
267
-	// Populate non-overlapping subnets into consolidation map
268
-	for _, s := range subnets {
269
-		for k := range iData {
270
-			ok1, err := subnetMatches(s, k)
271
-			if err != nil {
272
-				return nil, err
273
-			}
274
-			ok2, err := subnetMatches(k, s)
275
-			if err != nil {
276
-				return nil, err
277
-			}
278
-			if ok1 || ok2 {
279
-				return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
280
-			}
281
-		}
282
-		iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
283
-	}
284
-
285
-	// Validate and add valid ip ranges
286
-	for _, r := range ranges {
287
-		match := false
288
-		for _, s := range subnets {
289
-			ok, err := subnetMatches(s, r)
290
-			if err != nil {
291
-				return nil, err
292
-			}
293
-			if !ok {
294
-				continue
295
-			}
296
-			if iData[s].IPRange != "" {
297
-				return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
298
-			}
299
-			d := iData[s]
300
-			d.IPRange = r
301
-			match = true
302
-		}
303
-		if !match {
304
-			return nil, fmt.Errorf("no matching subnet for range %s", r)
305
-		}
306
-	}
307
-
308
-	// Validate and add valid gateways
309
-	for _, g := range gateways {
310
-		match := false
311
-		for _, s := range subnets {
312
-			ok, err := subnetMatches(s, g)
313
-			if err != nil {
314
-				return nil, err
315
-			}
316
-			if !ok {
317
-				continue
318
-			}
319
-			if iData[s].Gateway != "" {
320
-				return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
321
-			}
322
-			d := iData[s]
323
-			d.Gateway = g
324
-			match = true
325
-		}
326
-		if !match {
327
-			return nil, fmt.Errorf("no matching subnet for gateway %s", g)
328
-		}
329
-	}
330
-
331
-	// Validate and add aux-addresses
332
-	for key, aa := range auxaddrs {
333
-		match := false
334
-		for _, s := range subnets {
335
-			ok, err := subnetMatches(s, aa)
336
-			if err != nil {
337
-				return nil, err
338
-			}
339
-			if !ok {
340
-				continue
341
-			}
342
-			iData[s].AuxAddress[key] = aa
343
-			match = true
344
-		}
345
-		if !match {
346
-			return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
347
-		}
348
-	}
349
-
350
-	idl := []network.IPAMConfig{}
351
-	for _, v := range iData {
352
-		idl = append(idl, *v)
353
-	}
354
-	return idl, nil
355
-}
356
-
357
-func subnetMatches(subnet, data string) (bool, error) {
358
-	var (
359
-		ip net.IP
360
-	)
361
-
362
-	_, s, err := net.ParseCIDR(subnet)
363
-	if err != nil {
364
-		return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
365
-	}
366
-
367
-	if strings.Contains(data, "/") {
368
-		ip, _, err = net.ParseCIDR(data)
369
-		if err != nil {
370
-			return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
371
-		}
372
-	} else {
373
-		ip = net.ParseIP(data)
374
-	}
375
-
376
-	return s.Contains(ip), nil
377
-}
378
-
379
-func networkUsage() string {
380
-	networkCommands := [][]string{
381
-		{"create", "Create a network"},
382
-		{"connect", "Connect container to a network"},
383
-		{"disconnect", "Disconnect container from a network"},
384
-		{"inspect", "Display detailed network information"},
385
-		{"ls", "List all networks"},
386
-		{"rm", "Remove a network"},
387
-	}
388
-
389
-	help := "Commands:\n"
390
-
391
-	for _, cmd := range networkCommands {
392
-		help += fmt.Sprintf("  %-25.25s%s\n", cmd[0], cmd[1])
393
-	}
394
-
395
-	help += fmt.Sprintf("\nRun 'docker network COMMAND --help' for more information on a command.")
396
-	return help
397
-}
398 1
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package network
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/spf13/cobra"
6
+
7
+	"github.com/docker/docker/api/client"
8
+	"github.com/docker/docker/cli"
9
+)
10
+
11
+// NewNetworkCommand returns a cobra command for `network` subcommands
12
+func NewNetworkCommand(dockerCli *client.DockerCli) *cobra.Command {
13
+	cmd := &cobra.Command{
14
+		Use:   "network",
15
+		Short: "Manage Docker networks",
16
+		Args:  cli.NoArgs,
17
+		Run: func(cmd *cobra.Command, args []string) {
18
+			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
19
+		},
20
+	}
21
+	cmd.AddCommand(
22
+		newConnectCommand(dockerCli),
23
+		newCreateCommand(dockerCli),
24
+		newDisconnectCommand(dockerCli),
25
+		newInspectCommand(dockerCli),
26
+		newListCommand(dockerCli),
27
+		newRemoveCommand(dockerCli),
28
+	)
29
+	return cmd
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+package network
1
+
2
+import (
3
+	"golang.org/x/net/context"
4
+
5
+	"github.com/docker/docker/api/client"
6
+	"github.com/docker/docker/cli"
7
+	"github.com/docker/docker/opts"
8
+	runconfigopts "github.com/docker/docker/runconfig/opts"
9
+	"github.com/docker/engine-api/types/network"
10
+	"github.com/spf13/cobra"
11
+)
12
+
13
+type connectOptions struct {
14
+	network     string
15
+	container   string
16
+	ipaddress   string
17
+	ipv6address string
18
+	links       opts.ListOpts
19
+	aliases     []string
20
+}
21
+
22
+func newConnectCommand(dockerCli *client.DockerCli) *cobra.Command {
23
+	opts := connectOptions{
24
+		links: opts.NewListOpts(runconfigopts.ValidateLink),
25
+	}
26
+
27
+	cmd := &cobra.Command{
28
+		Use:   "connect [OPTIONS] NETWORK CONTAINER",
29
+		Short: "Connects a container to a network",
30
+		Args:  cli.ExactArgs(2),
31
+		RunE: func(cmd *cobra.Command, args []string) error {
32
+			opts.network = args[0]
33
+			opts.container = args[1]
34
+			return runConnect(dockerCli, opts)
35
+		},
36
+	}
37
+
38
+	flags := cmd.Flags()
39
+	flags.StringVar(&opts.ipaddress, "ip", "", "IP Address")
40
+	flags.StringVar(&opts.ipv6address, "ip6", "", "IPv6 Address")
41
+	flags.Var(&opts.links, "link", "Add link to another container")
42
+	flags.StringSliceVar(&opts.aliases, "alias", []string{}, "Add network-scoped alias for the container")
43
+
44
+	return cmd
45
+}
46
+
47
+func runConnect(dockerCli *client.DockerCli, opts connectOptions) error {
48
+	client := dockerCli.Client()
49
+
50
+	epConfig := &network.EndpointSettings{
51
+		IPAMConfig: &network.EndpointIPAMConfig{
52
+			IPv4Address: opts.ipaddress,
53
+			IPv6Address: opts.ipv6address,
54
+		},
55
+		Links:   opts.links.GetAll(),
56
+		Aliases: opts.aliases,
57
+	}
58
+
59
+	return client.NetworkConnect(context.Background(), opts.network, opts.container, epConfig)
60
+}
0 61
new file mode 100644
... ...
@@ -0,0 +1,222 @@
0
+package network
1
+
2
+import (
3
+	"fmt"
4
+	"net"
5
+	"strings"
6
+
7
+	"golang.org/x/net/context"
8
+
9
+	"github.com/docker/docker/api/client"
10
+	"github.com/docker/docker/cli"
11
+	"github.com/docker/docker/opts"
12
+	runconfigopts "github.com/docker/docker/runconfig/opts"
13
+	"github.com/docker/engine-api/types"
14
+	"github.com/docker/engine-api/types/network"
15
+	"github.com/spf13/cobra"
16
+)
17
+
18
+type createOptions struct {
19
+	name       string
20
+	driver     string
21
+	driverOpts opts.MapOpts
22
+	labels     []string
23
+	internal   bool
24
+	ipv6       bool
25
+
26
+	ipamDriver  string
27
+	ipamSubnet  []string
28
+	ipamIPRange []string
29
+	ipamGateway []string
30
+	ipamAux     opts.MapOpts
31
+	ipamOpt     opts.MapOpts
32
+}
33
+
34
+func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
35
+	opts := createOptions{
36
+		driverOpts: *opts.NewMapOpts(nil, nil),
37
+		ipamAux:    *opts.NewMapOpts(nil, nil),
38
+		ipamOpt:    *opts.NewMapOpts(nil, nil),
39
+	}
40
+
41
+	cmd := &cobra.Command{
42
+		Use:   "create",
43
+		Short: "Create a network",
44
+		Args:  cli.ExactArgs(1),
45
+		RunE: func(cmd *cobra.Command, args []string) error {
46
+			opts.name = args[0]
47
+			return runCreate(dockerCli, opts)
48
+		},
49
+	}
50
+
51
+	flags := cmd.Flags()
52
+	flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network")
53
+	flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
54
+	flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
55
+	flags.BoolVar(&opts.internal, "internal", false, "restricts external access to the network")
56
+	flags.BoolVar(&opts.ipv6, "ipv6", false, "enable IPv6 networking")
57
+
58
+	flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
59
+	flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "subnet in CIDR format that represents a network segment")
60
+	flags.StringSliceVar(&opts.ipamIPRange, "ip-range", []string{}, "allocate container ip from a sub-range")
61
+	flags.StringSliceVar(&opts.ipamGateway, "gateway", []string{}, "ipv4 or ipv6 Gateway for the master subnet")
62
+
63
+	flags.Var(&opts.ipamAux, "aux-address", "auxiliary ipv4 or ipv6 addresses used by Network driver")
64
+	flags.Var(&opts.ipamOpt, "ipam-opt", "set IPAM driver specific options")
65
+
66
+	return cmd
67
+}
68
+
69
+func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
70
+	client := dockerCli.Client()
71
+
72
+	ipamCfg, err := consolidateIpam(opts.ipamSubnet, opts.ipamIPRange, opts.ipamGateway, opts.ipamAux.GetAll())
73
+	if err != nil {
74
+		return err
75
+	}
76
+
77
+	// Construct network create request body
78
+	nc := types.NetworkCreate{
79
+		Driver:  opts.driver,
80
+		Options: opts.driverOpts.GetAll(),
81
+		IPAM: network.IPAM{
82
+			Driver:  opts.ipamDriver,
83
+			Config:  ipamCfg,
84
+			Options: opts.ipamOpt.GetAll(),
85
+		},
86
+		CheckDuplicate: true,
87
+		Internal:       opts.internal,
88
+		EnableIPv6:     opts.ipv6,
89
+		Labels:         runconfigopts.ConvertKVStringsToMap(opts.labels),
90
+	}
91
+
92
+	resp, err := client.NetworkCreate(context.Background(), opts.name, nc)
93
+	if err != nil {
94
+		return err
95
+	}
96
+	fmt.Fprintf(dockerCli.Out(), "%s\n", resp.ID)
97
+	return nil
98
+}
99
+
100
+// Consolidates the ipam configuration as a group from different related configurations
101
+// user can configure network with multiple non-overlapping subnets and hence it is
102
+// possible to correlate the various related parameters and consolidate them.
103
+// consoidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
104
+// structured ipam data.
105
+func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
106
+	if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
107
+		return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
108
+	}
109
+	iData := map[string]*network.IPAMConfig{}
110
+
111
+	// Populate non-overlapping subnets into consolidation map
112
+	for _, s := range subnets {
113
+		for k := range iData {
114
+			ok1, err := subnetMatches(s, k)
115
+			if err != nil {
116
+				return nil, err
117
+			}
118
+			ok2, err := subnetMatches(k, s)
119
+			if err != nil {
120
+				return nil, err
121
+			}
122
+			if ok1 || ok2 {
123
+				return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
124
+			}
125
+		}
126
+		iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
127
+	}
128
+
129
+	// Validate and add valid ip ranges
130
+	for _, r := range ranges {
131
+		match := false
132
+		for _, s := range subnets {
133
+			ok, err := subnetMatches(s, r)
134
+			if err != nil {
135
+				return nil, err
136
+			}
137
+			if !ok {
138
+				continue
139
+			}
140
+			if iData[s].IPRange != "" {
141
+				return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
142
+			}
143
+			d := iData[s]
144
+			d.IPRange = r
145
+			match = true
146
+		}
147
+		if !match {
148
+			return nil, fmt.Errorf("no matching subnet for range %s", r)
149
+		}
150
+	}
151
+
152
+	// Validate and add valid gateways
153
+	for _, g := range gateways {
154
+		match := false
155
+		for _, s := range subnets {
156
+			ok, err := subnetMatches(s, g)
157
+			if err != nil {
158
+				return nil, err
159
+			}
160
+			if !ok {
161
+				continue
162
+			}
163
+			if iData[s].Gateway != "" {
164
+				return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
165
+			}
166
+			d := iData[s]
167
+			d.Gateway = g
168
+			match = true
169
+		}
170
+		if !match {
171
+			return nil, fmt.Errorf("no matching subnet for gateway %s", g)
172
+		}
173
+	}
174
+
175
+	// Validate and add aux-addresses
176
+	for key, aa := range auxaddrs {
177
+		match := false
178
+		for _, s := range subnets {
179
+			ok, err := subnetMatches(s, aa)
180
+			if err != nil {
181
+				return nil, err
182
+			}
183
+			if !ok {
184
+				continue
185
+			}
186
+			iData[s].AuxAddress[key] = aa
187
+			match = true
188
+		}
189
+		if !match {
190
+			return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
191
+		}
192
+	}
193
+
194
+	idl := []network.IPAMConfig{}
195
+	for _, v := range iData {
196
+		idl = append(idl, *v)
197
+	}
198
+	return idl, nil
199
+}
200
+
201
+func subnetMatches(subnet, data string) (bool, error) {
202
+	var (
203
+		ip net.IP
204
+	)
205
+
206
+	_, s, err := net.ParseCIDR(subnet)
207
+	if err != nil {
208
+		return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
209
+	}
210
+
211
+	if strings.Contains(data, "/") {
212
+		ip, _, err = net.ParseCIDR(data)
213
+		if err != nil {
214
+			return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
215
+		}
216
+	} else {
217
+		ip = net.ParseIP(data)
218
+	}
219
+
220
+	return s.Contains(ip), nil
221
+}
0 222
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package network
1
+
2
+import (
3
+	"golang.org/x/net/context"
4
+
5
+	"github.com/docker/docker/api/client"
6
+	"github.com/docker/docker/cli"
7
+	"github.com/spf13/cobra"
8
+)
9
+
10
+type disconnectOptions struct {
11
+	network   string
12
+	container string
13
+	force     bool
14
+}
15
+
16
+func newDisconnectCommand(dockerCli *client.DockerCli) *cobra.Command {
17
+	opts := disconnectOptions{}
18
+
19
+	cmd := &cobra.Command{
20
+		Use:   "disconnect [OPTIONS] NETWORK CONTAINER",
21
+		Short: "Disconnects container from a network",
22
+		Args:  cli.ExactArgs(2),
23
+		RunE: func(cmd *cobra.Command, args []string) error {
24
+			opts.network = args[0]
25
+			opts.container = args[1]
26
+			return runDisconnect(dockerCli, opts)
27
+		},
28
+	}
29
+
30
+	flags := cmd.Flags()
31
+	flags.BoolVarP(&opts.force, "force", "f", false, "Force the container to disconnect from a network")
32
+
33
+	return cmd
34
+}
35
+
36
+func runDisconnect(dockerCli *client.DockerCli, opts disconnectOptions) error {
37
+	client := dockerCli.Client()
38
+
39
+	return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force)
40
+}
0 41
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+package network
1
+
2
+import (
3
+	"golang.org/x/net/context"
4
+
5
+	"github.com/docker/docker/api/client"
6
+	"github.com/docker/docker/api/client/inspect"
7
+	"github.com/docker/docker/cli"
8
+	"github.com/spf13/cobra"
9
+)
10
+
11
+type inspectOptions struct {
12
+	format string
13
+	names  []string
14
+}
15
+
16
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
17
+	var opts inspectOptions
18
+
19
+	cmd := &cobra.Command{
20
+		Use:   "inspect [OPTIONS] NETWORK [NETWORK...]",
21
+		Short: "Displays detailed information on one or more networks",
22
+		Args:  cli.RequiresMinArgs(1),
23
+		RunE: func(cmd *cobra.Command, args []string) error {
24
+			opts.names = args
25
+			return runInspect(dockerCli, opts)
26
+		},
27
+	}
28
+
29
+	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
30
+
31
+	return cmd
32
+}
33
+
34
+func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
35
+	client := dockerCli.Client()
36
+
37
+	getNetFunc := func(name string) (interface{}, []byte, error) {
38
+		i, err := client.NetworkInspect(context.Background(), name)
39
+		return i, nil, err
40
+	}
41
+
42
+	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,98 @@
0
+package network
1
+
2
+import (
3
+	"fmt"
4
+	"sort"
5
+	"text/tabwriter"
6
+
7
+	"golang.org/x/net/context"
8
+
9
+	"github.com/docker/docker/api/client"
10
+	"github.com/docker/docker/cli"
11
+	"github.com/docker/docker/pkg/stringid"
12
+	"github.com/docker/engine-api/types"
13
+	"github.com/docker/engine-api/types/filters"
14
+	"github.com/spf13/cobra"
15
+)
16
+
17
+type byNetworkName []types.NetworkResource
18
+
19
+func (r byNetworkName) Len() int           { return len(r) }
20
+func (r byNetworkName) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
21
+func (r byNetworkName) Less(i, j int) bool { return r[i].Name < r[j].Name }
22
+
23
+type listOptions struct {
24
+	quiet   bool
25
+	noTrunc bool
26
+	filter  []string
27
+}
28
+
29
+func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
30
+	var opts listOptions
31
+
32
+	cmd := &cobra.Command{
33
+		Use:     "ls",
34
+		Aliases: []string{"list"},
35
+		Short:   "List networks",
36
+		Args:    cli.NoArgs,
37
+		RunE: func(cmd *cobra.Command, args []string) error {
38
+			return runList(dockerCli, opts)
39
+		},
40
+	}
41
+
42
+	flags := cmd.Flags()
43
+	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names")
44
+	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output")
45
+	flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Provide filter values (i.e. 'dangling=true')")
46
+
47
+	return cmd
48
+}
49
+
50
+func runList(dockerCli *client.DockerCli, opts listOptions) error {
51
+	client := dockerCli.Client()
52
+
53
+	netFilterArgs := filters.NewArgs()
54
+	for _, f := range opts.filter {
55
+		var err error
56
+		netFilterArgs, err = filters.ParseFlag(f, netFilterArgs)
57
+		if err != nil {
58
+			return err
59
+		}
60
+	}
61
+
62
+	options := types.NetworkListOptions{
63
+		Filters: netFilterArgs,
64
+	}
65
+
66
+	networkResources, err := client.NetworkList(context.Background(), options)
67
+	if err != nil {
68
+		return err
69
+	}
70
+
71
+	w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
72
+	if !opts.quiet {
73
+		fmt.Fprintf(w, "NETWORK ID\tNAME\tDRIVER")
74
+		fmt.Fprintf(w, "\n")
75
+	}
76
+
77
+	sort.Sort(byNetworkName(networkResources))
78
+	for _, networkResource := range networkResources {
79
+		ID := networkResource.ID
80
+		netName := networkResource.Name
81
+		if !opts.noTrunc {
82
+			ID = stringid.TruncateID(ID)
83
+		}
84
+		if opts.quiet {
85
+			fmt.Fprintln(w, ID)
86
+			continue
87
+		}
88
+		driver := networkResource.Driver
89
+		fmt.Fprintf(w, "%s\t%s\t%s\t",
90
+			ID,
91
+			netName,
92
+			driver)
93
+		fmt.Fprint(w, "\n")
94
+	}
95
+	w.Flush()
96
+	return nil
97
+}
0 98
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package network
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"golang.org/x/net/context"
6
+
7
+	"github.com/docker/docker/api/client"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/spf13/cobra"
10
+)
11
+
12
+func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
13
+	return &cobra.Command{
14
+		Use:     "rm NETWORK [NETWORK]...",
15
+		Aliases: []string{"remove"},
16
+		Short:   "Remove a network",
17
+		Args:    cli.RequiresMinArgs(1),
18
+		RunE: func(cmd *cobra.Command, args []string) error {
19
+			return runRemove(dockerCli, args)
20
+		},
21
+	}
22
+}
23
+
24
+func runRemove(dockerCli *client.DockerCli, networks []string) error {
25
+	client := dockerCli.Client()
26
+	ctx := context.Background()
27
+	status := 0
28
+
29
+	for _, name := range networks {
30
+		if err := client.NetworkRemove(ctx, name); err != nil {
31
+			fmt.Fprintf(dockerCli.Err(), "%s\n", err)
32
+			status = 1
33
+			continue
34
+		}
35
+		fmt.Fprintf(dockerCli.Err(), "%s\n", name)
36
+	}
37
+
38
+	if status != 0 {
39
+		return cli.StatusError{StatusCode: status}
40
+	}
41
+	return nil
42
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"github.com/docker/docker/api/client"
5 5
 	"github.com/docker/docker/api/client/container"
6 6
 	"github.com/docker/docker/api/client/image"
7
+	"github.com/docker/docker/api/client/network"
7 8
 	"github.com/docker/docker/api/client/volume"
8 9
 	"github.com/docker/docker/cli"
9 10
 	cliflags "github.com/docker/docker/cli/flags"
... ...
@@ -43,6 +44,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
43 43
 		container.NewUnpauseCommand(dockerCli),
44 44
 		image.NewRemoveCommand(dockerCli),
45 45
 		image.NewSearchCommand(dockerCli),
46
+		network.NewNetworkCommand(dockerCli),
46 47
 		volume.NewVolumeCommand(dockerCli),
47 48
 	)
48 49
 
... ...
@@ -23,7 +23,6 @@ var DockerCommandUsage = []Command{
23 23
 	{"load", "Load an image from a tar archive or STDIN"},
24 24
 	{"login", "Log in to a Docker registry"},
25 25
 	{"logout", "Log out from a Docker registry"},
26
-	{"network", "Manage Docker networks"},
27 26
 	{"pause", "Pause all processes within a container"},
28 27
 	{"port", "List port mappings or a specific mapping for the CONTAINER"},
29 28
 	{"ps", "List containers"},
... ...
@@ -119,6 +119,12 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
119 119
 		cmdsToTest = append(cmdsToTest, "volume inspect")
120 120
 		cmdsToTest = append(cmdsToTest, "volume ls")
121 121
 		cmdsToTest = append(cmdsToTest, "volume rm")
122
+		cmdsToTest = append(cmdsToTest, "network connect")
123
+		cmdsToTest = append(cmdsToTest, "network create")
124
+		cmdsToTest = append(cmdsToTest, "network disconnect")
125
+		cmdsToTest = append(cmdsToTest, "network inspect")
126
+		cmdsToTest = append(cmdsToTest, "network ls")
127
+		cmdsToTest = append(cmdsToTest, "network rm")
122 128
 
123 129
 		// Divide the list of commands into go routines and  run the func testcommand on the commands in parallel
124 130
 		// to save runtime of test
... ...
@@ -362,7 +362,7 @@ func (s *DockerSuite) TestInspectContainerNetworkDefault(c *check.C) {
362 362
 
363 363
 	contName := "test1"
364 364
 	dockerCmd(c, "run", "--name", contName, "-d", "busybox", "top")
365
-	netOut, _ := dockerCmd(c, "network", "inspect", "--format='{{.ID}}'", "bridge")
365
+	netOut, _ := dockerCmd(c, "network", "inspect", "--format={{.ID}}", "bridge")
366 366
 	out := inspectField(c, contName, "NetworkSettings.Networks")
367 367
 	c.Assert(out, checker.Contains, "bridge")
368 368
 	out = inspectField(c, contName, "NetworkSettings.Networks.bridge.NetworkID")
... ...
@@ -379,7 +379,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkCreateLabel(c *check.C) {
379 379
 	dockerCmd(c, "network", "create", "--label", testLabel+"="+testValue, testNet)
380 380
 	assertNwIsAvailable(c, testNet)
381 381
 
382
-	out, _, err := dockerCmdWithError("network", "inspect", "--format='{{ .Labels."+testLabel+" }}'", testNet)
382
+	out, _, err := dockerCmdWithError("network", "inspect", "--format={{ .Labels."+testLabel+" }}", testNet)
383 383
 	c.Assert(err, check.IsNil)
384 384
 	c.Assert(strings.TrimSpace(out), check.Equals, testValue)
385 385
 
... ...
@@ -423,7 +423,7 @@ func (s *DockerSuite) TestDockerNetworkInspect(c *check.C) {
423 423
 	c.Assert(err, check.IsNil)
424 424
 	c.Assert(networkResources, checker.HasLen, 1)
425 425
 
426
-	out, _ = dockerCmd(c, "network", "inspect", "--format='{{ .Name }}'", "host")
426
+	out, _ = dockerCmd(c, "network", "inspect", "--format={{ .Name }}", "host")
427 427
 	c.Assert(strings.TrimSpace(out), check.Equals, "host")
428 428
 }
429 429