Browse Source

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 9 years ago
parent
commit
4bd202b00f

+ 30 - 37
api/client/commands.go

@@ -3,42 +3,35 @@ package client
 // Command returns a cli command handler if one exists
 func (cli *DockerCli) Command(name string) func(...string) error {
 	return map[string]func(...string) error{
-		"attach":             cli.CmdAttach,
-		"build":              cli.CmdBuild,
-		"commit":             cli.CmdCommit,
-		"cp":                 cli.CmdCp,
-		"events":             cli.CmdEvents,
-		"exec":               cli.CmdExec,
-		"history":            cli.CmdHistory,
-		"images":             cli.CmdImages,
-		"import":             cli.CmdImport,
-		"info":               cli.CmdInfo,
-		"inspect":            cli.CmdInspect,
-		"kill":               cli.CmdKill,
-		"load":               cli.CmdLoad,
-		"login":              cli.CmdLogin,
-		"logout":             cli.CmdLogout,
-		"network":            cli.CmdNetwork,
-		"network create":     cli.CmdNetworkCreate,
-		"network connect":    cli.CmdNetworkConnect,
-		"network disconnect": cli.CmdNetworkDisconnect,
-		"network inspect":    cli.CmdNetworkInspect,
-		"network ls":         cli.CmdNetworkLs,
-		"network rm":         cli.CmdNetworkRm,
-		"pause":              cli.CmdPause,
-		"port":               cli.CmdPort,
-		"ps":                 cli.CmdPs,
-		"pull":               cli.CmdPull,
-		"push":               cli.CmdPush,
-		"rename":             cli.CmdRename,
-		"restart":            cli.CmdRestart,
-		"rm":                 cli.CmdRm,
-		"save":               cli.CmdSave,
-		"stats":              cli.CmdStats,
-		"tag":                cli.CmdTag,
-		"top":                cli.CmdTop,
-		"update":             cli.CmdUpdate,
-		"version":            cli.CmdVersion,
-		"wait":               cli.CmdWait,
+		"attach":  cli.CmdAttach,
+		"build":   cli.CmdBuild,
+		"commit":  cli.CmdCommit,
+		"cp":      cli.CmdCp,
+		"events":  cli.CmdEvents,
+		"exec":    cli.CmdExec,
+		"history": cli.CmdHistory,
+		"images":  cli.CmdImages,
+		"import":  cli.CmdImport,
+		"info":    cli.CmdInfo,
+		"inspect": cli.CmdInspect,
+		"kill":    cli.CmdKill,
+		"load":    cli.CmdLoad,
+		"login":   cli.CmdLogin,
+		"logout":  cli.CmdLogout,
+		"pause":   cli.CmdPause,
+		"port":    cli.CmdPort,
+		"ps":      cli.CmdPs,
+		"pull":    cli.CmdPull,
+		"push":    cli.CmdPush,
+		"rename":  cli.CmdRename,
+		"restart": cli.CmdRestart,
+		"rm":      cli.CmdRm,
+		"save":    cli.CmdSave,
+		"stats":   cli.CmdStats,
+		"tag":     cli.CmdTag,
+		"top":     cli.CmdTop,
+		"update":  cli.CmdUpdate,
+		"version": cli.CmdVersion,
+		"wait":    cli.CmdWait,
 	}[name]
 }

+ 0 - 397
api/client/network.go

@@ -1,397 +0,0 @@
-package client
-
-import (
-	"fmt"
-	"net"
-	"sort"
-	"strings"
-	"text/tabwriter"
-
-	"golang.org/x/net/context"
-
-	"github.com/docker/docker/api/client/inspect"
-	Cli "github.com/docker/docker/cli"
-	"github.com/docker/docker/opts"
-	flag "github.com/docker/docker/pkg/mflag"
-	"github.com/docker/docker/pkg/stringid"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
-	"github.com/docker/engine-api/types"
-	"github.com/docker/engine-api/types/filters"
-	"github.com/docker/engine-api/types/network"
-)
-
-// CmdNetwork is the parent subcommand for all network commands
-//
-// Usage: docker network <COMMAND> [OPTIONS]
-func (cli *DockerCli) CmdNetwork(args ...string) error {
-	cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
-	cmd.Require(flag.Min, 1)
-	err := cmd.ParseFlags(args, true)
-	cmd.Usage()
-	return err
-}
-
-// CmdNetworkCreate creates a new network with a given name
-//
-// Usage: docker network create [OPTIONS] <NETWORK-NAME>
-func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
-	cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
-	flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network")
-	flOpts := opts.NewMapOpts(nil, nil)
-
-	flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver")
-	flIpamSubnet := opts.NewListOpts(nil)
-	flIpamIPRange := opts.NewListOpts(nil)
-	flIpamGateway := opts.NewListOpts(nil)
-	flIpamAux := opts.NewMapOpts(nil, nil)
-	flIpamOpt := opts.NewMapOpts(nil, nil)
-	flLabels := opts.NewListOpts(nil)
-
-	cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
-	cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
-	cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet")
-	cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
-	cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
-	cmd.Var(flIpamOpt, []string{"-ipam-opt"}, "set IPAM driver specific options")
-	cmd.Var(&flLabels, []string{"-label"}, "set metadata on a network")
-
-	flInternal := cmd.Bool([]string{"-internal"}, false, "restricts external access to the network")
-	flIPv6 := cmd.Bool([]string{"-ipv6"}, false, "enable IPv6 networking")
-
-	cmd.Require(flag.Exact, 1)
-	err := cmd.ParseFlags(args, true)
-	if err != nil {
-		return err
-	}
-
-	// Set the default driver to "" if the user didn't set the value.
-	// That way we can know whether it was user input or not.
-	driver := *flDriver
-	if !cmd.IsSet("-driver") && !cmd.IsSet("d") {
-		driver = ""
-	}
-
-	ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll())
-	if err != nil {
-		return err
-	}
-
-	// Construct network create request body
-	nc := types.NetworkCreate{
-		Driver:         driver,
-		IPAM:           network.IPAM{Driver: *flIpamDriver, Config: ipamCfg, Options: flIpamOpt.GetAll()},
-		Options:        flOpts.GetAll(),
-		CheckDuplicate: true,
-		Internal:       *flInternal,
-		EnableIPv6:     *flIPv6,
-		Labels:         runconfigopts.ConvertKVStringsToMap(flLabels.GetAll()),
-	}
-
-	resp, err := cli.client.NetworkCreate(context.Background(), cmd.Arg(0), nc)
-	if err != nil {
-		return err
-	}
-	fmt.Fprintf(cli.out, "%s\n", resp.ID)
-	return nil
-}
-
-// CmdNetworkRm deletes one or more networks
-//
-// Usage: docker network rm NETWORK-NAME|NETWORK-ID [NETWORK-NAME|NETWORK-ID...]
-func (cli *DockerCli) CmdNetworkRm(args ...string) error {
-	cmd := Cli.Subcmd("network rm", []string{"NETWORK [NETWORK...]"}, "Deletes one or more networks", false)
-	cmd.Require(flag.Min, 1)
-	if err := cmd.ParseFlags(args, true); err != nil {
-		return err
-	}
-
-	ctx := context.Background()
-
-	status := 0
-	for _, net := range cmd.Args() {
-		if err := cli.client.NetworkRemove(ctx, net); err != nil {
-			fmt.Fprintf(cli.err, "%s\n", err)
-			status = 1
-			continue
-		}
-		fmt.Fprintf(cli.out, "%s\n", net)
-	}
-	if status != 0 {
-		return Cli.StatusError{StatusCode: status}
-	}
-	return nil
-}
-
-// CmdNetworkConnect connects a container to a network
-//
-// Usage: docker network connect [OPTIONS] <NETWORK> <CONTAINER>
-func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
-	cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
-	flIPAddress := cmd.String([]string{"-ip"}, "", "IP Address")
-	flIPv6Address := cmd.String([]string{"-ip6"}, "", "IPv6 Address")
-	flLinks := opts.NewListOpts(runconfigopts.ValidateLink)
-	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
-	flAliases := opts.NewListOpts(nil)
-	cmd.Var(&flAliases, []string{"-alias"}, "Add network-scoped alias for the container")
-	cmd.Require(flag.Min, 2)
-	if err := cmd.ParseFlags(args, true); err != nil {
-		return err
-	}
-	epConfig := &network.EndpointSettings{
-		IPAMConfig: &network.EndpointIPAMConfig{
-			IPv4Address: *flIPAddress,
-			IPv6Address: *flIPv6Address,
-		},
-		Links:   flLinks.GetAll(),
-		Aliases: flAliases.GetAll(),
-	}
-	return cli.client.NetworkConnect(context.Background(), cmd.Arg(0), cmd.Arg(1), epConfig)
-}
-
-// CmdNetworkDisconnect disconnects a container from a network
-//
-// Usage: docker network disconnect <NETWORK> <CONTAINER>
-func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
-	cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
-	force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a network")
-	cmd.Require(flag.Exact, 2)
-	if err := cmd.ParseFlags(args, true); err != nil {
-		return err
-	}
-
-	return cli.client.NetworkDisconnect(context.Background(), cmd.Arg(0), cmd.Arg(1), *force)
-}
-
-// CmdNetworkLs lists all the networks managed by docker daemon
-//
-// Usage: docker network ls [OPTIONS]
-func (cli *DockerCli) CmdNetworkLs(args ...string) error {
-	cmd := Cli.Subcmd("network ls", nil, "Lists networks", true)
-	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
-	noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
-
-	flFilter := opts.NewListOpts(nil)
-	cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
-
-	cmd.Require(flag.Exact, 0)
-	err := cmd.ParseFlags(args, true)
-	if err != nil {
-		return err
-	}
-
-	// Consolidate all filter flags, and sanity check them early.
-	// They'll get process after get response from server.
-	netFilterArgs := filters.NewArgs()
-	for _, f := range flFilter.GetAll() {
-		if netFilterArgs, err = filters.ParseFlag(f, netFilterArgs); err != nil {
-			return err
-		}
-	}
-
-	options := types.NetworkListOptions{
-		Filters: netFilterArgs,
-	}
-
-	networkResources, err := cli.client.NetworkList(context.Background(), options)
-	if err != nil {
-		return err
-	}
-
-	wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
-
-	// unless quiet (-q) is specified, print field titles
-	if !*quiet {
-		fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
-	}
-	sort.Sort(byNetworkName(networkResources))
-	for _, networkResource := range networkResources {
-		ID := networkResource.ID
-		netName := networkResource.Name
-		if !*noTrunc {
-			ID = stringid.TruncateID(ID)
-		}
-		if *quiet {
-			fmt.Fprintln(wr, ID)
-			continue
-		}
-		driver := networkResource.Driver
-		fmt.Fprintf(wr, "%s\t%s\t%s\t",
-			ID,
-			netName,
-			driver)
-		fmt.Fprint(wr, "\n")
-	}
-	wr.Flush()
-	return nil
-}
-
-type byNetworkName []types.NetworkResource
-
-func (r byNetworkName) Len() int           { return len(r) }
-func (r byNetworkName) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
-func (r byNetworkName) Less(i, j int) bool { return r[i].Name < r[j].Name }
-
-// CmdNetworkInspect inspects the network object for more details
-//
-// Usage: docker network inspect [OPTIONS] <NETWORK> [NETWORK...]
-func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
-	cmd := Cli.Subcmd("network inspect", []string{"NETWORK [NETWORK...]"}, "Displays detailed information on one or more networks", false)
-	tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
-	cmd.Require(flag.Min, 1)
-
-	if err := cmd.ParseFlags(args, true); err != nil {
-		return err
-	}
-
-	ctx := context.Background()
-
-	inspectSearcher := func(name string) (interface{}, []byte, error) {
-		i, err := cli.client.NetworkInspect(ctx, name)
-		return i, nil, err
-	}
-
-	return inspect.Inspect(cli.out, cmd.Args(), *tmplStr, inspectSearcher)
-}
-
-// Consolidates the ipam configuration as a group from different related configurations
-// user can configure network with multiple non-overlapping subnets and hence it is
-// possible to correlate the various related parameters and consolidate them.
-// consoidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
-// structured ipam data.
-func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
-	if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
-		return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
-	}
-	iData := map[string]*network.IPAMConfig{}
-
-	// Populate non-overlapping subnets into consolidation map
-	for _, s := range subnets {
-		for k := range iData {
-			ok1, err := subnetMatches(s, k)
-			if err != nil {
-				return nil, err
-			}
-			ok2, err := subnetMatches(k, s)
-			if err != nil {
-				return nil, err
-			}
-			if ok1 || ok2 {
-				return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
-			}
-		}
-		iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
-	}
-
-	// Validate and add valid ip ranges
-	for _, r := range ranges {
-		match := false
-		for _, s := range subnets {
-			ok, err := subnetMatches(s, r)
-			if err != nil {
-				return nil, err
-			}
-			if !ok {
-				continue
-			}
-			if iData[s].IPRange != "" {
-				return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
-			}
-			d := iData[s]
-			d.IPRange = r
-			match = true
-		}
-		if !match {
-			return nil, fmt.Errorf("no matching subnet for range %s", r)
-		}
-	}
-
-	// Validate and add valid gateways
-	for _, g := range gateways {
-		match := false
-		for _, s := range subnets {
-			ok, err := subnetMatches(s, g)
-			if err != nil {
-				return nil, err
-			}
-			if !ok {
-				continue
-			}
-			if iData[s].Gateway != "" {
-				return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
-			}
-			d := iData[s]
-			d.Gateway = g
-			match = true
-		}
-		if !match {
-			return nil, fmt.Errorf("no matching subnet for gateway %s", g)
-		}
-	}
-
-	// Validate and add aux-addresses
-	for key, aa := range auxaddrs {
-		match := false
-		for _, s := range subnets {
-			ok, err := subnetMatches(s, aa)
-			if err != nil {
-				return nil, err
-			}
-			if !ok {
-				continue
-			}
-			iData[s].AuxAddress[key] = aa
-			match = true
-		}
-		if !match {
-			return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
-		}
-	}
-
-	idl := []network.IPAMConfig{}
-	for _, v := range iData {
-		idl = append(idl, *v)
-	}
-	return idl, nil
-}
-
-func subnetMatches(subnet, data string) (bool, error) {
-	var (
-		ip net.IP
-	)
-
-	_, s, err := net.ParseCIDR(subnet)
-	if err != nil {
-		return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
-	}
-
-	if strings.Contains(data, "/") {
-		ip, _, err = net.ParseCIDR(data)
-		if err != nil {
-			return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
-		}
-	} else {
-		ip = net.ParseIP(data)
-	}
-
-	return s.Contains(ip), nil
-}
-
-func networkUsage() string {
-	networkCommands := [][]string{
-		{"create", "Create a network"},
-		{"connect", "Connect container to a network"},
-		{"disconnect", "Disconnect container from a network"},
-		{"inspect", "Display detailed network information"},
-		{"ls", "List all networks"},
-		{"rm", "Remove a network"},
-	}
-
-	help := "Commands:\n"
-
-	for _, cmd := range networkCommands {
-		help += fmt.Sprintf("  %-25.25s%s\n", cmd[0], cmd[1])
-	}
-
-	help += fmt.Sprintf("\nRun 'docker network COMMAND --help' for more information on a command.")
-	return help
-}

