diff --git a/api/client/commands.go b/api/client/commands.go index 40f44f389b..a1ae5cd5a8 100644 --- a/api/client/commands.go +++ b/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] } diff --git a/api/client/network.go b/api/client/network.go deleted file mode 100644 index 505ecac1cd..0000000000 --- a/api/client/network.go +++ /dev/null @@ -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 [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] -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] -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 -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...] -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 -} diff --git a/api/client/network/cmd.go b/api/client/network/cmd.go new file mode 100644 index 0000000000..f616129f15 --- /dev/null +++ b/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 +} diff --git a/api/client/network/connect.go b/api/client/network/connect.go new file mode 100644 index 0000000000..3184fd6bf6 --- /dev/null +++ b/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) +} diff --git a/api/client/network/create.go b/api/client/network/create.go new file mode 100644 index 0000000000..d488efb7eb --- /dev/null +++ b/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 +} diff --git a/api/client/network/disconnect.go b/api/client/network/disconnect.go new file mode 100644 index 0000000000..effb55fcd1 --- /dev/null +++ b/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) +} diff --git a/api/client/network/inspect.go b/api/client/network/inspect.go new file mode 100644 index 0000000000..c2b872bf6c --- /dev/null +++ b/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) +} diff --git a/api/client/network/list.go b/api/client/network/list.go new file mode 100644 index 0000000000..3e113b7275 --- /dev/null +++ b/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 +} diff --git a/api/client/network/remove.go b/api/client/network/remove.go new file mode 100644 index 0000000000..01a591833b --- /dev/null +++ b/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 +} diff --git a/cli/cobraadaptor/adaptor.go b/cli/cobraadaptor/adaptor.go index a3df870cb7..4ceb57757d 100644 --- a/cli/cobraadaptor/adaptor.go +++ b/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), ) diff --git a/cli/usage.go b/cli/usage.go index b5d9e904ad..f6152e5c93 100644 --- a/cli/usage.go +++ b/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"}, diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go index 4c967a04bc..60ee110f85 100644 --- a/integration-cli/docker_cli_help_test.go +++ b/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 diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index 29614537d6..1ab5642916 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/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") diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index b911168a51..3699a22482 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/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") }