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:
parent
541150da53
commit
a66e0dc349
|
@ -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,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,11 +27,45 @@ func TestPluginListError(t *testing.T) {
|
||||||
|
|
||||||
func TestPluginList(t *testing.T) {
|
func TestPluginList(t *testing.T) {
|
||||||
expectedURL := "/plugins"
|
expectedURL := "/plugins"
|
||||||
|
|
||||||
|
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}}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, listCase := range listCases {
|
||||||
client := &Client{
|
client := &Client{
|
||||||
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
||||||
if !strings.HasPrefix(req.URL.Path, expectedURL) {
|
if !strings.HasPrefix(req.URL.Path, expectedURL) {
|
||||||
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
|
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{
|
content, err := json.Marshal([]*types.Plugin{
|
||||||
{
|
{
|
||||||
ID: "plugin_id1",
|
ID: "plugin_id1",
|
||||||
|
@ -49,7 +84,7 @@ func TestPluginList(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins, err := client.PluginList(context.Background())
|
plugins, err := client.PluginList(context.Background(), listCase.filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -57,3 +92,4 @@ func TestPluginList(t *testing.T) {
|
||||||
t.Fatalf("expected 2 plugins, got %v", plugins)
|
t.Fatalf("expected 2 plugins, got %v", plugins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue