Sfoglia il codice sorgente

Merge pull request #28627 from yongtang/28624-docker-plugin-ls

Add `--filter enabled=true` for `docker plugin ls`
Vincent Demeester 8 anni fa
parent
commit
4c1b40b9d4

+ 2 - 1
api/server/router/plugin/backend.go

@@ -5,6 +5,7 @@ import (
 	"net/http"
 	"net/http"
 
 
 	enginetypes "github.com/docker/docker/api/types"
 	enginetypes "github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -13,7 +14,7 @@ import (
 type Backend interface {
 type Backend interface {
 	Disable(name string, config *enginetypes.PluginDisableConfig) error
 	Disable(name string, config *enginetypes.PluginDisableConfig) error
 	Enable(name string, config *enginetypes.PluginEnableConfig) error
 	Enable(name string, config *enginetypes.PluginEnableConfig) error
-	List() ([]enginetypes.Plugin, error)
+	List(filters.Args) ([]enginetypes.Plugin, error)
 	Inspect(name string) (*enginetypes.Plugin, error)
 	Inspect(name string) (*enginetypes.Plugin, error)
 	Remove(name string, config *enginetypes.PluginRmConfig) error
 	Remove(name string, config *enginetypes.PluginRmConfig) error
 	Set(name string, args []string) error
 	Set(name string, args []string) error

+ 10 - 1
api/server/router/plugin/plugin_routes.go

@@ -10,6 +10,7 @@ import (
 	distreference "github.com/docker/distribution/reference"
 	distreference "github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
@@ -253,7 +254,15 @@ func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r
 }
 }
 
 
 func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	l, err := pr.backend.List()
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	pluginFilters, err := filters.FromParam(r.Form.Get("filters"))
+	if err != nil {
+		return err
+	}
+	l, err := pr.backend.List(pluginFilters)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 5 - 2
cli/command/plugin/list.go

@@ -4,6 +4,7 @@ import (
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/formatter"
 	"github.com/docker/docker/cli/command/formatter"
+	"github.com/docker/docker/opts"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -12,10 +13,11 @@ type listOptions struct {
 	quiet   bool
 	quiet   bool
 	noTrunc bool
 	noTrunc bool
 	format  string
 	format  string
+	filter  opts.FilterOpt
 }
 }
 
 
 func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
 func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
-	var opts listOptions
+	opts := listOptions{filter: opts.NewFilterOpt()}
 
 
 	cmd := &cobra.Command{
 	cmd := &cobra.Command{
 		Use:     "ls [OPTIONS]",
 		Use:     "ls [OPTIONS]",
@@ -32,12 +34,13 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display plugin IDs")
 	flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display plugin IDs")
 	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
 	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
 	flags.StringVar(&opts.format, "format", "", "Pretty-print plugins using a Go template")
 	flags.StringVar(&opts.format, "format", "", "Pretty-print plugins using a Go template")
+	flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'enabled=true')")
 
 
 	return cmd
 	return cmd
 }
 }
 
 
 func runList(dockerCli *command.DockerCli, opts listOptions) error {
 func runList(dockerCli *command.DockerCli, opts listOptions) error {
-	plugins, err := dockerCli.Client().PluginList(context.Background())
+	plugins, err := dockerCli.Client().PluginList(context.Background(), opts.filter.Value())
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 1 - 1
client/interface.go

@@ -108,7 +108,7 @@ type NodeAPIClient interface {
 
 
 // PluginAPIClient defines API client methods for the plugins
 // PluginAPIClient defines API client methods for the plugins
 type PluginAPIClient interface {
 type PluginAPIClient interface {
-	PluginList(ctx context.Context) (types.PluginsListResponse, error)
+	PluginList(ctx context.Context, filter filters.Args) (types.PluginsListResponse, error)
 	PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
 	PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
 	PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
 	PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
 	PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error
 	PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error

+ 13 - 2
client/plugin_list.go

@@ -2,15 +2,26 @@ package client
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"net/url"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
 // PluginList returns the installed plugins
 // PluginList returns the installed plugins
-func (cli *Client) PluginList(ctx context.Context) (types.PluginsListResponse, error) {
+func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (types.PluginsListResponse, error) {
 	var plugins types.PluginsListResponse
 	var plugins types.PluginsListResponse
-	resp, err := cli.get(ctx, "/plugins", nil, nil)
+	query := url.Values{}
+
+	if filter.Len() > 0 {
+		filterJSON, err := filters.ToParamWithVersion(cli.version, filter)
+		if err != nil {
+			return plugins, err
+		}
+		query.Set("filters", filterJSON)
+	}
+	resp, err := cli.get(ctx, "/plugins", query, nil)
 	if err != nil {
 	if err != nil {
 		return plugins, err
 		return plugins, err
 	}
 	}

+ 76 - 28
client/plugin_list_test.go

@@ -10,6 +10,7 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -18,7 +19,7 @@ func TestPluginListError(t *testing.T) {
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 	}
 	}
 
 
-	_, err := client.PluginList(context.Background())
+	_, err := client.PluginList(context.Background(), filters.NewArgs())
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
 	if err == nil || err.Error() != "Error response from daemon: Server error" {
 		t.Fatalf("expected a Server Error, got %v", err)
 		t.Fatalf("expected a Server Error, got %v", err)
 	}
 	}
@@ -26,34 +27,81 @@ func TestPluginListError(t *testing.T) {
 
 
 func TestPluginList(t *testing.T) {
 func TestPluginList(t *testing.T) {
 	expectedURL := "/plugins"
 	expectedURL := "/plugins"
-	client := &Client{
-		client: newMockClient(func(req *http.Request) (*http.Response, error) {
-			if !strings.HasPrefix(req.URL.Path, expectedURL) {
-				return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
-			}
-			content, err := json.Marshal([]*types.Plugin{
-				{
-					ID: "plugin_id1",
-				},
-				{
-					ID: "plugin_id2",
-				},
-			})
-			if err != nil {
-				return nil, err
-			}
-			return &http.Response{
-				StatusCode: http.StatusOK,
-				Body:       ioutil.NopCloser(bytes.NewReader(content)),
-			}, nil
-		}),
-	}
 
 
-	plugins, err := client.PluginList(context.Background())
-	if err != nil {
-		t.Fatal(err)
+	enabledFilters := filters.NewArgs()
+	enabledFilters.Add("enabled", "true")
+
+	capabilityFilters := filters.NewArgs()
+	capabilityFilters.Add("capability", "volumedriver")
+	capabilityFilters.Add("capability", "authz")
+
+	listCases := []struct {
+		filters             filters.Args
+		expectedQueryParams map[string]string
+	}{
+		{
+			filters: filters.NewArgs(),
+			expectedQueryParams: map[string]string{
+				"all":     "",
+				"filter":  "",
+				"filters": "",
+			},
+		},
+		{
+			filters: enabledFilters,
+			expectedQueryParams: map[string]string{
+				"all":     "",
+				"filter":  "",
+				"filters": `{"enabled":{"true":true}}`,
+			},
+		},
+		{
+			filters: capabilityFilters,
+			expectedQueryParams: map[string]string{
+				"all":     "",
+				"filter":  "",
+				"filters": `{"capability":{"authz":true,"volumedriver":true}}`,
+			},
+		},
 	}
 	}
-	if len(plugins) != 2 {
-		t.Fatalf("expected 2 plugins, got %v", plugins)
+
+	for _, listCase := range listCases {
+		client := &Client{
+			client: newMockClient(func(req *http.Request) (*http.Response, error) {
+				if !strings.HasPrefix(req.URL.Path, expectedURL) {
+					return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
+				}
+				query := req.URL.Query()
+				for key, expected := range listCase.expectedQueryParams {
+					actual := query.Get(key)
+					if actual != expected {
+						return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
+					}
+				}
+				content, err := json.Marshal([]*types.Plugin{
+					{
+						ID: "plugin_id1",
+					},
+					{
+						ID: "plugin_id2",
+					},
+				})
+				if err != nil {
+					return nil, err
+				}
+				return &http.Response{
+					StatusCode: http.StatusOK,
+					Body:       ioutil.NopCloser(bytes.NewReader(content)),
+				}, nil
+			}),
+		}
+
+		plugins, err := client.PluginList(context.Background(), listCase.filters)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(plugins) != 2 {
+			t.Fatalf("expected 2 plugins, got %v", plugins)
+		}
 	}
 	}
 }
 }

+ 2 - 1
daemon/cluster/executor/container/executor.go

@@ -5,6 +5,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
@@ -53,7 +54,7 @@ func (e *executor) Describe(ctx context.Context) (*api.NodeDescription, error) {
 	addPlugins("Authorization", info.Plugins.Authorization)
 	addPlugins("Authorization", info.Plugins.Authorization)
 
 
 	// add v2 plugins
 	// add v2 plugins
-	v2Plugins, err := e.backend.PluginManager().List()
+	v2Plugins, err := e.backend.PluginManager().List(filters.NewArgs())
 	if err == nil {
 	if err == nil {
 		for _, plgn := range v2Plugins {
 		for _, plgn := range v2Plugins {
 			for _, typ := range plgn.Config.Interface.Types {
 			for _, typ := range plgn.Config.Interface.Types {

+ 6 - 2
docs/extend/config.md

@@ -51,9 +51,13 @@ Config provides the base accessible fields for working with V0 plugin format
 
 
       currently supported:
       currently supported:
 
 
-      	- **docker.volumedriver/1.0**
+        - **docker.volumedriver/1.0**
 
 
-      	- **docker.authz/1.0**
+        - **docker.networkdriver/1.0**
+
+        - **docker.ipamdriver/1.0**
+
+        - **docker.authz/1.0**
 
 
     - **`socket`** *string*
     - **`socket`** *string*
 
 

+ 33 - 0
docs/reference/commandline/plugin_ls.md

@@ -24,6 +24,7 @@ Aliases:
   ls, list
   ls, list
 
 
 Options:
 Options:
+  -f, --filter filter   Provide filter values (e.g. 'enabled=true')
       --format string   Pretty-print plugins using a Go template
       --format string   Pretty-print plugins using a Go template
       --help            Print usage
       --help            Print usage
       --no-trunc        Don't truncate output
       --no-trunc        Don't truncate output
@@ -32,6 +33,8 @@ Options:
 
 
 Lists all the plugins that are currently installed. You can install plugins
 Lists all the plugins that are currently installed. You can install plugins
 using the [`docker plugin install`](plugin_install.md) command.
 using the [`docker plugin install`](plugin_install.md) command.
+You can also filter using the `-f` or `--filter` flag.
+Refer to the [filtering](#filtering) section for more information about available filter options.
 
 
 Example output:
 Example output:
 
 
@@ -42,6 +45,35 @@ ID                  NAME                             TAG                 DESCRIP
 69553ca1d123        tiborvass/sample-volume-plugin   latest              A test plugin for Docker   true
 69553ca1d123        tiborvass/sample-volume-plugin   latest              A test plugin for Docker   true
 ```
 ```
 
 
+## Filtering
+
+The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
+than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
+
+The currently supported filters are:
+
+* enabled (boolean - true or false, 0 or 1)
+* capability (string - currently `volumedriver`, `networkdriver`, `ipamdriver`, or `authz`)
+
+### enabled
+
+The `enabled` filter matches on plugins enabled or disabled.
+
+### capability
+
+The `capability` filter matches on plugin capabilities. One plugin
+might have multiple capabilities. Currently `volumedriver`, `networkdriver`,
+`ipamdriver`, and `authz` are supported capabilities.
+
+```bash
+$ docker plugin install --disable tiborvass/no-remove
+tiborvass/no-remove
+
+$ docker plugin ls --filter enabled=true
+NAME                  TAG                 DESCRIPTION                ENABLED
+```
+
+
 ## Formatting
 ## Formatting
 
 
 The formatting options (`--format`) pretty-prints plugins output
 The formatting options (`--format`) pretty-prints plugins output
@@ -68,6 +100,7 @@ $ docker plugin ls --format "{{.ID}}: {{.Name}}"
 4be01827a72e: tiborvass/no-remove
 4be01827a72e: tiborvass/no-remove
 ```
 ```
 
 
+
 ## Related information
 ## Related information
 
 
 * [plugin create](plugin_create.md)
 * [plugin create](plugin_create.md)

+ 55 - 0
integration-cli/docker_cli_daemon_plugins_test.go

@@ -285,3 +285,58 @@ func existsMountpointWithPrefix(mountpointPrefix string) (bool, error) {
 	}
 	}
 	return false, nil
 	return false, nil
 }
 }
+
+func (s *DockerDaemonSuite) TestPluginListFilterEnabled(c *check.C) {
+	testRequires(c, Network)
+
+	s.d.Start(c)
+
+	out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pNameWithTag, "--disable")
+	c.Assert(err, check.IsNil, check.Commentf(out))
+
+	defer func() {
+		if out, err := s.d.Cmd("plugin", "remove", pNameWithTag); err != nil {
+			c.Fatalf("Could not remove plugin: %v %s", err, out)
+		}
+	}()
+
+	out, err = s.d.Cmd("plugin", "ls", "--filter", "enabled=true")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Not(checker.Contains), pName)
+
+	out, err = s.d.Cmd("plugin", "ls", "--filter", "enabled=false")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, pName)
+	c.Assert(out, checker.Contains, "false")
+
+	out, err = s.d.Cmd("plugin", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, pName)
+}
+
+func (s *DockerDaemonSuite) TestPluginListFilterCapability(c *check.C) {
+	testRequires(c, Network)
+
+	s.d.Start(c)
+
+	out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pNameWithTag, "--disable")
+	c.Assert(err, check.IsNil, check.Commentf(out))
+
+	defer func() {
+		if out, err := s.d.Cmd("plugin", "remove", pNameWithTag); err != nil {
+			c.Fatalf("Could not remove plugin: %v %s", err, out)
+		}
+	}()
+
+	out, err = s.d.Cmd("plugin", "ls", "--filter", "capability=volumedriver")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, pName)
+
+	out, err = s.d.Cmd("plugin", "ls", "--filter", "capability=authz")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Not(checker.Contains), pName)
+
+	out, err = s.d.Cmd("plugin", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, pName)
+}

+ 38 - 1
plugin/backend_linux.go

@@ -18,6 +18,7 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/manifest/schema2"
 	"github.com/docker/distribution/manifest/schema2"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/distribution"
 	"github.com/docker/docker/distribution"
 	progressutils "github.com/docker/docker/distribution/utils"
 	progressutils "github.com/docker/docker/distribution/utils"
 	"github.com/docker/docker/distribution/xfer"
 	"github.com/docker/docker/distribution/xfer"
@@ -33,6 +34,11 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
+var acceptedPluginFilterTags = map[string]bool{
+	"enabled":    true,
+	"capability": true,
+}
+
 // Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
 // Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
 func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) error {
 func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) error {
 	p, err := pm.config.Store.GetV2Plugin(refOrID)
 	p, err := pm.config.Store.GetV2Plugin(refOrID)
@@ -259,10 +265,41 @@ func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, m
 }
 }
 
 
 // List displays the list of plugins and associated metadata.
 // List displays the list of plugins and associated metadata.
-func (pm *Manager) List() ([]types.Plugin, error) {
+func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
+	if err := pluginFilters.Validate(acceptedPluginFilterTags); err != nil {
+		return nil, err
+	}
+
+	enabledOnly := false
+	disabledOnly := false
+	if pluginFilters.Include("enabled") {
+		if pluginFilters.ExactMatch("enabled", "true") {
+			enabledOnly = true
+		} else if pluginFilters.ExactMatch("enabled", "false") {
+			disabledOnly = true
+		} else {
+			return nil, fmt.Errorf("Invalid filter 'enabled=%s'", pluginFilters.Get("enabled"))
+		}
+	}
+
 	plugins := pm.config.Store.GetAll()
 	plugins := pm.config.Store.GetAll()
 	out := make([]types.Plugin, 0, len(plugins))
 	out := make([]types.Plugin, 0, len(plugins))
+
+next:
 	for _, p := range plugins {
 	for _, p := range plugins {
+		if enabledOnly && !p.PluginObj.Enabled {
+			continue
+		}
+		if disabledOnly && p.PluginObj.Enabled {
+			continue
+		}
+		if pluginFilters.Include("capability") {
+			for _, f := range p.GetTypes() {
+				if !pluginFilters.Match("capability", f.Capability) {
+					continue next
+				}
+			}
+		}
 		out = append(out, p.PluginObj)
 		out = append(out, p.PluginObj)
 	}
 	}
 	return out, nil
 	return out, nil

+ 2 - 1
plugin/backend_unsupported.go

@@ -8,6 +8,7 @@ import (
 	"net/http"
 	"net/http"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -40,7 +41,7 @@ func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, m
 }
 }
 
 
 // List displays the list of plugins and associated metadata.
 // List displays the list of plugins and associated metadata.
-func (pm *Manager) List() ([]types.Plugin, error) {
+func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
 	return nil, errNotSupported
 	return nil, errNotSupported
 }
 }