+ 31 - 0
api/client/network/cmd.go

@@ -0,0 +1,31 @@
+package network
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+)
+
+// NewNetworkCommand returns a cobra command for `network` subcommands
+func NewNetworkCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "network",
+		Short: "Manage Docker networks",
+		Args:  cli.NoArgs,
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
+		},
+	}
+	cmd.AddCommand(
+		newConnectCommand(dockerCli),
+		newCreateCommand(dockerCli),
+		newDisconnectCommand(dockerCli),
+		newInspectCommand(dockerCli),
+		newListCommand(dockerCli),
+		newRemoveCommand(dockerCli),
+	)
+	return cmd
+}

+ 61 - 0
api/client/network/connect.go

@@ -0,0 +1,61 @@
+package network
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/engine-api/types/network"
+	"github.com/spf13/cobra"
+)
+
+type connectOptions struct {
+	network     string
+	container   string
+	ipaddress   string
+	ipv6address string
+	links       opts.ListOpts
+	aliases     []string
+}
+
+func newConnectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := connectOptions{
+		links: opts.NewListOpts(runconfigopts.ValidateLink),
+	}
+
+	cmd := &cobra.Command{
+		Use:   "connect [OPTIONS] NETWORK CONTAINER",
+		Short: "Connects a container to a network",
+		Args:  cli.ExactArgs(2),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.network = args[0]
+			opts.container = args[1]
+			return runConnect(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.StringVar(&opts.ipaddress, "ip", "", "IP Address")
+	flags.StringVar(&opts.ipv6address, "ip6", "", "IPv6 Address")
+	flags.Var(&opts.links, "link", "Add link to another container")
+	flags.StringSliceVar(&opts.aliases, "alias", []string{}, "Add network-scoped alias for the container")
+
+	return cmd
+}
+
+func runConnect(dockerCli *client.DockerCli, opts connectOptions) error {
+	client := dockerCli.Client()
+
+	epConfig := &network.EndpointSettings{
+		IPAMConfig: &network.EndpointIPAMConfig{
+			IPv4Address: opts.ipaddress,
+			IPv6Address: opts.ipv6address,
+		},
+		Links:   opts.links.GetAll(),
+		Aliases: opts.aliases,
+	}
+
+	return client.NetworkConnect(context.Background(), opts.network, opts.container, epConfig)
+}

+ 222 - 0
api/client/network/create.go

@@ -0,0 +1,222 @@
+package network
+
+import (
+	"fmt"
+	"net"
+	"strings"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/opts"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/network"
+	"github.com/spf13/cobra"
+)
+
+type createOptions struct {
+	name       string
+	driver     string
+	driverOpts opts.MapOpts
+	labels     []string
+	internal   bool
+	ipv6       bool
+
+	ipamDriver  string
+	ipamSubnet  []string
+	ipamIPRange []string
+	ipamGateway []string
+	ipamAux     opts.MapOpts
+	ipamOpt     opts.MapOpts
+}
+
+func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := createOptions{
+		driverOpts: *opts.NewMapOpts(nil, nil),
+		ipamAux:    *opts.NewMapOpts(nil, nil),
+		ipamOpt:    *opts.NewMapOpts(nil, nil),
+	}
+
+	cmd := &cobra.Command{
+		Use:   "create",
+		Short: "Create a network",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.name = args[0]
+			return runCreate(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network")
+	flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
+	flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
+	flags.BoolVar(&opts.internal, "internal", false, "restricts external access to the network")
+	flags.BoolVar(&opts.ipv6, "ipv6", false, "enable IPv6 networking")
+
+	flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
+	flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "subnet in CIDR format that represents a network segment")
+	flags.StringSliceVar(&opts.ipamIPRange, "ip-range", []string{}, "allocate container ip from a sub-range")
+	flags.StringSliceVar(&opts.ipamGateway, "gateway", []string{}, "ipv4 or ipv6 Gateway for the master subnet")
+
+	flags.Var(&opts.ipamAux, "aux-address", "auxiliary ipv4 or ipv6 addresses used by Network driver")
+	flags.Var(&opts.ipamOpt, "ipam-opt", "set IPAM driver specific options")
+
+	return cmd
+}
+
+func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
+	client := dockerCli.Client()
+
+	ipamCfg, err := consolidateIpam(opts.ipamSubnet, opts.ipamIPRange, opts.ipamGateway, opts.ipamAux.GetAll())
+	if err != nil {
+		return err
+	}
+
+	// Construct network create request body
+	nc := types.NetworkCreate{
+		Driver:  opts.driver,
+		Options: opts.driverOpts.GetAll(),
+		IPAM: network.IPAM{
+			Driver:  opts.ipamDriver,
+			Config:  ipamCfg,
+			Options: opts.ipamOpt.GetAll(),
+		},
+		CheckDuplicate: true,
+		Internal:       opts.internal,
+		EnableIPv6:     opts.ipv6,
+		Labels:         runconfigopts.ConvertKVStringsToMap(opts.labels),
+	}
+
+	resp, err := client.NetworkCreate(context.Background(), opts.name, nc)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(dockerCli.Out(), "%s\n", resp.ID)
+	return nil
+}
+
+// Consolidates the ipam configuration as a group from different related configurations
+// user can configure network with multiple non-overlapping subnets and hence it is
+// possible to correlate the various related parameters and consolidate them.
+// consoidateIpam consolidates subnets, ip-ranges, gateways and auxiliary addresses into
+// structured ipam data.
+func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
+	if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
+		return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
+	}
+	iData := map[string]*network.IPAMConfig{}
+
+	// Populate non-overlapping subnets into consolidation map
+	for _, s := range subnets {
+		for k := range iData {
+			ok1, err := subnetMatches(s, k)
+			if err != nil {
+				return nil, err
+			}
+			ok2, err := subnetMatches(k, s)
+			if err != nil {
+				return nil, err
+			}
+			if ok1 || ok2 {
+				return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
+			}
+		}
+		iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
+	}
+
+	// Validate and add valid ip ranges
+	for _, r := range ranges {
+		match := false
+		for _, s := range subnets {
+			ok, err := subnetMatches(s, r)
+			if err != nil {
+				return nil, err
+			}
+			if !ok {
+				continue
+			}
+			if iData[s].IPRange != "" {
+				return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
+			}
+			d := iData[s]
+			d.IPRange = r
+			match = true
+		}
+		if !match {
+			return nil, fmt.Errorf("no matching subnet for range %s", r)
+		}
+	}
+
+	// Validate and add valid gateways
+	for _, g := range gateways {
+		match := false
+		for _, s := range subnets {
+			ok, err := subnetMatches(s, g)
+			if err != nil {
+				return nil, err
+			}
+			if !ok {
+				continue
+			}
+			if iData[s].Gateway != "" {
+				return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
+			}
+			d := iData[s]
+			d.Gateway = g
+			match = true
+		}
+		if !match {
+			return nil, fmt.Errorf("no matching subnet for gateway %s", g)
+		}
+	}
+
+	// Validate and add aux-addresses
+	for key, aa := range auxaddrs {
+		match := false
+		for _, s := range subnets {
+			ok, err := subnetMatches(s, aa)
+			if err != nil {
+				return nil, err
+			}
+			if !ok {
+				continue
+			}
+			iData[s].AuxAddress[key] = aa
+			match = true
+		}
+		if !match {
+			return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
+		}
+	}
+
+	idl := []network.IPAMConfig{}
+	for _, v := range iData {
+		idl = append(idl, *v)
+	}
+	return idl, nil
+}
+
+func subnetMatches(subnet, data string) (bool, error) {
+	var (
+		ip net.IP
+	)
+
+	_, s, err := net.ParseCIDR(subnet)
+	if err != nil {
+		return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
+	}
+
+	if strings.Contains(data, "/") {
+		ip, _, err = net.ParseCIDR(data)
+		if err != nil {
+			return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
+		}
+	} else {
+		ip = net.ParseIP(data)
+	}
+
+	return s.Contains(ip), nil
+}

+ 41 - 0
api/client/network/disconnect.go

@@ -0,0 +1,41 @@
+package network
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+type disconnectOptions struct {
+	network   string
+	container string
+	force     bool
+}
+
+func newDisconnectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	opts := disconnectOptions{}
+
+	cmd := &cobra.Command{
+		Use:   "disconnect [OPTIONS] NETWORK CONTAINER",
+		Short: "Disconnects container from a network",
+		Args:  cli.ExactArgs(2),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.network = args[0]
+			opts.container = args[1]
+			return runDisconnect(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.BoolVarP(&opts.force, "force", "f", false, "Force the container to disconnect from a network")
+
+	return cmd
+}
+
+func runDisconnect(dockerCli *client.DockerCli, opts disconnectOptions) error {
+	client := dockerCli.Client()
+
+	return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force)
+}

+ 44 - 0
api/client/network/inspect.go

@@ -0,0 +1,44 @@
+package network
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/inspect"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+type inspectOptions struct {
+	format string
+	names  []string
+}
+
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts inspectOptions
+
+	cmd := &cobra.Command{
+		Use:   "inspect [OPTIONS] NETWORK [NETWORK...]",
+		Short: "Displays detailed information on one or more networks",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.names = args
+			return runInspect(dockerCli, opts)
+		},
+	}
+
+	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
+
+	return cmd
+}
+
+func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
+	client := dockerCli.Client()
+
+	getNetFunc := func(name string) (interface{}, []byte, error) {
+		i, err := client.NetworkInspect(context.Background(), name)
+		return i, nil, err
+	}
+
+	return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)
+}

