Browse Source

Add `--filter enabled=true` for `docker plugin ls`

This fix adds `--filter enabled=true` to `docker plugin ls`,
as was specified in 28624.

The related API and docs has been updated.

An integration test has been added.

This fix fixes 28624.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Yong Tang 8 năm trước cách đây
mục cha
commit
a66e0dc349

+ 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
 	}
 	}

+ 64 - 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,69 @@ 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")
+
+	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}}`,
+			},
+		},
 	}
 	}
-	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 {

+ 18 - 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,20 @@ 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)
+
+### enabled
+
+The `enabled` filter matches on plugins enabled or disabled.
+
+
 ## Formatting
 ## Formatting
 
 
 The formatting options (`--format`) pretty-prints plugins output
 The formatting options (`--format`) pretty-prints plugins output
@@ -68,6 +85,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)

+ 28 - 0
integration-cli/docker_cli_daemon_plugins_test.go

@@ -285,3 +285,31 @@ 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)
+}

+ 29 - 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,10 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
+var acceptedPluginFilterTags = map[string]bool{
+	"enabled": 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 +264,33 @@ 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))
+
 	for _, p := range plugins {
 	for _, p := range plugins {
+		if enabledOnly && !p.PluginObj.Enabled {
+			continue
+		}
+		if disabledOnly && p.PluginObj.Enabled {
+			continue
+		}
 		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
 }
 }