package client import ( "fmt" "net" "sort" "strings" "text/tabwriter" "golang.org/x/net/context" 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{ Name: cmd.Arg(0), 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(), 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 } status := 0 for _, net := range cmd.Args() { if err := cli.client.NetworkRemove(context.Background(), net); err != nil { fmt.Fprintf(cli.err, "%s\n", err) status = 1 continue } } 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 } inspectSearcher := func(name string) (interface{}, []byte, error) { i, err := cli.client.NetworkInspect(context.Background(), name) return i, nil, err } return cli.inspectElements(*tmplStr, cmd.Args(), 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 }