diff --git a/api/client/client.go b/api/client/client.go index 7da14e2cb..7707aba52 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -65,7 +65,7 @@ type apiClient interface { NetworkCreate(options types.NetworkCreate) (types.NetworkCreateResponse, error) NetworkDisconnect(networkID, containerID string) error NetworkInspect(networkID string) (types.NetworkResource, error) - NetworkList() ([]types.NetworkResource, error) + NetworkList(options types.NetworkListOptions) ([]types.NetworkResource, error) NetworkRemove(networkID string) error RegistryLogin(auth types.AuthConfig) (types.AuthResponse, error) ServerVersion() (types.Version, error) diff --git a/api/client/lib/network.go b/api/client/lib/network.go index 0ad9b24c3..e38d9e8c8 100644 --- a/api/client/lib/network.go +++ b/api/client/lib/network.go @@ -3,8 +3,10 @@ package lib import ( "encoding/json" "net/http" + "net/url" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" ) // NetworkCreate creates a new network in the docker host. @@ -44,9 +46,18 @@ func (cli *Client) NetworkDisconnect(networkID, containerID string) error { } // NetworkList returns the list of networks configured in the docker host. -func (cli *Client) NetworkList() ([]types.NetworkResource, error) { +func (cli *Client) NetworkList(options types.NetworkListOptions) ([]types.NetworkResource, error) { + query := url.Values{} + if options.Filters.Len() > 0 { + filterJSON, err := filters.ToParam(options.Filters) + if err != nil { + return nil, err + } + + query.Set("filters", filterJSON) + } var networkResources []types.NetworkResource - resp, err := cli.get("/networks", nil, nil) + resp, err := cli.get("/networks", query, nil) if err != nil { return networkResources, err } diff --git a/api/client/network.go b/api/client/network.go index b6e70f5f4..8c769d56d 100644 --- a/api/client/network.go +++ b/api/client/network.go @@ -7,6 +7,7 @@ import ( "text/tabwriter" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" Cli "github.com/docker/docker/cli" "github.com/docker/docker/opts" @@ -138,12 +139,29 @@ func (cli *DockerCli) CmdNetworkLs(args ...string) error { 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) - if err := cmd.ParseFlags(args, true); err != nil { + err := cmd.ParseFlags(args, true) + if err != nil { return err } - networkResources, err := cli.client.NetworkList() + // 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(options) if err != nil { return err } diff --git a/api/server/router/network/backend.go b/api/server/router/network/backend.go index ad6c56cc6..75bd12c5d 100644 --- a/api/server/router/network/backend.go +++ b/api/server/router/network/backend.go @@ -12,6 +12,7 @@ type Backend interface { FindNetwork(idName string) (libnetwork.Network, error) GetNetwork(idName string, by int) (libnetwork.Network, error) GetNetworksByID(partialID string) []libnetwork.Network + GetAllNetworks() []libnetwork.Network CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string) (libnetwork.Network, error) ConnectContainerToNetwork(containerName, networkName string) error diff --git a/api/server/router/network/filter.go b/api/server/router/network/filter.go new file mode 100644 index 000000000..58bdb1044 --- /dev/null +++ b/api/server/router/network/filter.go @@ -0,0 +1,110 @@ +package network + +import ( + "fmt" + "regexp" + "strings" + + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/runconfig" + "github.com/docker/libnetwork" +) + +type filterHandler func([]libnetwork.Network, string) ([]libnetwork.Network, error) + +var ( + // supportedFilters predefined some supported filter handler function + supportedFilters = map[string]filterHandler{ + "type": filterNetworkByType, + "name": filterNetworkByName, + "id": filterNetworkByID, + } + + // acceptFilters is an acceptable filter flag list + // generated for validation. e.g. + // acceptedFilters = map[string]bool{ + // "type": true, + // "name": true, + // "id": true, + // } + acceptedFilters = func() map[string]bool { + ret := make(map[string]bool) + for k := range supportedFilters { + ret[k] = true + } + return ret + }() +) + +func filterNetworkByType(nws []libnetwork.Network, netType string) (retNws []libnetwork.Network, err error) { + switch netType { + case "builtin": + for _, nw := range nws { + if runconfig.IsPreDefinedNetwork(nw.Name()) { + retNws = append(retNws, nw) + } + } + case "custom": + for _, nw := range nws { + if !runconfig.IsPreDefinedNetwork(nw.Name()) { + retNws = append(retNws, nw) + } + } + default: + return nil, fmt.Errorf("Invalid filter: 'type'='%s'", netType) + } + return retNws, nil +} + +func filterNetworkByName(nws []libnetwork.Network, name string) (retNws []libnetwork.Network, err error) { + for _, nw := range nws { + // exact match (fast path) + if nw.Name() == name { + retNws = append(retNws, nw) + continue + } + + // regexp match (slow path) + match, err := regexp.MatchString(name, nw.Name()) + if err != nil || !match { + continue + } else { + retNws = append(retNws, nw) + } + } + return retNws, nil +} + +func filterNetworkByID(nws []libnetwork.Network, id string) (retNws []libnetwork.Network, err error) { + for _, nw := range nws { + if strings.HasPrefix(nw.ID(), id) { + retNws = append(retNws, nw) + } + } + return retNws, nil +} + +// filterAllNetworks filter network list according to user specified filter +// and return user chosen networks +func filterNetworks(nws []libnetwork.Network, filter filters.Args) ([]libnetwork.Network, error) { + // if filter is empty, return original network list + if filter.Len() == 0 { + return nws, nil + } + + var displayNet []libnetwork.Network + for fkey, fhandler := range supportedFilters { + errFilter := filter.WalkValues(fkey, func(fval string) error { + passList, err := fhandler(nws, fval) + if err != nil { + return err + } + displayNet = append(displayNet, passList...) + return nil + }) + if errFilter != nil { + return nil, errFilter + } + } + return displayNet, nil +} diff --git a/api/server/router/network/network_routes.go b/api/server/router/network/network_routes.go index da647080d..6c4e22e7e 100644 --- a/api/server/router/network/network_routes.go +++ b/api/server/router/network/network_routes.go @@ -7,7 +7,6 @@ import ( "golang.org/x/net/context" - "github.com/Sirupsen/logrus" "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" @@ -28,29 +27,24 @@ func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWrit return err } - list := []*types.NetworkResource{} - netFilters.WalkValues("name", func(name string) error { - if nw, err := n.backend.GetNetwork(name, daemon.NetworkByName); err == nil { - list = append(list, buildNetworkResource(nw)) - } else { - logrus.Errorf("failed to get network for filter=%s : %v", name, err) - } - return nil - }) - - netFilters.WalkValues("id", func(id string) error { - for _, nw := range n.backend.GetNetworksByID(id) { - list = append(list, buildNetworkResource(nw)) - } - return nil - }) - - if !netFilters.Include("name") && !netFilters.Include("id") { - nwList := n.backend.GetNetworksByID("") - for _, nw := range nwList { - list = append(list, buildNetworkResource(nw)) + if netFilters.Len() != 0 { + if err := netFilters.Validate(acceptedFilters); err != nil { + return err } } + + list := []*types.NetworkResource{} + + nwList := n.backend.GetAllNetworks() + displayable, err := filterNetworks(nwList, netFilters) + if err != nil { + return err + } + + for _, nw := range displayable { + list = append(list, buildNetworkResource(nw)) + } + return httputils.WriteJSON(w, http.StatusOK, list) } diff --git a/api/types/client.go b/api/types/client.go index 4a0877f97..c588508e7 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -86,6 +86,11 @@ type EventsOptions struct { Filters filters.Args } +// NetworkListOptions holds parameters to filter the list of networks with. +type NetworkListOptions struct { + Filters filters.Args +} + // HijackedResponse holds connection information for a hijacked request. type HijackedResponse struct { Conn net.Conn diff --git a/daemon/network.go b/daemon/network.go index 53742faa0..e803ab196 100644 --- a/daemon/network.go +++ b/daemon/network.go @@ -85,6 +85,19 @@ func (daemon *Daemon) GetNetworksByID(partialID string) []libnetwork.Network { return list } +// GetAllNetworks returns a list containing all networks +func (daemon *Daemon) GetAllNetworks() []libnetwork.Network { + c := daemon.netController + list := []libnetwork.Network{} + l := func(nw libnetwork.Network) bool { + list = append(list, nw) + return false + } + c.WalkNetworks(l) + + return list +} + // CreateNetwork creates a network with the given name, driver and other optional parameters func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM, options map[string]string) (libnetwork.Network, error) { c := daemon.netController diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index a458cd5ef..c18edbb16 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -109,6 +109,7 @@ This section lists each version from latest to oldest. Each listing includes a the push or pull completes. * `POST /containers/create` now allows you to set a read/write rate limit for a device (in bytes per second or IO per second). +* `GET /networks` now supports filtering by `name`, `id` and `type`. ### v1.21 API changes diff --git a/docs/reference/api/docker_remote_api_v1.22.md b/docs/reference/api/docker_remote_api_v1.22.md index c792ccecf..59b500230 100644 --- a/docs/reference/api/docker_remote_api_v1.22.md +++ b/docs/reference/api/docker_remote_api_v1.22.md @@ -2728,7 +2728,7 @@ Status Codes **Example request**: - GET /networks HTTP/1.1 + GET /networks?filters={"type":{"custom":true}} HTTP/1.1 **Example response**: @@ -2794,11 +2794,12 @@ Content-Type: application/json ] ``` - - Query Parameters: -- **filters** - JSON encoded value of the filters (a `map[string][]string`) to process on the networks list. Available filters: `name=[network-names]` , `id=[network-ids]` +- **filters** - JSON encoded network list filter. The filter value is one of: + - `name=` Matches all or part of a network name. + - `id=` Matches all or part of a network id. + - `type=["custom"|"builtin"]` Filters networks by type. The `custom` keyword returns all user-defined networks. Status Codes: diff --git a/docs/reference/commandline/network_ls.md b/docs/reference/commandline/network_ls.md index 9b4dbddc8..b5ddbf409 100644 --- a/docs/reference/commandline/network_ls.md +++ b/docs/reference/commandline/network_ls.md @@ -13,6 +13,7 @@ parent = "smn_cli" Usage: docker network ls [OPTIONS] Lists all the networks created by the user + -f, --filter=[] Filter output based on conditions provided --help=false Print usage --no-trunc=false Do not truncate the output -q, --quiet=false Only display numeric IDs @@ -38,8 +39,91 @@ NETWORK ID NAME c288470c46f6c8949c5f7e5099b5b7947b07eabe8d9a27d79a9cbf111adcbf47 host host 7b369448dccbf865d397c8d2be0cda7cf7edc6b0945f77d2529912ae917a0185 bridge bridge 95e74588f40db048e86320c6526440c504650a1ff3e9f7d60a497c4d2163e5bd foo bridge +63d1ff1f77b07ca51070a8c227e962238358bd310bde1529cf62e6c307ade161 dev bridge ``` +## Filtering + +The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there +is more than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`). +Multiple filter flags are combined as an `OR` filter. For example, +`-f type=custom -f type=builtin` returns both `custom` and `builtin` networks. + +The currently supported filters are: + +* id (network's id) +* name (network's name) +* type (custom|builtin) + +#### Type + +The `type` filter supports two values; `builtin` displays predefined networks +(`bridge`, `none`, `host`), whereas `custom` displays user defined networks. + +The following filter matches all user defined networks: + +```bash +$ docker network ls --filter type=custom +NETWORK ID NAME DRIVER +95e74588f40d foo bridge +63d1ff1f77b0 dev bridge +``` + +By having this flag it allows for batch cleanup. For example, use this filter +to delete all user defined networks: + +```bash +$ docker network rm `docker network ls --filter type=custom -q` +``` + +A warning will be issued when trying to remove a network that has containers +attached. + +#### Name + +The `name` filter matches on all or part of a network's name. + +The following filter matches all networks with a name containing the `foobar` string. + +```bash +$ docker network ls --filter name=foobar +NETWORK ID NAME DRIVER +06e7eef0a170 foobar bridge +``` + +You can also filter for a substring in a name as this shows: + +```bash +$ docker ps --filter name=foo +NETWORK ID NAME DRIVER +95e74588f40d foo bridge +06e7eef0a170 foobar bridge +``` + +#### ID + +The `id` filter matches on all or part of a network's ID. + +The following filter matches all networks with a name containing the +`06e7eef01700` string. + +```bash +$ docker network ls --filter id=63d1ff1f77b07ca51070a8c227e962238358bd310bde1529cf62e6c307ade161 +NETWORK ID NAME DRIVER +63d1ff1f77b0 dev bridge +``` + +You can also filter for a substring in a ID as this shows: + +```bash +$ docker ps --filter id=95e74588f40d +NETWORK ID NAME DRIVER +95e74588f40d foo bridge + +$ docker ps --filter id=95e +NETWORK ID NAME DRIVER +95e74588f40d foo bridge +``` ## Related information diff --git a/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index 12669df0f..7a4a497cd 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/integration-cli/docker_cli_network_unix_test.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptest" "os" + "sort" "strings" "github.com/docker/docker/api/types" @@ -242,6 +243,25 @@ func isNwPresent(c *check.C, name string) bool { return false } +// assertNwList checks network list retrived with ls command +// equals to expected network list +// note: out should be `network ls [option]` result +func assertNwList(c *check.C, out string, expectNws []string) { + lines := strings.Split(out, "\n") + var nwList []string + for _, line := range lines[1 : len(lines)-1] { + netFields := strings.Fields(line) + // wrap all network name in nwList + nwList = append(nwList, netFields[1]) + } + // first need to sort out and expected + sort.StringSlice(nwList).Sort() + sort.StringSlice(expectNws).Sort() + + // network ls should contains all expected networks + c.Assert(nwList, checker.DeepEquals, expectNws) +} + func getNwResource(c *check.C, name string) *types.NetworkResource { out, _ := dockerCmd(c, "network", "inspect", name) nr := []types.NetworkResource{} @@ -257,6 +277,32 @@ func (s *DockerNetworkSuite) TestDockerNetworkLsDefault(c *check.C) { } } +func (s *DockerNetworkSuite) TestDockerNetworkLsFilter(c *check.C) { + out, _ := dockerCmd(c, "network", "create", "dev") + defer func() { + dockerCmd(c, "network", "rm", "dev") + }() + containerID := strings.TrimSpace(out) + + // filter with partial ID and partial name + // only show 'bridge' and 'dev' network + out, _ = dockerCmd(c, "network", "ls", "-f", "id="+containerID[0:5], "-f", "name=dge") + assertNwList(c, out, []string{"dev", "bridge"}) + + // only show built-in network (bridge, none, host) + out, _ = dockerCmd(c, "network", "ls", "-f", "type=builtin") + assertNwList(c, out, []string{"bridge", "none", "host"}) + + // only show custom networks (dev) + out, _ = dockerCmd(c, "network", "ls", "-f", "type=custom") + assertNwList(c, out, []string{"dev"}) + + // show all networks with filter + // it should be equivalent of ls without option + out, _ = dockerCmd(c, "network", "ls", "-f", "type=custom", "-f", "type=builtin") + assertNwList(c, out, []string{"dev", "bridge", "host", "none"}) +} + func (s *DockerNetworkSuite) TestDockerNetworkCreateDelete(c *check.C) { dockerCmd(c, "network", "create", "test") assertNwIsAvailable(c, "test") diff --git a/man/docker-network-ls.1.md b/man/docker-network-ls.1.md index 3d1a1fbe4..ceca40573 100644 --- a/man/docker-network-ls.1.md +++ b/man/docker-network-ls.1.md @@ -6,6 +6,7 @@ docker-network-ls - list networks # SYNOPSIS **docker network ls** +[**-f**|**--filter**[=*[]*]] [**--no-trunc**[=*true*|*false*]] [**-q**|**--quiet**[=*true*|*false*]] [**--help**] @@ -16,7 +17,7 @@ Lists all the networks the Engine `daemon` knows about. This includes the networks that span across multiple hosts in a cluster, for example: ```bash - $ sudo docker network ls + $ docker network ls NETWORK ID NAME DRIVER 7fca4eb8c647 bridge bridge 9f904ee27bf5 none null @@ -27,16 +28,103 @@ networks that span across multiple hosts in a cluster, for example: Use the `--no-trunc` option to display the full network id: ```bash -docker network ls --no-trunc +$ docker network ls --no-trunc NETWORK ID NAME DRIVER 18a2866682b85619a026c81b98a5e375bd33e1b0936a26cc497c283d27bae9b3 none null c288470c46f6c8949c5f7e5099b5b7947b07eabe8d9a27d79a9cbf111adcbf47 host host 7b369448dccbf865d397c8d2be0cda7cf7edc6b0945f77d2529912ae917a0185 bridge bridge 95e74588f40db048e86320c6526440c504650a1ff3e9f7d60a497c4d2163e5bd foo bridge +63d1ff1f77b07ca51070a8c227e962238358bd310bde1529cf62e6c307ade161 dev bridge +``` + +## Filtering + +The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there +is more than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`). +Multiple filter flags are combined as an `OR` filter. For example, +`-f type=custom -f type=builtin` returns both `custom` and `builtin` networks. + +The currently supported filters are: + +* id (network's id) +* name (network's name) +* type (custom|builtin) + +#### Type + +The `type` filter supports two values; `builtin` displays predefined networks +(`bridge`, `none`, `host`), whereas `custom` displays user defined networks. + +The following filter matches all user defined networks: + +```bash +$ docker network ls --filter type=custom +NETWORK ID NAME DRIVER +95e74588f40d foo bridge +63d1ff1f77b0 dev bridge +``` + +By having this flag it allows for batch cleanup. For example, use this filter +to delete all user defined networks: + +```bash +$ docker network rm `docker network ls --filter type=custom -q` +``` + +A warning will be issued when trying to remove a network that has containers +attached. + +#### Name + +The `name` filter matches on all or part of a network's name. + +The following filter matches all networks with a name containing the `foobar` string. + +```bash +$ docker network ls --filter name=foobar +NETWORK ID NAME DRIVER +06e7eef0a170 foobar bridge +``` + +You can also filter for a substring in a name as this shows: + +```bash +$ docker ps --filter name=foo +NETWORK ID NAME DRIVER +95e74588f40d foo bridge +06e7eef0a170 foobar bridge +``` + +#### ID + +The `id` filter matches on all or part of a network's ID. + +The following filter matches all networks with a name containing the +`06e7eef01700` string. + +```bash +$ docker network ls --filter id=63d1ff1f77b07ca51070a8c227e962238358bd310bde1529cf62e6c307ade161 +NETWORK ID NAME DRIVER +63d1ff1f77b0 dev bridge +``` + +You can also filter for a substring in a ID as this shows: + +```bash +$ docker ps --filter id=95e74588f40d +NETWORK ID NAME DRIVER +95e74588f40d foo bridge + +$ docker ps --filter id=95e +NETWORK ID NAME DRIVER +95e74588f40d foo bridge ``` # OPTIONS +**-f**, **--filter**=*[]* + filter output based on conditions provided. + **--no-trunc**=*true*|*false* Do not truncate the output