+ 98 - 0
api/client/network/list.go

@@ -0,0 +1,98 @@
+package network
+
+import (
+	"fmt"
+	"sort"
+	"text/tabwriter"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/filters"
+	"github.com/spf13/cobra"
+)
+
+type byNetworkName []types.NetworkResource
+
+func (r byNetworkName) Len() int           { return len(r) }
+func (r byNetworkName) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
+func (r byNetworkName) Less(i, j int) bool { return r[i].Name < r[j].Name }
+
+type listOptions struct {
+	quiet   bool
+	noTrunc bool
+	filter  []string
+}
+
+func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts listOptions
+
+	cmd := &cobra.Command{
+		Use:     "ls",
+		Aliases: []string{"list"},
+		Short:   "List networks",
+		Args:    cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runList(dockerCli, opts)
+		},
+	}
+
+	flags := cmd.Flags()
+	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names")
+	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output")
+	flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Provide filter values (i.e. 'dangling=true')")
+
+	return cmd
+}
+
+func runList(dockerCli *client.DockerCli, opts listOptions) error {
+	client := dockerCli.Client()
+
+	netFilterArgs := filters.NewArgs()
+	for _, f := range opts.filter {
+		var err error
+		netFilterArgs, err = filters.ParseFlag(f, netFilterArgs)
+		if err != nil {
+			return err
+		}
+	}
+
+	options := types.NetworkListOptions{
+		Filters: netFilterArgs,
+	}
+
+	networkResources, err := client.NetworkList(context.Background(), options)
+	if err != nil {
+		return err
+	}
+
+	w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
+	if !opts.quiet {
+		fmt.Fprintf(w, "NETWORK ID\tNAME\tDRIVER")
+		fmt.Fprintf(w, "\n")
+	}
+
+	sort.Sort(byNetworkName(networkResources))
+	for _, networkResource := range networkResources {
+		ID := networkResource.ID
+		netName := networkResource.Name
+		if !opts.noTrunc {
+			ID = stringid.TruncateID(ID)
+		}
+		if opts.quiet {
+			fmt.Fprintln(w, ID)
+			continue
+		}
+		driver := networkResource.Driver
+		fmt.Fprintf(w, "%s\t%s\t%s\t",
+			ID,
+			netName,
+			driver)
+		fmt.Fprint(w, "\n")
+	}
+	w.Flush()
+	return nil
+}

+ 43 - 0
api/client/network/remove.go

@@ -0,0 +1,43 @@
+package network
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
+	return &cobra.Command{
+		Use:     "rm NETWORK [NETWORK]...",
+		Aliases: []string{"remove"},
+		Short:   "Remove a network",
+		Args:    cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runRemove(dockerCli, args)
+		},
+	}
+}
+
+func runRemove(dockerCli *client.DockerCli, networks []string) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+	status := 0
+
+	for _, name := range networks {
+		if err := client.NetworkRemove(ctx, name); err != nil {
+			fmt.Fprintf(dockerCli.Err(), "%s\n", err)
+			status = 1
+			continue
+		}
+		fmt.Fprintf(dockerCli.Err(), "%s\n", name)
+	}
+
+	if status != 0 {
+		return cli.StatusError{StatusCode: status}
+	}
+	return nil
+}

+ 2 - 0
cli/cobraadaptor/adaptor.go

@@ -4,6 +4,7 @@ import (
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/api/client/container"
 	"github.com/docker/docker/api/client/image"
+	"github.com/docker/docker/api/client/network"
 	"github.com/docker/docker/api/client/volume"
 	"github.com/docker/docker/cli"
 	cliflags "github.com/docker/docker/cli/flags"
@@ -43,6 +44,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 		container.NewUnpauseCommand(dockerCli),
 		image.NewRemoveCommand(dockerCli),
 		image.NewSearchCommand(dockerCli),
+		network.NewNetworkCommand(dockerCli),
 		volume.NewVolumeCommand(dockerCli),
 	)
 

+ 0 - 1
cli/usage.go

@@ -23,7 +23,6 @@ var DockerCommandUsage = []Command{
 	{"load", "Load an image from a tar archive or STDIN"},
 	{"login", "Log in to a Docker registry"},
 	{"logout", "Log out from a Docker registry"},
-	{"network", "Manage Docker networks"},
 	{"pause", "Pause all processes within a container"},
 	{"port", "List port mappings or a specific mapping for the CONTAINER"},
 	{"ps", "List containers"},

+ 6 - 0
integration-cli/docker_cli_help_test.go

@@ -119,6 +119,12 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
 		cmdsToTest = append(cmdsToTest, "volume inspect")
 		cmdsToTest = append(cmdsToTest, "volume ls")
 		cmdsToTest = append(cmdsToTest, "volume rm")
+		cmdsToTest = append(cmdsToTest, "network connect")
+		cmdsToTest = append(cmdsToTest, "network create")
+		cmdsToTest = append(cmdsToTest, "network disconnect")
+		cmdsToTest = append(cmdsToTest, "network inspect")
+		cmdsToTest = append(cmdsToTest, "network ls")
+		cmdsToTest = append(cmdsToTest, "network rm")
 
 		// Divide the list of commands into go routines and  run the func testcommand on the commands in parallel
 		// to save runtime of test

+ 1 - 1
integration-cli/docker_cli_inspect_test.go

@@ -362,7 +362,7 @@ func (s *DockerSuite) TestInspectContainerNetworkDefault(c *check.C) {
 
 	contName := "test1"
 	dockerCmd(c, "run", "--name", contName, "-d", "busybox", "top")
-	netOut, _ := dockerCmd(c, "network", "inspect", "--format='{{.ID}}'", "bridge")
+	netOut, _ := dockerCmd(c, "network", "inspect", "--format={{.ID}}", "bridge")
 	out := inspectField(c, contName, "NetworkSettings.Networks")
 	c.Assert(out, checker.Contains, "bridge")
 	out = inspectField(c, contName, "NetworkSettings.Networks.bridge.NetworkID")

+ 2 - 2
integration-cli/docker_cli_network_unix_test.go

@@ -379,7 +379,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkCreateLabel(c *check.C) {
 	dockerCmd(c, "network", "create", "--label", testLabel+"="+testValue, testNet)
 	assertNwIsAvailable(c, testNet)
 
-	out, _, err := dockerCmdWithError("network", "inspect", "--format='{{ .Labels."+testLabel+" }}'", testNet)
+	out, _, err := dockerCmdWithError("network", "inspect", "--format={{ .Labels."+testLabel+" }}", testNet)
 	c.Assert(err, check.IsNil)
 	c.Assert(strings.TrimSpace(out), check.Equals, testValue)
 
@@ -423,7 +423,7 @@ func (s *DockerSuite) TestDockerNetworkInspect(c *check.C) {
 	c.Assert(err, check.IsNil)
 	c.Assert(networkResources, checker.HasLen, 1)
 
-	out, _ = dockerCmd(c, "network", "inspect", "--format='{{ .Name }}'", "host")
+	out, _ = dockerCmd(c, "network", "inspect", "--format={{ .Name }}", "host")
 	c.Assert(strings.TrimSpace(out), check.Equals, "host")
 }