Merge pull request #28789 from yongtang/28735-plugin-inspect-id-or-name
Allow `docker plugin inspect` to search based on ID or name Upstream-commit: 57ace38103e1c9fc13e40c57b0fc77361e389f54 Component: engine
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cli/command"
|
||||
"github.com/docker/docker/cli/command/inspect"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -20,7 +17,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [OPTIONS] PLUGIN [PLUGIN...]",
|
||||
Use: "inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]",
|
||||
Short: "Display detailed information on one or more plugins",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
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 {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
getRef := func(name string) (interface{}, []byte, error) {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
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())
|
||||
getRef := func(ref string) (interface{}, []byte, error) {
|
||||
return client.PluginInspectWithRaw(ctx, ref)
|
||||
}
|
||||
|
||||
return inspect.Inspect(dockerCli.Out(), opts.pluginNames, opts.format, getRef)
|
||||
|
||||
@@ -16,13 +16,13 @@ keywords: "plugin, inspect"
|
||||
# plugin inspect
|
||||
|
||||
```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
|
||||
|
||||
Options:
|
||||
-f, --format string Format the output using the given Go template
|
||||
--help Print usage
|
||||
-f, --format string Format the output using the given Go template
|
||||
--help Print usage
|
||||
```
|
||||
|
||||
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
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -23,6 +24,11 @@ import (
|
||||
"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.
|
||||
func (pm *Manager) Disable(name string) error {
|
||||
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
|
||||
func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
|
||||
p, err := pm.pluginStore.GetByName(name)
|
||||
if err != nil {
|
||||
func (pm *Manager) Inspect(refOrID string) (tp types.Plugin, err error) {
|
||||
// Match on full ID
|
||||
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 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) {
|
||||
@@ -244,3 +270,18 @@ func (pm *Manager) createFromContext(ctx context.Context, pluginID, pluginDir st
|
||||
|
||||
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)) }
|
||||
|
||||
// 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.
|
||||
func (ps *Store) GetByName(name string) (*v2.Plugin, error) {
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user