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>
This commit is contained in:
parent
28dde09cdf
commit
4bd202b00f
14 changed files with 581 additions and 438 deletions
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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
api/client/network/cmd.go
Normal file
31
api/client/network/cmd.go
Normal file
|
@ -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
api/client/network/connect.go
Normal file
61
api/client/network/connect.go
Normal file
|
@ -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
api/client/network/create.go
Normal file
222
api/client/network/create.go
Normal file
|
@ -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
api/client/network/disconnect.go
Normal file
41
api/client/network/disconnect.go
Normal file
|
@ -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
api/client/network/inspect.go
Normal file
44
api/client/network/inspect.go
Normal file
|
@ -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
api/client/network/list.go
Normal file
98
api/client/network/list.go
Normal file
|
@ -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
api/client/network/remove.go
Normal file
43
api/client/network/remove.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
)
|
||||
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue