mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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
11 changed files with 174 additions and 38 deletions
|
@ -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,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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}),
|
||||
|
||||
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}}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plugins, err := client.PluginList(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue