mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Allow docker plugin inspect
to search based on ID or name
This fix tries to address the issue raised in discussion of PR 28735 where it was not possible to manage plugin based on plugin ID. Previously it was not possible to invoke `docker plugin inspect` with a plugin ID (or ID prefix). This fix updates the implementation of `docker plugin inspect` so that it is possbile to search based on a plugin name, or a plugin ID. A short format of plugin ID (prefix) is also possible, as long as there is no ambiguity. Previously the check of `docker plugin inspect` was mostly done on the client side. This could potentially cause inconsistency between API and CMD. This fix move all the checks to daemon side so that API and CMD will be consistent. An integration test has been added to cover the changes. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
parent
0a5cb187b4
commit
0ce6e070f7
5 changed files with 129 additions and 25 deletions
|
@ -1,12 +1,9 @@
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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/inspect"
|
"github.com/docker/docker/cli/command/inspect"
|
||||||
"github.com/docker/docker/reference"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -20,7 +17,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
var opts inspectOptions
|
var opts inspectOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "inspect [OPTIONS] PLUGIN [PLUGIN...]",
|
Use: "inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]",
|
||||||
Short: "Display detailed information on one or more plugins",
|
Short: "Display detailed information on one or more plugins",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
@ -37,20 +34,8 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
getRef := func(name string) (interface{}, []byte, error) {
|
getRef := func(ref string) (interface{}, []byte, error) {
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
return client.PluginInspectWithRaw(ctx, ref)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if reference.IsNameOnly(named) {
|
|
||||||
named = reference.WithDefaultTag(named)
|
|
||||||
}
|
|
||||||
ref, ok := named.(reference.NamedTagged)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, fmt.Errorf("invalid name: %s", named.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.PluginInspectWithRaw(ctx, ref.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return inspect.Inspect(dockerCli.Out(), opts.pluginNames, opts.format, getRef)
|
return inspect.Inspect(dockerCli.Out(), opts.pluginNames, opts.format, getRef)
|
||||||
|
|
|
@ -16,13 +16,13 @@ keywords: "plugin, inspect"
|
||||||
# plugin inspect
|
# plugin inspect
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
Usage: docker plugin inspect [OPTIONS] PLUGIN [PLUGIN...]
|
Usage: docker plugin inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]
|
||||||
|
|
||||||
Display detailed information on one or more plugins
|
Display detailed information on one or more plugins
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-f, --format string Format the output using the given Go template
|
-f, --format string Format the output using the given Go template
|
||||||
--help Print usage
|
--help Print usage
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns information about a plugin. By default, this command renders all results
|
Returns information about a plugin. By default, this command renders all results
|
||||||
|
|
|
@ -201,3 +201,52 @@ func (s *DockerSuite) TestPluginCreate(c *check.C) {
|
||||||
// The output will consists of one HEADER line and one line of foo/bar-driver
|
// The output will consists of one HEADER line and one line of foo/bar-driver
|
||||||
c.Assert(len(strings.Split(strings.TrimSpace(out), "\n")), checker.Equals, 2)
|
c.Assert(len(strings.Split(strings.TrimSpace(out), "\n")), checker.Equals, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestPluginInspect(c *check.C) {
|
||||||
|
testRequires(c, DaemonIsLinux, Network)
|
||||||
|
_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
out, _, err := dockerCmdWithError("plugin", "ls")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(out, checker.Contains, pName)
|
||||||
|
c.Assert(out, checker.Contains, pTag)
|
||||||
|
c.Assert(out, checker.Contains, "true")
|
||||||
|
|
||||||
|
// Find the ID first
|
||||||
|
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
id := strings.TrimSpace(out)
|
||||||
|
c.Assert(id, checker.Not(checker.Equals), "")
|
||||||
|
|
||||||
|
// Long form
|
||||||
|
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Equals, id)
|
||||||
|
|
||||||
|
// Short form
|
||||||
|
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id[:5])
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Equals, id)
|
||||||
|
|
||||||
|
// Name with tag form
|
||||||
|
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Equals, id)
|
||||||
|
|
||||||
|
// Name without tag form
|
||||||
|
out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pName)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(strings.TrimSpace(out), checker.Equals, id)
|
||||||
|
|
||||||
|
_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(out, checker.Contains, pNameWithTag)
|
||||||
|
|
||||||
|
// After remove nothing should be found
|
||||||
|
_, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id[:5])
|
||||||
|
c.Assert(err, checker.NotNil)
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
@ -23,6 +24,11 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validFullID = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
||||||
|
validPartialID = regexp.MustCompile(`^([a-f0-9]{1,64})$`)
|
||||||
|
)
|
||||||
|
|
||||||
// Disable deactivates a plugin, which implies that they cannot be used by containers.
|
// Disable deactivates a plugin, which implies that they cannot be used by containers.
|
||||||
func (pm *Manager) Disable(name string) error {
|
func (pm *Manager) Disable(name string) error {
|
||||||
p, err := pm.pluginStore.GetByName(name)
|
p, err := pm.pluginStore.GetByName(name)
|
||||||
|
@ -53,12 +59,32 @@ func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspect examines a plugin config
|
// Inspect examines a plugin config
|
||||||
func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
|
func (pm *Manager) Inspect(refOrID string) (tp types.Plugin, err error) {
|
||||||
p, err := pm.pluginStore.GetByName(name)
|
// Match on full ID
|
||||||
if err != nil {
|
if validFullID.MatchString(refOrID) {
|
||||||
|
p, err := pm.pluginStore.GetByID(refOrID)
|
||||||
|
if err == nil {
|
||||||
|
return p.PluginObj, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match on full name
|
||||||
|
if pluginName, err := getPluginName(refOrID); err == nil {
|
||||||
|
if p, err := pm.pluginStore.GetByName(pluginName); err == nil {
|
||||||
|
return p.PluginObj, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match on partial ID
|
||||||
|
if validPartialID.MatchString(refOrID) {
|
||||||
|
p, err := pm.pluginStore.Search(refOrID)
|
||||||
|
if err == nil {
|
||||||
|
return p.PluginObj, nil
|
||||||
|
}
|
||||||
return tp, err
|
return tp, err
|
||||||
}
|
}
|
||||||
return p.PluginObj, nil
|
|
||||||
|
return tp, fmt.Errorf("no plugin name or ID associated with %q", refOrID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) {
|
func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) {
|
||||||
|
@ -244,3 +270,18 @@ func (pm *Manager) createFromContext(ctx context.Context, pluginID, pluginDir st
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPluginName(name string) (string, error) {
|
||||||
|
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if reference.IsNameOnly(named) {
|
||||||
|
named = reference.WithDefaultTag(named)
|
||||||
|
}
|
||||||
|
ref, ok := named.(reference.NamedTagged)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("invalid name: %s", named.String())
|
||||||
|
}
|
||||||
|
return ref.String(), nil
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,13 @@ type ErrNotFound string
|
||||||
|
|
||||||
func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
|
func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
|
||||||
|
|
||||||
|
// ErrAmbiguous indicates that a plugin was not found locally.
|
||||||
|
type ErrAmbiguous string
|
||||||
|
|
||||||
|
func (name ErrAmbiguous) Error() string {
|
||||||
|
return fmt.Sprintf("multiple plugins found for %q", string(name))
|
||||||
|
}
|
||||||
|
|
||||||
// GetByName retreives a plugin by name.
|
// GetByName retreives a plugin by name.
|
||||||
func (ps *Store) GetByName(name string) (*v2.Plugin, error) {
|
func (ps *Store) GetByName(name string) (*v2.Plugin, error) {
|
||||||
ps.RLock()
|
ps.RLock()
|
||||||
|
@ -253,3 +260,25 @@ func (ps *Store) CallHandler(p *v2.Plugin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search retreives a plugin by ID Prefix
|
||||||
|
// If no plugin is found, then ErrNotFound is returned
|
||||||
|
// If multiple plugins are found, then ErrAmbiguous is returned
|
||||||
|
func (ps *Store) Search(partialID string) (*v2.Plugin, error) {
|
||||||
|
ps.RLock()
|
||||||
|
defer ps.RUnlock()
|
||||||
|
|
||||||
|
var found *v2.Plugin
|
||||||
|
for id, p := range ps.plugins {
|
||||||
|
if strings.HasPrefix(id, partialID) {
|
||||||
|
if found != nil {
|
||||||
|
return nil, ErrAmbiguous(partialID)
|
||||||
|
}
|
||||||
|
found = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
return nil, ErrNotFound(partialID)
|
||||||
|
}
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue