diff --git a/components/engine/cli/command/plugin/inspect.go b/components/engine/cli/command/plugin/inspect.go index 13c7fa72d8..46ec7b229b 100644 --- a/components/engine/cli/command/plugin/inspect.go +++ b/components/engine/cli/command/plugin/inspect.go @@ -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) diff --git a/components/engine/docs/reference/commandline/plugin_inspect.md b/components/engine/docs/reference/commandline/plugin_inspect.md index 80b8e8c881..9e09e0c587 100644 --- a/components/engine/docs/reference/commandline/plugin_inspect.md +++ b/components/engine/docs/reference/commandline/plugin_inspect.md @@ -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 diff --git a/components/engine/integration-cli/docker_cli_plugins_test.go b/components/engine/integration-cli/docker_cli_plugins_test.go index f6d2f28b54..730580e568 100644 --- a/components/engine/integration-cli/docker_cli_plugins_test.go +++ b/components/engine/integration-cli/docker_cli_plugins_test.go @@ -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) +} diff --git a/components/engine/plugin/backend_linux.go b/components/engine/plugin/backend_linux.go index d40dd83e06..8c1a152d9b 100644 --- a/components/engine/plugin/backend_linux.go +++ b/components/engine/plugin/backend_linux.go @@ -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 +} diff --git a/components/engine/plugin/store/store.go b/components/engine/plugin/store/store.go index fe834dc4d7..0517f8f2c7 100644 --- a/components/engine/plugin/store/store.go +++ b/components/engine/plugin/store/store.go @@ -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 +}