Przeglądaj źródła

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 lat temu
rodzic
commit
a66e0dc349

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

@@ -5,6 +5,7 @@ import (
 	"net/http"
 
 	enginetypes "github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/reference"
 	"golang.org/x/net/context"
 )
@@ -13,7 +14,7 @@ import (
 type Backend interface {
 	Disable(name string, config *enginetypes.PluginDisableConfig) error
 	Enable(name string, config *enginetypes.PluginEnableConfig) error
-	List() ([]enginetypes.Plugin, error)
+	List(filters.Args) ([]enginetypes.Plugin, error)
 	Inspect(name string) (*enginetypes.Plugin, error)
 	Remove(name string, config *enginetypes.PluginRmConfig) 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"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/streamformatter"
 	"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 {
-	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 {
 		return err
 	}

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

@@ -4,6 +4,7 @@ import (
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command/formatter"
+	"github.com/docker/docker/opts"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
 )
@@ -12,10 +13,11 @@ type listOptions struct {
 	quiet   bool
 	noTrunc bool
 	format  string
+	filter  opts.FilterOpt
 }
 
 func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
-	var opts listOptions
+	opts := listOptions{filter: opts.NewFilterOpt()}
 
 	cmd := &cobra.Command{
 		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.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
 	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
 }
 
 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 {
 		return err
 	}

+ 1 - 1
client/interface.go

@@ -108,7 +108,7 @@ type NodeAPIClient interface {
 
 // PluginAPIClient defines API client methods for the plugins
 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
 	PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
 	PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error

+ 13 - 2
client/plugin_list.go

@@ -2,15 +2,26 @@ package client
 
 import (
 	"encoding/json"
+	"net/url"
 
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"golang.org/x/net/context"
 )
 
 // 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
-	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 {
 		return plugins, err
 	}

+ 64 - 28
client/plugin_list_test.go

@@ -10,6 +10,7 @@ import (
 	"testing"
 
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"golang.org/x/net/context"
 )
 
@@ -18,7 +19,7 @@ func TestPluginListError(t *testing.T) {
 		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" {
 		t.Fatalf("expected a Server Error, got %v", err)
 	}
@@ -26,34 +27,69 @@ func TestPluginListError(t *testing.T) {
 
 func TestPluginList(t *testing.T) {
 	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"
 
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/network"
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
 	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)
 
 	// add v2 plugins
-	v2Plugins, err := e.backend.PluginManager().List()
+	v2Plugins, err := e.backend.PluginManager().List(filters.NewArgs())
 	if err == nil {
 		for _, plgn := range v2Plugins {
 			for _, typ := range plgn.Config.Interface.Types {

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

@@ -24,6 +24,7 @@ Aliases:
   ls, list
 
 Options:
+  -f, --filter filter   Provide filter values (e.g. 'enabled=true')
       --format string   Pretty-print plugins using a Go template
       --help            Print usage
       --no-trunc        Don't truncate output
@@ -32,6 +33,8 @@ Options:
 
 Lists all the plugins that are currently installed. You can install plugins
 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:
 
@@ -42,6 +45,20 @@ ID                  NAME                             TAG                 DESCRIP
 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
 
 The formatting options (`--format`) pretty-prints plugins output
@@ -68,6 +85,7 @@ $ docker plugin ls --format "{{.ID}}: {{.Name}}"
 4be01827a72e: tiborvass/no-remove
 ```
 
+
 ## Related information
 
 * [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
 }
+
+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/docker/distribution/manifest/schema2"
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/distribution"
 	progressutils "github.com/docker/docker/distribution/utils"
 	"github.com/docker/docker/distribution/xfer"
@@ -33,6 +34,10 @@ import (
 	"golang.org/x/net/context"
 )
 
+var acceptedPluginFilterTags = map[string]bool{
+	"enabled": true,
+}
+
 // Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
 func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) error {
 	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.
-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()
 	out := make([]types.Plugin, 0, len(plugins))
+
 	for _, p := range plugins {
+		if enabledOnly && !p.PluginObj.Enabled {
+			continue
+		}
+		if disabledOnly && p.PluginObj.Enabled {
+			continue
+		}
 		out = append(out, p.PluginObj)
 	}
 	return out, nil

+ 2 - 1
plugin/backend_unsupported.go

@@ -8,6 +8,7 @@ import (
 	"net/http"
 
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/reference"
 	"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.
-func (pm *Manager) List() ([]types.Plugin, error) {
+func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
 	return nil, errNotSupported
 }