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>
This commit is contained in:
Yong Tang 2016-11-23 04:58:15 -08:00
parent 541150da53
commit a66e0dc349
11 changed files with 174 additions and 38 deletions

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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) { enabledFilters := filters.NewArgs()
if !strings.HasPrefix(req.URL.Path, expectedURL) { enabledFilters.Add("enabled", "true")
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
} listCases := []struct {
content, err := json.Marshal([]*types.Plugin{ filters filters.Args
{ expectedQueryParams map[string]string
ID: "plugin_id1", }{
}, {
{ filters: filters.NewArgs(),
ID: "plugin_id2", expectedQueryParams: map[string]string{
}, "all": "",
}) "filter": "",
if err != nil { "filters": "",
return nil, err },
} },
return &http.Response{ {
StatusCode: http.StatusOK, filters: enabledFilters,
Body: ioutil.NopCloser(bytes.NewReader(content)), expectedQueryParams: map[string]string{
}, nil "all": "",
}), "filter": "",
"filters": `{"enabled":{"true":true}}`,
},
},
} }
plugins, err := client.PluginList(context.Background()) for _, listCase := range listCases {
if err != nil { client := &Client{
t.Fatal(err) client: newMockClient(func(req *http.Request) (*http.Response, error) {
} if !strings.HasPrefix(req.URL.Path, expectedURL) {
if len(plugins) != 2 { return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
t.Fatalf("expected 2 plugins, got %v", plugins) }
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)
}
} }
} }

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

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