瀏覽代碼

Merge pull request #23446 from tiborvass/plugins-experimental

Plugins: experimental support for new plugin management
Brian Goff 9 年之前
父節點
當前提交
6ed921dc38
共有 67 個文件被更改,包括 2497 次插入66 次删除
  1. 12 0
      api/client/plugin/cmd.go
  2. 36 0
      api/client/plugin/cmd_experimental.go
  3. 23 0
      api/client/plugin/disable.go
  4. 23 0
      api/client/plugin/enable.go
  5. 39 0
      api/client/plugin/inspect.go
  6. 51 0
      api/client/plugin/install.go
  7. 44 0
      api/client/plugin/list.go
  8. 50 0
      api/client/plugin/push.go
  9. 43 0
      api/client/plugin/remove.go
  10. 28 0
      api/client/plugin/set.go
  11. 21 0
      api/server/router/plugin/backend.go
  12. 23 0
      api/server/router/plugin/plugin.go
  13. 20 0
      api/server/router/plugin/plugin_experimental.go
  14. 9 0
      api/server/router/plugin/plugin_regular.go
  15. 103 0
      api/server/router/plugin/plugin_routes.go
  16. 2 0
      cli/cobraadaptor/adaptor.go
  17. 21 0
      cli/error.go
  18. 5 0
      cmd/dockerd/daemon.go
  19. 3 3
      cmd/dockerd/daemon_linux.go
  20. 13 0
      cmd/dockerd/daemon_no_plugin_support.go
  21. 14 0
      cmd/dockerd/daemon_plugin_support.go
  22. 9 0
      cmd/dockerd/routes.go
  23. 13 0
      cmd/dockerd/routes_experimental.go
  24. 4 4
      daemon/daemon.go
  25. 1 1
      daemon/graphdriver/plugin.go
  26. 3 3
      distribution/pull.go
  27. 52 0
      docs/reference/commandline/plugin_disable.md
  28. 52 0
      docs/reference/commandline/plugin_enable.md
  29. 135 0
      docs/reference/commandline/plugin_inspect.md
  30. 51 0
      docs/reference/commandline/plugin_install.md
  31. 40 0
      docs/reference/commandline/plugin_ls.md
  32. 41 0
      docs/reference/commandline/plugin_rm.md
  33. 1 1
      hack/vendor.sh
  34. 1 1
      integration-cli/docker_cli_external_graphdriver_unix_test.go
  35. 2 3
      pkg/authorization/authz_unix_test.go
  36. 9 4
      pkg/authorization/plugin.go
  37. 7 5
      pkg/plugins/client.go
  38. 2 2
      pkg/plugins/client_test.go
  39. 3 3
      pkg/plugins/discovery.go
  40. 2 2
      pkg/plugins/discovery_test.go
  41. 1 1
      pkg/plugins/discovery_unix_test.go
  42. 22 10
      pkg/plugins/plugins.go
  43. 139 0
      plugin/backend.go
  44. 208 0
      plugin/distribution/pull.go
  45. 134 0
      plugin/distribution/push.go
  46. 16 0
      plugin/distribution/types.go
  47. 9 0
      plugin/interface.go
  48. 23 0
      plugin/legacy.go
  49. 384 0
      plugin/manager.go
  50. 126 0
      plugin/manager_linux.go
  51. 21 0
      plugin/manager_windows.go
  52. 15 0
      vendor/src/github.com/docker/engine-api/client/errors.go
  53. 3 7
      vendor/src/github.com/docker/engine-api/client/interface.go
  54. 26 0
      vendor/src/github.com/docker/engine-api/client/interface_experimental.go
  55. 11 0
      vendor/src/github.com/docker/engine-api/client/interface_stable.go
  56. 14 0
      vendor/src/github.com/docker/engine-api/client/plugin_disable.go
  57. 14 0
      vendor/src/github.com/docker/engine-api/client/plugin_enable.go
  58. 22 0
      vendor/src/github.com/docker/engine-api/client/plugin_inspect.go
  59. 54 0
      vendor/src/github.com/docker/engine-api/client/plugin_install.go
  60. 23 0
      vendor/src/github.com/docker/engine-api/client/plugin_list.go
  61. 15 0
      vendor/src/github.com/docker/engine-api/client/plugin_push.go
  62. 14 0
      vendor/src/github.com/docker/engine-api/client/plugin_remove.go
  63. 14 0
      vendor/src/github.com/docker/engine-api/client/plugin_set.go
  64. 160 0
      vendor/src/github.com/docker/engine-api/types/plugin.go
  65. 12 11
      volume/drivers/extpoint.go
  66. 1 1
      volume/drivers/proxy_test.go
  67. 5 4
      volume/store/store.go

+ 12 - 0
api/client/plugin/cmd.go

@@ -0,0 +1,12 @@
+// +build !experimental
+
+package plugin
+
+import (
+	"github.com/docker/docker/api/client"
+	"github.com/spf13/cobra"
+)
+
+// NewPluginCommand returns a cobra command for `plugin` subcommands
+func NewPluginCommand(cmd *cobra.Command, dockerCli *client.DockerCli) {
+}

+ 36 - 0
api/client/plugin/cmd_experimental.go

@@ -0,0 +1,36 @@
+// +build experimental
+
+package plugin
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+// NewPluginCommand returns a cobra command for `plugin` subcommands
+func NewPluginCommand(rootCmd *cobra.Command, dockerCli *client.DockerCli) {
+	cmd := &cobra.Command{
+		Use:   "plugin",
+		Short: "Manage Docker plugins",
+		Args:  cli.NoArgs,
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
+		},
+	}
+
+	cmd.AddCommand(
+		newDisableCommand(dockerCli),
+		newEnableCommand(dockerCli),
+		newInspectCommand(dockerCli),
+		newInstallCommand(dockerCli),
+		newListCommand(dockerCli),
+		newRemoveCommand(dockerCli),
+		newSetCommand(dockerCli),
+		newPushCommand(dockerCli),
+	)
+
+	rootCmd.AddCommand(cmd)
+}

+ 23 - 0
api/client/plugin/disable.go

@@ -0,0 +1,23 @@
+// +build experimental
+
+package plugin
+
+import (
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newDisableCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "disable",
+		Short: "Disable a plugin",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return dockerCli.Client().PluginDisable(context.Background(), args[0])
+		},
+	}
+
+	return cmd
+}

+ 23 - 0
api/client/plugin/enable.go

@@ -0,0 +1,23 @@
+// +build experimental
+
+package plugin
+
+import (
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newEnableCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "enable",
+		Short: "Enable a plugin",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return dockerCli.Client().PluginEnable(context.Background(), args[0])
+		},
+	}
+
+	return cmd
+}

+ 39 - 0
api/client/plugin/inspect.go

@@ -0,0 +1,39 @@
+// +build experimental
+
+package plugin
+
+import (
+	"encoding/json"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "inspect",
+		Short: "Inspect a plugin",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runInspect(dockerCli, args[0])
+		},
+	}
+
+	return cmd
+}
+
+func runInspect(dockerCli *client.DockerCli, name string) error {
+	p, err := dockerCli.Client().PluginInspect(context.Background(), name)
+	if err != nil {
+		return err
+	}
+
+	b, err := json.MarshalIndent(p, "", "\t")
+	if err != nil {
+		return err
+	}
+	_, err = dockerCli.Out().Write(b)
+	return err
+}

+ 51 - 0
api/client/plugin/install.go

@@ -0,0 +1,51 @@
+// +build experimental
+
+package plugin
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/reference"
+	"github.com/docker/docker/registry"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "install",
+		Short: "Install a plugin",
+		Args:  cli.RequiresMinArgs(1), // TODO: allow for set args
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runInstall(dockerCli, args[0], args[1:])
+		},
+	}
+
+	return cmd
+}
+
+func runInstall(dockerCli *client.DockerCli, name string, args []string) error {
+	named, err := reference.ParseNamed(name) // FIXME: validate
+	if err != nil {
+		return err
+	}
+	named = reference.WithDefaultTag(named)
+	ref, ok := named.(reference.NamedTagged)
+	if !ok {
+		return fmt.Errorf("invalid name: %s", named.String())
+	}
+
+	ctx := context.Background()
+
+	repoInfo, err := registry.ParseRepositoryInfo(named)
+	authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
+
+	encodedAuth, err := client.EncodeAuthToBase64(authConfig)
+	if err != nil {
+		return err
+	}
+	// TODO: pass acceptAllPermissions and noEnable flag
+	return dockerCli.Client().PluginInstall(ctx, ref.String(), encodedAuth, false, false, dockerCli.In(), dockerCli.Out())
+}

+ 44 - 0
api/client/plugin/list.go

@@ -0,0 +1,44 @@
+// +build experimental
+
+package plugin
+
+import (
+	"fmt"
+	"text/tabwriter"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "ls",
+		Short:   "List plugins",
+		Aliases: []string{"list"},
+		Args:    cli.ExactArgs(0),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runList(dockerCli)
+		},
+	}
+
+	return cmd
+}
+
+func runList(dockerCli *client.DockerCli) error {
+	plugins, err := dockerCli.Client().PluginList(context.Background())
+	if err != nil {
+		return err
+	}
+
+	w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
+	fmt.Fprintf(w, "NAME \tTAG \tACTIVE")
+	fmt.Fprintf(w, "\n")
+
+	for _, p := range plugins {
+		fmt.Fprintf(w, "%s\t%s\t%v\n", p.Name, p.Tag, p.Active)
+	}
+	w.Flush()
+	return nil
+}

+ 50 - 0
api/client/plugin/push.go

@@ -0,0 +1,50 @@
+// +build experimental
+
+package plugin
+
+import (
+	"fmt"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/reference"
+	"github.com/docker/docker/registry"
+	"github.com/spf13/cobra"
+)
+
+func newPushCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "push",
+		Short: "Push a plugin",
+		Args:  cli.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runPush(dockerCli, args[0])
+		},
+	}
+	return cmd
+}
+
+func runPush(dockerCli *client.DockerCli, name string) error {
+	named, err := reference.ParseNamed(name) // FIXME: validate
+	if err != nil {
+		return err
+	}
+	named = reference.WithDefaultTag(named)
+	ref, ok := named.(reference.NamedTagged)
+	if !ok {
+		return fmt.Errorf("invalid name: %s", named.String())
+	}
+
+	ctx := context.Background()
+
+	repoInfo, err := registry.ParseRepositoryInfo(named)
+	authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
+
+	encodedAuth, err := client.EncodeAuthToBase64(authConfig)
+	if err != nil {
+		return err
+	}
+	return dockerCli.Client().PluginPush(ctx, ref.String(), encodedAuth)
+}

+ 43 - 0
api/client/plugin/remove.go

@@ -0,0 +1,43 @@
+// +build experimental
+
+package plugin
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+	"golang.org/x/net/context"
+)
+
+func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "rm",
+		Short:   "Remove a plugin",
+		Aliases: []string{"remove"},
+		Args:    cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runRemove(dockerCli, args)
+		},
+	}
+
+	return cmd
+}
+
+func runRemove(dockerCli *client.DockerCli, names []string) error {
+	var errs cli.Errors
+	for _, name := range names {
+		// TODO: pass names to api instead of making multiple api calls
+		if err := dockerCli.Client().PluginRemove(context.Background(), name); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		fmt.Fprintln(dockerCli.Out(), name)
+	}
+	// Do not simplify to `return errs` because even if errs == nil, it is not a nil-error interface value.
+	if errs != nil {
+		return errs
+	}
+	return nil
+}

+ 28 - 0
api/client/plugin/set.go

@@ -0,0 +1,28 @@
+// +build experimental
+
+package plugin
+
+import (
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/spf13/cobra"
+)
+
+func newSetCommand(dockerCli *client.DockerCli) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "set",
+		Short: "Change settings for a plugin",
+		Args:  cli.RequiresMinArgs(2),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runSet(dockerCli, args[0], args[1:])
+		},
+	}
+
+	return cmd
+}
+
+func runSet(dockerCli *client.DockerCli, name string, args []string) error {
+	return dockerCli.Client().PluginSet(context.Background(), name, args)
+}

+ 21 - 0
api/server/router/plugin/backend.go

@@ -0,0 +1,21 @@
+// +build experimental
+
+package plugin
+
+import (
+	"net/http"
+
+	enginetypes "github.com/docker/engine-api/types"
+)
+
+// Backend for Plugin
+type Backend interface {
+	Disable(name string) error
+	Enable(name string) error
+	List() ([]enginetypes.Plugin, error)
+	Inspect(name string) (enginetypes.Plugin, error)
+	Remove(name string) error
+	Set(name string, args []string) error
+	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
+	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
+}

+ 23 - 0
api/server/router/plugin/plugin.go

@@ -0,0 +1,23 @@
+package plugin
+
+import "github.com/docker/docker/api/server/router"
+
+// pluginRouter is a router to talk with the plugin controller
+type pluginRouter struct {
+	backend Backend
+	routes  []router.Route
+}
+
+// NewRouter initializes a new plugin router
+func NewRouter(b Backend) router.Router {
+	r := &pluginRouter{
+		backend: b,
+	}
+	r.initRoutes()
+	return r
+}
+
+// Routes returns the available routers to the plugin controller
+func (r *pluginRouter) Routes() []router.Route {
+	return r.routes
+}

+ 20 - 0
api/server/router/plugin/plugin_experimental.go

@@ -0,0 +1,20 @@
+// +build experimental
+
+package plugin
+
+import (
+	"github.com/docker/docker/api/server/router"
+)
+
+func (r *pluginRouter) initRoutes() {
+	r.routes = []router.Route{
+		router.NewGetRoute("/plugins", r.listPlugins),
+		router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin),
+		router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
+		router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
+		router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),
+		router.NewPostRoute("/plugins/pull", r.pullPlugin),
+		router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin),
+		router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin),
+	}
+}

+ 9 - 0
api/server/router/plugin/plugin_regular.go

@@ -0,0 +1,9 @@
+// +build !experimental
+
+package plugin
+
+func (r *pluginRouter) initRoutes() {}
+
+// Backend is empty so that the package can compile in non-experimental
+// (Needed by volume driver)
+type Backend interface{}

+ 103 - 0
api/server/router/plugin/plugin_routes.go

@@ -0,0 +1,103 @@
+// +build experimental
+
+package plugin
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"net/http"
+	"strings"
+
+	"github.com/docker/docker/api/server/httputils"
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	metaHeaders := map[string][]string{}
+	for k, v := range r.Header {
+		if strings.HasPrefix(k, "X-Meta-") {
+			metaHeaders[k] = v
+		}
+	}
+
+	// Get X-Registry-Auth
+	authEncoded := r.Header.Get("X-Registry-Auth")
+	authConfig := &types.AuthConfig{}
+	if authEncoded != "" {
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
+		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
+			authConfig = &types.AuthConfig{}
+		}
+	}
+
+	privileges, err := pr.backend.Pull(r.FormValue("name"), metaHeaders, authConfig)
+	if err != nil {
+		return err
+	}
+	return httputils.WriteJSON(w, http.StatusOK, privileges)
+}
+
+func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	return pr.backend.Enable(vars["name"])
+}
+
+func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	return pr.backend.Disable(vars["name"])
+}
+
+func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	return pr.backend.Remove(vars["name"])
+}
+
+func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := httputils.ParseForm(r); err != nil {
+		return err
+	}
+
+	metaHeaders := map[string][]string{}
+	for k, v := range r.Header {
+		if strings.HasPrefix(k, "X-Meta-") {
+			metaHeaders[k] = v
+		}
+	}
+
+	// Get X-Registry-Auth
+	authEncoded := r.Header.Get("X-Registry-Auth")
+	authConfig := &types.AuthConfig{}
+	if authEncoded != "" {
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
+		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
+			authConfig = &types.AuthConfig{}
+		}
+	}
+	return pr.backend.Push(vars["name"], metaHeaders, authConfig)
+}
+
+func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	var args []string
+	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
+		return err
+	}
+	return pr.backend.Set(vars["name"], args)
+}
+
+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 != nil {
+		return err
+	}
+	return httputils.WriteJSON(w, http.StatusOK, l)
+}
+
+func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	result, err := pr.backend.Inspect(vars["name"])
+	if err != nil {
+		return err
+	}
+	return httputils.WriteJSON(w, http.StatusOK, result)
+}

+ 2 - 0
cli/cobraadaptor/adaptor.go

@@ -6,6 +6,7 @@ import (
 	"github.com/docker/docker/api/client/image"
 	"github.com/docker/docker/api/client/image"
 	"github.com/docker/docker/api/client/network"
 	"github.com/docker/docker/api/client/network"
 	"github.com/docker/docker/api/client/node"
 	"github.com/docker/docker/api/client/node"
+	"github.com/docker/docker/api/client/plugin"
 	"github.com/docker/docker/api/client/registry"
 	"github.com/docker/docker/api/client/registry"
 	"github.com/docker/docker/api/client/service"
 	"github.com/docker/docker/api/client/service"
 	"github.com/docker/docker/api/client/swarm"
 	"github.com/docker/docker/api/client/swarm"
@@ -81,6 +82,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 		system.NewVersionCommand(dockerCli),
 		system.NewVersionCommand(dockerCli),
 		volume.NewVolumeCommand(dockerCli),
 		volume.NewVolumeCommand(dockerCli),
 	)
 	)
+	plugin.NewPluginCommand(rootCmd, dockerCli)
 
 
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
 	rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
 	rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")

+ 21 - 0
cli/error.go

@@ -0,0 +1,21 @@
+package cli
+
+import "bytes"
+
+// Errors is a list of errors.
+// Useful in a loop if you don't want to return the error right away and you want to display after the loop,
+// all the errors that happened during the loop.
+type Errors []error
+
+func (errs Errors) Error() string {
+	if len(errs) < 1 {
+		return ""
+	}
+	var buf bytes.Buffer
+	buf.WriteString(errs[0].Error())
+	for _, err := range errs[1:] {
+		buf.WriteString(", ")
+		buf.WriteString(err.Error())
+	}
+	return buf.String()
+}

+ 5 - 0
cmd/dockerd/daemon.go

@@ -262,6 +262,10 @@ func (cli *DaemonCli) start() (err error) {
 		<-stopc // wait for daemonCli.start() to return
 		<-stopc // wait for daemonCli.start() to return
 	})
 	})
 
 
+	if err := pluginInit(cli.Config, containerdRemote, registryService); err != nil {
+		return err
+	}
+
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("Error starting daemon: %v", err)
 		return fmt.Errorf("Error starting daemon: %v", err)
@@ -418,6 +422,7 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
 	if d.NetworkControllerEnabled() {
 	if d.NetworkControllerEnabled() {
 		routers = append(routers, network.NewRouter(d, c))
 		routers = append(routers, network.NewRouter(d, c))
 	}
 	}
+	routers = addExperimentalRouters(routers)
 
 
 	s.InitRouter(utils.IsDebugEnabled(), routers...)
 	s.InitRouter(utils.IsDebugEnabled(), routers...)
 }
 }

+ 3 - 3
cmd/dockerd/daemon_linux.go

@@ -1,8 +1,8 @@
+// +build linux
+
 package main
 package main
 
 
-import (
-	systemdDaemon "github.com/coreos/go-systemd/daemon"
-)
+import systemdDaemon "github.com/coreos/go-systemd/daemon"
 
 
 // notifySystem sends a message to the host when the server is ready to be used
 // notifySystem sends a message to the host when the server is ready to be used
 func notifySystem() {
 func notifySystem() {

+ 13 - 0
cmd/dockerd/daemon_no_plugin_support.go

@@ -0,0 +1,13 @@
+// +build !experimental !linux
+
+package main
+
+import (
+	"github.com/docker/docker/daemon"
+	"github.com/docker/docker/libcontainerd"
+	"github.com/docker/docker/registry"
+)
+
+func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
+	return nil
+}

+ 14 - 0
cmd/dockerd/daemon_plugin_support.go

@@ -0,0 +1,14 @@
+// +build linux,experimental
+
+package main
+
+import (
+	"github.com/docker/docker/daemon"
+	"github.com/docker/docker/libcontainerd"
+	"github.com/docker/docker/plugin"
+	"github.com/docker/docker/registry"
+)
+
+func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
+	return plugin.Init(config.Root, config.ExecRoot, remote, rs)
+}

+ 9 - 0
cmd/dockerd/routes.go

@@ -0,0 +1,9 @@
+// +build !experimental
+
+package main
+
+import "github.com/docker/docker/api/server/router"
+
+func addExperimentalRouters(routers []router.Router) []router.Router {
+	return routers
+}

+ 13 - 0
cmd/dockerd/routes_experimental.go

@@ -0,0 +1,13 @@
+// +build experimental
+
+package main
+
+import (
+	"github.com/docker/docker/api/server/router"
+	pluginrouter "github.com/docker/docker/api/server/router/plugin"
+	"github.com/docker/docker/plugin"
+)
+
+func addExperimentalRouters(routers []router.Router) []router.Router {
+	return append(routers, pluginrouter.NewRouter(plugin.GetManager()))
+}

+ 4 - 4
daemon/daemon.go

@@ -486,7 +486,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
 	}
 	}
 
 
 	// Configure the volumes driver
 	// Configure the volumes driver
-	volStore, err := configureVolumes(config, rootUID, rootGID)
+	volStore, err := d.configureVolumes(rootUID, rootGID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -768,8 +768,8 @@ func setDefaultMtu(config *Config) {
 	config.Mtu = defaultNetworkMtu
 	config.Mtu = defaultNetworkMtu
 }
 }
 
 
-func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, error) {
-	volumesDriver, err := local.New(config.Root, rootUID, rootGID)
+func (daemon *Daemon) configureVolumes(rootUID, rootGID int) (*store.VolumeStore, error) {
+	volumesDriver, err := local.New(daemon.configStore.Root, rootUID, rootGID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -777,7 +777,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
 	if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) {
 	if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) {
 		return nil, fmt.Errorf("local volume driver could not be registered")
 		return nil, fmt.Errorf("local volume driver could not be registered")
 	}
 	}
-	return store.New(config.Root)
+	return store.New(daemon.configStore.Root)
 }
 }
 
 
 // IsShuttingDown tells whether the daemon is shutting down or not
 // IsShuttingDown tells whether the daemon is shutting down or not

+ 1 - 1
daemon/graphdriver/plugin.go

@@ -23,7 +23,7 @@ func lookupPlugin(name, home string, opts []string) (Driver, error) {
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err)
 		return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err)
 	}
 	}
-	return newPluginDriver(name, home, opts, pl.Client)
+	return newPluginDriver(name, home, opts, pl.Client())
 }
 }
 
 
 func newPluginDriver(name, home string, opts []string, c pluginClient) (Driver, error) {
 func newPluginDriver(name, home string, opts []string, c pluginClient) (Driver, error) {

+ 3 - 3
distribution/pull.go

@@ -84,7 +84,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 	}
 	}
 
 
 	// makes sure name is not empty or `scratch`
 	// makes sure name is not empty or `scratch`
-	if err := validateRepoName(repoInfo.Name()); err != nil {
+	if err := ValidateRepoName(repoInfo.Name()); err != nil {
 		return err
 		return err
 	}
 	}
 
 
@@ -193,8 +193,8 @@ func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool
 	}
 	}
 }
 }
 
 
-// validateRepoName validates the name of a repository.
-func validateRepoName(name string) error {
+// ValidateRepoName validates the name of a repository.
+func ValidateRepoName(name string) error {
 	if name == "" {
 	if name == "" {
 		return fmt.Errorf("Repository name can't be empty")
 		return fmt.Errorf("Repository name can't be empty")
 	}
 	}

+ 52 - 0
docs/reference/commandline/plugin_disable.md

@@ -0,0 +1,52 @@
+<!--[metadata]>
++++
+title = "plugin disable"
+description = "the plugin disable command description and usage"
+keywords = ["plugin, disable"]
+[menu.main]
+parent = "smn_cli"
+advisory = "experimental"
++++
+<![end-metadata]-->
+
+# plugin disable (experimental)
+
+    Usage: docker plugin disable PLUGIN
+
+    Disable a plugin
+
+      --help             Print usage
+
+Disables a plugin. The plugin must be installed before it can be disabled,
+see [`docker plugin install`](plugin_install.md).
+
+
+The following example shows that the `no-remove` plugin is currently installed
+and active:
+
+```bash
+$ docker plugin ls
+NAME        	            TAG			ACTIVE
+tiborvass/no-remove	    latest		true
+```
+To disable the plugin, use the following command:
+
+```bash
+$ docker plugin disable tiborvass/no-remove:latest
+```
+
+After the plugin is disabled, it appears as "inactive" in the list of plugins:
+
+```bash
+$ docker plugin ls
+NAME			VERSION		ACTIVE
+tiborvass/no-remove	latest		false
+```
+
+## Related information
+
+* [plugin ls](plugin_ls.md)
+* [plugin enable](plugin_enable.md)
+* [plugin inspect](plugin_inspect.md)
+* [plugin install](plugin_install.md)
+* [plugin rm](plugin_rm.md)

+ 52 - 0
docs/reference/commandline/plugin_enable.md

@@ -0,0 +1,52 @@
+<!--[metadata]>
++++
+title = "plugin enable"
+description = "the plugin enable command description and usage"
+keywords = ["plugin, enable"]
+[menu.main]
+parent = "smn_cli"
+advisory = "experimental"
++++
+<![end-metadata]-->
+
+# plugin enable (experimental)
+
+    Usage: docker plugin enable PLUGIN
+
+    Enable a plugin
+
+      --help             Print usage
+
+Enables a plugin. The plugin must be installed before it can be enabled,
+see [`docker plugin install`](plugin_install.md).
+
+
+The following example shows that the `no-remove` plugin is currently installed,
+but disabled ("inactive"):
+
+```bash
+$ docker plugin ls
+NAME                	VERSION             ACTIVE
+tiborvass/no-remove	latest              false
+```
+To enable the plugin, use the following command:
+
+```bash
+$ docker plugin enable tiborvass/no-remove:latest
+```
+
+After the plugin is enabled, it appears as "active" in the list of plugins:
+
+```bash
+$ docker plugin ls
+NAME                	VERSION             ACTIVE
+tiborvass/no-remove	latest              true
+```
+
+## Related information
+
+* [plugin ls](plugin_ls.md)
+* [plugin disable](plugin_disable.md)
+* [plugin inspect](plugin_inspect.md)
+* [plugin install](plugin_install.md)
+* [plugin rm](plugin_rm.md)

+ 135 - 0
docs/reference/commandline/plugin_inspect.md

@@ -0,0 +1,135 @@
+<!--[metadata]>
++++
+title = "plugin inspect"
+description = "The plugin inspect command description and usage"
+keywords = ["plugin, inspect"]
+[menu.main]
+parent = "smn_cli"
+advisory = "experimental"
++++
+<![end-metadata]-->
+
+# plugin inspect (experimental)
+
+    Usage: docker plugin inspect PLUGIN
+
+    Return low-level information about a plugin
+
+      --help              Print usage
+
+
+Returns information about a plugin. By default, this command renders all results
+in a JSON array.
+
+Example output:
+
+```bash
+$ docker plugin inspect tiborvass/no-remove:latest
+```
+```JSON
+{
+    "Manifest": {
+        "ManifestVersion": "",
+        "Description": "A test plugin for Docker",
+        "Documentation": "https://docs.docker.com/engine/extend/plugins/",
+        "Entrypoint": [
+            "plugin-no-remove",
+            "/data"
+        ],
+        "Interface": {
+            "Types": [
+                "docker.volumedriver/1.0"
+            ],
+            "Socket": "plugins.sock"
+        },
+        "Network": {
+            "Type": "host"
+        },
+        "Capabilities": null,
+        "Mounts": [
+            {
+                "Name": "",
+                "Description": "",
+                "Settable": false,
+                "Source": "/data",
+                "Destination": "/data",
+                "Type": "bind",
+                "Options": [
+                    "shared",
+                    "rbind"
+                ]
+            },
+            {
+                "Name": "",
+                "Description": "",
+                "Settable": false,
+                "Source": null,
+                "Destination": "/foobar",
+                "Type": "tmpfs",
+                "Options": null
+            }
+        ],
+        "Devices": [
+            {
+                "Name": "device",
+                "Description": "a host device to mount",
+                "Settable": false,
+                "Path": null
+            }
+        ],
+        "Env": [
+            {
+                "Name": "DEBUG",
+                "Description": "If set, prints debug messages",
+                "Settable": false,
+                "Value": null
+            }
+        ],
+        "Args": [
+            {
+                "Name": "arg1",
+                "Description": "a command line argument",
+                "Settable": false,
+                "Value": null
+            }
+        ]
+    },
+    "Config": {
+        "Mounts": [
+            {
+                "Source": "/data",
+                "Destination": "/data",
+                "Type": "bind",
+                "Options": [
+                    "shared",
+                    "rbind"
+                ]
+            },
+            {
+                "Source": null,
+                "Destination": "/foobar",
+                "Type": "tmpfs",
+                "Options": null
+            }
+        ],
+        "Env": [],
+        "Args": [],
+        "Devices": null
+    },
+    "Active": true,
+    "Name": "tiborvass/no-remove",
+    "Tag": "latest",
+    "ID": "ac9d36b664921d61813254f7e9946f10e3cadbb676346539f1705fcaf039c01f"
+}
+```
+(output formatted for readability)
+
+
+
+## Related information
+
+* [plugin ls](plugin_ls.md)
+* [plugin enable](plugin_enable.md)
+* [plugin disable](plugin_disable.md)
+* [plugin install](plugin_install.md)
+* [plugin rm](plugin_rm.md)

+ 51 - 0
docs/reference/commandline/plugin_install.md

@@ -0,0 +1,51 @@
+<!--[metadata]>
++++
+title = "plugin install"
+description = "the plugin install command description and usage"
+keywords = ["plugin, install"]
+[menu.main]
+parent = "smn_cli"
+advisory = "experimental"
++++
+<![end-metadata]-->
+
+# plugin install (experimental)
+
+    Usage: docker plugin install PLUGIN
+
+    Install a plugin
+
+      --help             Print usage
+
+Installs and enables a plugin. Docker looks first for the plugin on your Docker
+host. If the plugin does not exist locally, then the plugin is pulled from
+Docker Hub.
+
+
+The following example installs `no-remove` plugin. Install consists of pulling the
+plugin from Docker Hub, prompting the user to accept the list of privileges that
+the plugin needs and enabling the plugin.
+
+```bash
+$ docker plugin install tiborvass/no-remove
+Plugin "tiborvass/no-remove:latest" requested the following privileges:
+ - Networking: host
+ - Mounting host path: /data
+Do you grant the above permissions? [y/N] y
+```
+
+After the plugin is installed, it appears in the list of plugins:
+
+```bash
+$ docker plugin ls
+NAME                	VERSION             ACTIVE
+tiborvass/no-remove   latest              true
+```
+
+## Related information
+
+* [plugin ls](plugin_ls.md)
+* [plugin enable](plugin_enable.md)
+* [plugin disable](plugin_disable.md)
+* [plugin inspect](plugin_inspect.md)
+* [plugin rm](plugin_rm.md)

+ 40 - 0
docs/reference/commandline/plugin_ls.md

@@ -0,0 +1,40 @@
+<!--[metadata]>
++++
+title = "plugin ls"
+description = "The plugin ls command description and usage"
+keywords = ["plugin, list"]
+[menu.main]
+parent = "smn_cli"
+advisory = "experimental"
++++
+<![end-metadata]-->
+
+# plugin ls (experimental)
+
+    Usage: docker plugin ls
+
+    List plugins
+
+      --help   Print usage
+
+    Aliases:
+      ls, list
+
+Lists all the plugins that are currently installed. You can install plugins
+using the [`docker plugin install`](plugin_install.md) command.
+
+Example output:
+
+```bash
+$ docker plugin ls
+NAME                	VERSION             ACTIVE
+tiborvass/no-remove	latest              true
+```
+
+## Related information
+
+* [plugin enable](plugin_enable.md)
+* [plugin disable](plugin_disable.md)
+* [plugin inspect](plugin_inspect.md)
+* [plugin install](plugin_install.md)
+* [plugin rm](plugin_rm.md)

+ 41 - 0
docs/reference/commandline/plugin_rm.md

@@ -0,0 +1,41 @@
+<!--[metadata]>
++++
+title = "plugin rm"
+description = "the plugin rm command description and usage"
+keywords = ["plugin, rm"]
+[menu.main]
+parent = "smn_cli"
+advisory = "experimental"
++++
+<![end-metadata]-->
+
+# plugin rm (experimental)
+
+    Usage: docker plugin rm PLUGIN
+
+    Remove a plugin
+
+      --help             Print usage
+
+    Aliases:
+      rm, remove
+
+Removes a plugin. You cannot remove a plugin if it is active, you must disable
+a plugin using the [`docker plugin disable`](plugin_disable.md) before removing
+it.
+
+The following example disables and removes the `no-remove:latest` plugin;
+
+```bash
+$ docker plugin disable tiborvass/no-remove:latest
+$ docker plugin rm tiborvass/no-remove:latest
+no-remove:latest
+```
+
+## Related information
+
+* [plugin ls](plugin_ls.md)
+* [plugin enable](plugin_enable.md)
+* [plugin disable](plugin_disable.md)
+* [plugin inspect](plugin_inspect.md)
+* [plugin install](plugin_install.md)

+ 1 - 1
hack/vendor.sh

@@ -60,7 +60,7 @@ clone git golang.org/x/net 2beffdc2e92c8a3027590f898fe88f69af48a3f8 https://gith
 clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git
 clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://github.com/golang/sys.git
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
 clone git github.com/docker/go-connections fa2850ff103453a9ad190da0df0af134f0314b3d
 clone git github.com/docker/go-connections fa2850ff103453a9ad190da0df0af134f0314b3d
-clone git github.com/docker/engine-api 6b2f24f16a7f1598635b6a99dbe38ec8a5eccaf8
+clone git github.com/docker/engine-api f3b5ad20d4576de14c96603db522dec530d03f62
 clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
 clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
 clone git github.com/imdario/mergo 0.2.1
 clone git github.com/imdario/mergo 0.2.1
 
 

+ 1 - 1
integration-cli/docker_cli_external_graphdriver_unix_test.go

@@ -77,7 +77,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPluginViaJSONFile(c *check.C) {
 	mux := http.NewServeMux()
 	mux := http.NewServeMux()
 	s.jserver = httptest.NewServer(mux)
 	s.jserver = httptest.NewServer(mux)
 
 
-	p := plugins.Plugin{Name: "json-external-graph-driver", Addr: s.jserver.URL}
+	p := plugins.NewLocalPlugin("json-external-graph-driver", s.jserver.URL)
 	b, err := json.Marshal(p)
 	b, err := json.Marshal(p)
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
 
 

+ 2 - 3
pkg/authorization/authz_unix_test.go

@@ -203,18 +203,17 @@ func TestResponseModifierOverride(t *testing.T) {
 
 
 // createTestPlugin creates a new sample authorization plugin
 // createTestPlugin creates a new sample authorization plugin
 func createTestPlugin(t *testing.T) *authorizationPlugin {
 func createTestPlugin(t *testing.T) *authorizationPlugin {
-	plugin := &plugins.Plugin{Name: "authz"}
 	pwd, err := os.Getwd()
 	pwd, err := os.Getwd()
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	plugin.Client, err = plugins.NewClient("unix:///"+path.Join(pwd, pluginAddress), tlsconfig.Options{InsecureSkipVerify: true})
+	client, err := plugins.NewClient("unix:///"+path.Join(pwd, pluginAddress), &tlsconfig.Options{InsecureSkipVerify: true})
 	if err != nil {
 	if err != nil {
 		t.Fatalf("Failed to create client %v", err)
 		t.Fatalf("Failed to create client %v", err)
 	}
 	}
 
 
-	return &authorizationPlugin{name: "plugin", plugin: plugin}
+	return &authorizationPlugin{name: "plugin", plugin: client}
 }
 }
 
 
 // AuthZPluginTestServer is a simple server that implements the authZ plugin interface
 // AuthZPluginTestServer is a simple server that implements the authZ plugin interface

+ 9 - 4
pkg/authorization/plugin.go

@@ -35,7 +35,7 @@ func NewPlugins(names []string) []Plugin {
 
 
 // authorizationPlugin is an internal adapter to docker plugin system
 // authorizationPlugin is an internal adapter to docker plugin system
 type authorizationPlugin struct {
 type authorizationPlugin struct {
-	plugin *plugins.Plugin
+	plugin *plugins.Client
 	name   string
 	name   string
 	once   sync.Once
 	once   sync.Once
 }
 }
@@ -54,7 +54,7 @@ func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error)
 	}
 	}
 
 
 	authRes := &Response{}
 	authRes := &Response{}
-	if err := a.plugin.Client.Call(AuthZApiRequest, authReq, authRes); err != nil {
+	if err := a.plugin.Call(AuthZApiRequest, authReq, authRes); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
@@ -67,7 +67,7 @@ func (a *authorizationPlugin) AuthZResponse(authReq *Request) (*Response, error)
 	}
 	}
 
 
 	authRes := &Response{}
 	authRes := &Response{}
-	if err := a.plugin.Client.Call(AuthZApiResponse, authReq, authRes); err != nil {
+	if err := a.plugin.Call(AuthZApiResponse, authReq, authRes); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
@@ -80,7 +80,12 @@ func (a *authorizationPlugin) initPlugin() error {
 	var err error
 	var err error
 	a.once.Do(func() {
 	a.once.Do(func() {
 		if a.plugin == nil {
 		if a.plugin == nil {
-			a.plugin, err = plugins.Get(a.name, AuthZApiImplements)
+			plugin, e := plugins.Get(a.name, AuthZApiImplements)
+			if e != nil {
+				err = e
+				return
+			}
+			a.plugin = plugin.Client()
 		}
 		}
 	})
 	})
 	return err
 	return err

+ 7 - 5
pkg/plugins/client.go

@@ -20,14 +20,16 @@ const (
 )
 )
 
 
 // NewClient creates a new plugin client (http).
 // NewClient creates a new plugin client (http).
-func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
+func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
 	tr := &http.Transport{}
 	tr := &http.Transport{}
 
 
-	c, err := tlsconfig.Client(tlsConfig)
-	if err != nil {
-		return nil, err
+	if tlsConfig != nil {
+		c, err := tlsconfig.Client(*tlsConfig)
+		if err != nil {
+			return nil, err
+		}
+		tr.TLSClientConfig = c
 	}
 	}
-	tr.TLSClientConfig = c
 
 
 	u, err := url.Parse(addr)
 	u, err := url.Parse(addr)
 	if err != nil {
 	if err != nil {

+ 2 - 2
pkg/plugins/client_test.go

@@ -31,7 +31,7 @@ func teardownRemotePluginServer() {
 }
 }
 
 
 func TestFailedConnection(t *testing.T) {
 func TestFailedConnection(t *testing.T) {
-	c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true})
+	c, _ := NewClient("tcp://127.0.0.1:1", &tlsconfig.Options{InsecureSkipVerify: true})
 	_, err := c.callWithRetry("Service.Method", nil, false)
 	_, err := c.callWithRetry("Service.Method", nil, false)
 	if err == nil {
 	if err == nil {
 		t.Fatal("Unexpected successful connection")
 		t.Fatal("Unexpected successful connection")
@@ -55,7 +55,7 @@ func TestEchoInputOutput(t *testing.T) {
 		io.Copy(w, r.Body)
 		io.Copy(w, r.Body)
 	})
 	})
 
 
-	c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
+	c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true})
 	var output Manifest
 	var output Manifest
 	err := c.Call("Test.Echo", m, &output)
 	err := c.Call("Test.Echo", m, &output)
 	if err != nil {
 	if err != nil {

+ 3 - 3
pkg/plugins/discovery.go

@@ -64,7 +64,7 @@ func (l *localRegistry) Plugin(name string) (*Plugin, error) {
 
 
 	for _, p := range socketpaths {
 	for _, p := range socketpaths {
 		if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
 		if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
-			return newLocalPlugin(name, "unix://"+p), nil
+			return NewLocalPlugin(name, "unix://"+p), nil
 		}
 		}
 	}
 	}
 
 
@@ -101,7 +101,7 @@ func readPluginInfo(name, path string) (*Plugin, error) {
 		return nil, fmt.Errorf("Unknown protocol")
 		return nil, fmt.Errorf("Unknown protocol")
 	}
 	}
 
 
-	return newLocalPlugin(name, addr), nil
+	return NewLocalPlugin(name, addr), nil
 }
 }
 
 
 func readPluginJSONInfo(name, path string) (*Plugin, error) {
 func readPluginJSONInfo(name, path string) (*Plugin, error) {
@@ -115,7 +115,7 @@ func readPluginJSONInfo(name, path string) (*Plugin, error) {
 	if err := json.NewDecoder(f).Decode(&p); err != nil {
 	if err := json.NewDecoder(f).Decode(&p); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	p.Name = name
+	p.name = name
 	if len(p.TLSConfig.CAFile) == 0 {
 	if len(p.TLSConfig.CAFile) == 0 {
 		p.TLSConfig.InsecureSkipVerify = true
 		p.TLSConfig.InsecureSkipVerify = true
 	}
 	}

+ 2 - 2
pkg/plugins/discovery_test.go

@@ -58,7 +58,7 @@ func TestFileSpecPlugin(t *testing.T) {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
 
 
-		if p.Name != c.name {
+		if p.name != c.name {
 			t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
 			t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
 		}
 		}
 
 
@@ -97,7 +97,7 @@ func TestFileJSONSpecPlugin(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	if plugin.Name != "example" {
+	if plugin.name != "example" {
 		t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
 		t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
 	}
 	}
 
 

+ 1 - 1
pkg/plugins/discovery_unix_test.go

@@ -45,7 +45,7 @@ func TestLocalSocket(t *testing.T) {
 			t.Fatalf("Expected %v, was %v\n", p, pp)
 			t.Fatalf("Expected %v, was %v\n", p, pp)
 		}
 		}
 
 
-		if p.Name != "echo" {
+		if p.name != "echo" {
 			t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
 			t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
 		}
 		}
 
 

+ 22 - 10
pkg/plugins/plugins.go

@@ -55,13 +55,13 @@ type Manifest struct {
 // Plugin is the definition of a docker plugin.
 // Plugin is the definition of a docker plugin.
 type Plugin struct {
 type Plugin struct {
 	// Name of the plugin
 	// Name of the plugin
-	Name string `json:"-"`
+	name string
 	// Address of the plugin
 	// Address of the plugin
 	Addr string
 	Addr string
 	// TLS configuration of the plugin
 	// TLS configuration of the plugin
-	TLSConfig tlsconfig.Options
+	TLSConfig *tlsconfig.Options
 	// Client attached to the plugin
 	// Client attached to the plugin
-	Client *Client `json:"-"`
+	client *Client
 	// Manifest of the plugin (see above)
 	// Manifest of the plugin (see above)
 	Manifest *Manifest `json:"-"`
 	Manifest *Manifest `json:"-"`
 
 
@@ -73,11 +73,23 @@ type Plugin struct {
 	activateWait *sync.Cond
 	activateWait *sync.Cond
 }
 }
 
 
-func newLocalPlugin(name, addr string) *Plugin {
+// Name returns the name of the plugin.
+func (p *Plugin) Name() string {
+	return p.name
+}
+
+// Client returns a ready-to-use plugin client that can be used to communicate with the plugin.
+func (p *Plugin) Client() *Client {
+	return p.client
+}
+
+// NewLocalPlugin creates a new local plugin.
+func NewLocalPlugin(name, addr string) *Plugin {
 	return &Plugin{
 	return &Plugin{
-		Name:         name,
-		Addr:         addr,
-		TLSConfig:    tlsconfig.Options{InsecureSkipVerify: true},
+		name: name,
+		Addr: addr,
+		// TODO: change to nil
+		TLSConfig:    &tlsconfig.Options{InsecureSkipVerify: true},
 		activateWait: sync.NewCond(&sync.Mutex{}),
 		activateWait: sync.NewCond(&sync.Mutex{}),
 	}
 	}
 }
 }
@@ -102,10 +114,10 @@ func (p *Plugin) activateWithLock() error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	p.Client = c
+	p.client = c
 
 
 	m := new(Manifest)
 	m := new(Manifest)
-	if err = p.Client.Call("Plugin.Activate", nil, m); err != nil {
+	if err = p.client.Call("Plugin.Activate", nil, m); err != nil {
 		return err
 		return err
 	}
 	}
 
 
@@ -116,7 +128,7 @@ func (p *Plugin) activateWithLock() error {
 		if !handled {
 		if !handled {
 			continue
 			continue
 		}
 		}
-		handler(p.Name, p.Client)
+		handler(p.name, p.client)
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 139 - 0
plugin/backend.go

@@ -0,0 +1,139 @@
+// +build experimental
+
+package plugin
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"path/filepath"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/plugin/distribution"
+	"github.com/docker/docker/reference"
+	"github.com/docker/engine-api/types"
+)
+
+// Disable deactivates a plugin, which implies that they cannot be used by containers.
+func (pm *Manager) Disable(name string) error {
+	p, err := pm.get(name)
+	if err != nil {
+		return err
+	}
+	return pm.disable(p)
+}
+
+// Enable activates a plugin, which implies that they are ready to be used by containers.
+func (pm *Manager) Enable(name string) error {
+	p, err := pm.get(name)
+	if err != nil {
+		return err
+	}
+	return pm.enable(p)
+}
+
+// Inspect examines a plugin manifest
+func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
+	p, err := pm.get(name)
+	if err != nil {
+		return tp, err
+	}
+	return p.p, nil
+}
+
+// Pull pulls a plugin and enables it.
+func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
+	ref, err := reference.ParseNamed(name)
+	if err != nil {
+		logrus.Debugf("error in reference.ParseNamed: %v", err)
+		return nil, err
+	}
+	name = ref.String()
+
+	if p, _ := pm.get(name); p != nil {
+		logrus.Debugf("plugin already exists")
+		return nil, fmt.Errorf("%s exists", name)
+	}
+
+	pluginID := stringid.GenerateNonCryptoID()
+
+	if err := os.MkdirAll(filepath.Join(pm.libRoot, pluginID), 0755); err != nil {
+		logrus.Debugf("error in MkdirAll: %v", err)
+		return nil, err
+	}
+
+	pd, err := distribution.Pull(name, pm.registryService, metaHeader, authConfig)
+	if err != nil {
+		logrus.Debugf("error in distribution.Pull(): %v", err)
+		return nil, err
+	}
+
+	if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil {
+		logrus.Debugf("error in distribution.WritePullData(): %v", err)
+		return nil, err
+	}
+
+	p := pm.newPlugin(ref, pluginID)
+	if ref, ok := ref.(reference.NamedTagged); ok {
+		p.p.Tag = ref.Tag()
+	}
+
+	if err := pm.initPlugin(p); err != nil {
+		return nil, err
+	}
+
+	pm.Lock()
+	pm.plugins[pluginID] = p
+	pm.nameToID[name] = pluginID
+	pm.save()
+	pm.Unlock()
+
+	return computePrivileges(&p.p.Manifest), nil
+}
+
+// List displays the list of plugins and associated metadata.
+func (pm *Manager) List() ([]types.Plugin, error) {
+	out := make([]types.Plugin, 0, len(pm.plugins))
+	for _, p := range pm.plugins {
+		out = append(out, p.p)
+	}
+	return out, nil
+}
+
+// Push pushes a plugin to the store.
+func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.AuthConfig) error {
+	p, err := pm.get(name)
+	dest := filepath.Join(pm.libRoot, p.p.ID)
+	config, err := os.Open(filepath.Join(dest, "manifest.json"))
+	if err != nil {
+		return err
+	}
+	rootfs, err := archive.Tar(filepath.Join(dest, "rootfs"), archive.Gzip)
+	if err != nil {
+		return err
+	}
+	_, err = distribution.Push(name, pm.registryService, metaHeader, authConfig, config, rootfs)
+	// XXX: Ignore returning digest for now.
+	// Since digest needs to be written to the ProgressWriter.
+	return nil
+}
+
+// Remove deletes plugin's root directory.
+func (pm *Manager) Remove(name string) error {
+	p, err := pm.get(name)
+	if err != nil {
+		return err
+	}
+	return pm.remove(p)
+}
+
+// Set sets plugin args
+func (pm *Manager) Set(name string, args []string) error {
+	p, err := pm.get(name)
+	if err != nil {
+		return err
+	}
+	return pm.set(p, args)
+}

+ 208 - 0
plugin/distribution/pull.go

@@ -0,0 +1,208 @@
+// +build experimental
+
+package distribution
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution"
+	"github.com/docker/distribution/manifest/schema2"
+	dockerdist "github.com/docker/docker/distribution"
+	archive "github.com/docker/docker/pkg/chrootarchive"
+	"github.com/docker/docker/reference"
+	"github.com/docker/docker/registry"
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// PullData is the plugin manifest and the rootfs
+type PullData interface {
+	Config() ([]byte, error)
+	Layer() (io.ReadCloser, error)
+}
+
+type pullData struct {
+	repository distribution.Repository
+	manifest   schema2.Manifest
+	index      int
+}
+
+func (pd *pullData) Config() ([]byte, error) {
+	blobs := pd.repository.Blobs(context.Background())
+	config, err := blobs.Get(context.Background(), pd.manifest.Config.Digest)
+	if err != nil {
+		return nil, err
+	}
+	// validate
+	var p types.Plugin
+	if err := json.Unmarshal(config, &p); err != nil {
+		return nil, err
+	}
+	return config, nil
+}
+
+func (pd *pullData) Layer() (io.ReadCloser, error) {
+	if pd.index >= len(pd.manifest.Layers) {
+		return nil, io.EOF
+	}
+
+	blobs := pd.repository.Blobs(context.Background())
+	rsc, err := blobs.Open(context.Background(), pd.manifest.Layers[pd.index].Digest)
+	if err != nil {
+		return nil, err
+	}
+	pd.index++
+	return rsc, nil
+}
+
+// Pull downloads the plugin from Store
+func Pull(name string, rs registry.Service, metaheader http.Header, authConfig *types.AuthConfig) (PullData, error) {
+	ref, err := reference.ParseNamed(name)
+	if err != nil {
+		logrus.Debugf("pull.go: error in ParseNamed: %v", err)
+		return nil, err
+	}
+
+	repoInfo, err := rs.ResolveRepository(ref)
+	if err != nil {
+		logrus.Debugf("pull.go: error in ResolveRepository: %v", err)
+		return nil, err
+	}
+
+	if err := dockerdist.ValidateRepoName(repoInfo.Name()); err != nil {
+		logrus.Debugf("pull.go: error in ValidateRepoName: %v", err)
+		return nil, err
+	}
+
+	endpoints, err := rs.LookupPullEndpoints(repoInfo.Hostname())
+	if err != nil {
+		logrus.Debugf("pull.go: error in LookupPullEndpoints: %v", err)
+		return nil, err
+	}
+
+	var confirmedV2 bool
+	var repository distribution.Repository
+
+	for _, endpoint := range endpoints {
+		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
+			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
+			continue
+		}
+
+		// TODO: reuse contexts
+		repository, confirmedV2, err = dockerdist.NewV2Repository(context.Background(), repoInfo, endpoint, metaheader, authConfig, "pull")
+		if err != nil {
+			logrus.Debugf("pull.go: error in NewV2Repository: %v", err)
+			return nil, err
+		}
+		if !confirmedV2 {
+			logrus.Debugf("pull.go: !confirmedV2")
+			return nil, ErrUnSupportedRegistry
+		}
+		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
+		break
+	}
+
+	tag := DefaultTag
+	if ref, ok := ref.(reference.NamedTagged); ok {
+		tag = ref.Tag()
+	}
+
+	// tags := repository.Tags(context.Background())
+	// desc, err := tags.Get(context.Background(), tag)
+	// 	if err != nil {
+	// 		return nil, err
+	// 	}
+	//
+	msv, err := repository.Manifests(context.Background())
+	if err != nil {
+		logrus.Debugf("pull.go: error in repository.Manifests: %v", err)
+		return nil, err
+	}
+	manifest, err := msv.Get(context.Background(), "", distribution.WithTag(tag))
+	if err != nil {
+		// TODO: change 401 to 404
+		logrus.Debugf("pull.go: error in msv.Get(): %v", err)
+		return nil, err
+	}
+
+	_, pl, err := manifest.Payload()
+	if err != nil {
+		logrus.Debugf("pull.go: error in manifest.Payload(): %v", err)
+		return nil, err
+	}
+	var m schema2.Manifest
+	if err := json.Unmarshal(pl, &m); err != nil {
+		logrus.Debugf("pull.go: error in json.Unmarshal(): %v", err)
+		return nil, err
+	}
+
+	pd := &pullData{
+		repository: repository,
+		manifest:   m,
+	}
+
+	logrus.Debugf("manifest: %s", pl)
+	return pd, nil
+}
+
+// WritePullData extracts manifest and rootfs to the disk.
+func WritePullData(pd PullData, dest string, extract bool) error {
+	config, err := pd.Config()
+	if err != nil {
+		return err
+	}
+	var p types.Plugin
+	if err := json.Unmarshal(config, &p); err != nil {
+		return err
+	}
+	logrus.Debugf("%#v", p)
+
+	if err := os.MkdirAll(dest, 0700); err != nil {
+		return err
+	}
+
+	if extract {
+		if err := ioutil.WriteFile(filepath.Join(dest, "manifest.json"), config, 0600); err != nil {
+			return err
+		}
+
+		if err := os.MkdirAll(filepath.Join(dest, "rootfs"), 0700); err != nil {
+			return err
+		}
+	}
+
+	for i := 0; ; i++ {
+		l, err := pd.Layer()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return err
+		}
+
+		if !extract {
+			f, err := os.Create(filepath.Join(dest, fmt.Sprintf("layer%d.tar", i)))
+			if err != nil {
+				return err
+			}
+			io.Copy(f, l)
+			l.Close()
+			f.Close()
+			continue
+		}
+
+		if _, err := archive.ApplyLayer(filepath.Join(dest, "rootfs"), l); err != nil {
+			return err
+		}
+
+	}
+	return nil
+}

+ 134 - 0
plugin/distribution/push.go

@@ -0,0 +1,134 @@
+// +build experimental
+
+package distribution
+
+import (
+	"crypto/sha256"
+	"io"
+	"net/http"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution"
+	"github.com/docker/distribution/digest"
+	"github.com/docker/distribution/manifest/schema2"
+	dockerdist "github.com/docker/docker/distribution"
+	"github.com/docker/docker/reference"
+	"github.com/docker/docker/registry"
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// Push pushes a plugin to a registry.
+func Push(name string, rs registry.Service, metaHeader http.Header, authConfig *types.AuthConfig, config io.ReadCloser, layers io.ReadCloser) (digest.Digest, error) {
+	ref, err := reference.ParseNamed(name)
+	if err != nil {
+		return "", err
+	}
+
+	repoInfo, err := rs.ResolveRepository(ref)
+	if err != nil {
+		return "", err
+	}
+
+	if err := dockerdist.ValidateRepoName(repoInfo.Name()); err != nil {
+		return "", err
+	}
+
+	endpoints, err := rs.LookupPushEndpoints(repoInfo.Hostname())
+	if err != nil {
+		return "", err
+	}
+
+	var confirmedV2 bool
+	var repository distribution.Repository
+	for _, endpoint := range endpoints {
+		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
+			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
+			continue
+		}
+		repository, confirmedV2, err = dockerdist.NewV2Repository(context.Background(), repoInfo, endpoint, metaHeader, authConfig, "push", "pull")
+		if err != nil {
+			return "", err
+		}
+		if !confirmedV2 {
+			return "", ErrUnSupportedRegistry
+		}
+		logrus.Debugf("Trying to push %s to %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
+		// This means that we found an endpoint. and we are ready to push
+		break
+	}
+
+	// Returns a reference to the repository's blob service.
+	blobs := repository.Blobs(context.Background())
+
+	// Descriptor = {mediaType, size, digest}
+	var descs []distribution.Descriptor
+
+	for i, f := range []io.ReadCloser{config, layers} {
+		bw, err := blobs.Create(context.Background())
+		if err != nil {
+			logrus.Debugf("Error in blobs.Create: %v", err)
+			return "", err
+		}
+		h := sha256.New()
+		r := io.TeeReader(f, h)
+		_, err = io.Copy(bw, r)
+		if err != nil {
+			logrus.Debugf("Error in io.Copy: %v", err)
+			return "", err
+		}
+		f.Close()
+		mt := MediaTypeLayer
+		if i == 0 {
+			mt = MediaTypeConfig
+		}
+		// Commit completes the write process to the BlobService.
+		// The descriptor arg to Commit is called the "provisional" descriptor and
+		// used for validation.
+		// The returned descriptor should be the one used. Its called the "Canonical"
+		// descriptor.
+		desc, err := bw.Commit(context.Background(), distribution.Descriptor{
+			MediaType: mt,
+			// XXX: What about the Size?
+			Digest: digest.NewDigest("sha256", h),
+		})
+		if err != nil {
+			logrus.Debugf("Error in bw.Commit: %v", err)
+			return "", err
+		}
+		// The canonical descriptor is set the mediatype again, just in case.
+		// Dont touch the digest or the size here.
+		desc.MediaType = mt
+		logrus.Debugf("pushed blob: %s %s", desc.MediaType, desc.Digest)
+		descs = append(descs, desc)
+	}
+
+	// XXX: schema2.Versioned needs a MediaType as well.
+	// "application/vnd.docker.distribution.manifest.v2+json"
+	m, err := schema2.FromStruct(schema2.Manifest{Versioned: schema2.SchemaVersion, Config: descs[0], Layers: descs[1:]})
+	if err != nil {
+		logrus.Debugf("error in schema2.FromStruct: %v", err)
+		return "", err
+	}
+
+	msv, err := repository.Manifests(context.Background())
+	if err != nil {
+		logrus.Debugf("error in repository.Manifests: %v", err)
+		return "", err
+	}
+
+	_, pl, err := m.Payload()
+	if err != nil {
+		logrus.Debugf("error in m.Payload: %v", err)
+		return "", err
+	}
+
+	logrus.Debugf("Pushed manifest: %s", pl)
+
+	tag := DefaultTag
+	if tagged, ok := ref.(reference.NamedTagged); ok {
+		tag = tagged.Tag()
+	}
+
+	return msv.Put(context.Background(), m, distribution.WithTag(tag))
+}

+ 16 - 0
plugin/distribution/types.go

@@ -0,0 +1,16 @@
+// +build experimental
+
+package distribution
+
+import "errors"
+
+// ErrUnSupportedRegistry indicates that the registry does not support v2 protocol
+var ErrUnSupportedRegistry = errors.New("Only V2 repositories are supported for plugin distribution")
+
+// Plugin related media types
+const (
+	MediaTypeManifest = "application/vnd.docker.distribution.manifest.v2+json"
+	MediaTypeConfig   = "application/vnd.docker.plugin.v0+json"
+	MediaTypeLayer    = "application/vnd.docker.image.rootfs.diff.tar.gzip"
+	DefaultTag        = "latest"
+)

+ 9 - 0
plugin/interface.go

@@ -0,0 +1,9 @@
+package plugin
+
+import "github.com/docker/docker/pkg/plugins"
+
+// Plugin represents a plugin. It is used to abstract from an older plugin architecture (in pkg/plugins).
+type Plugin interface {
+	Client() *plugins.Client
+	Name() string
+}

+ 23 - 0
plugin/legacy.go

@@ -0,0 +1,23 @@
+// +build !experimental
+
+package plugin
+
+import "github.com/docker/docker/pkg/plugins"
+
+// FindWithCapability returns a list of plugins matching the given capability.
+func FindWithCapability(capability string) ([]Plugin, error) {
+	pl, err := plugins.GetAll(capability)
+	if err != nil {
+		return nil, err
+	}
+	result := make([]Plugin, len(pl))
+	for i, p := range pl {
+		result[i] = p
+	}
+	return result, nil
+}
+
+// LookupWithCapability returns a plugin matching the given name and capability.
+func LookupWithCapability(name, capability string) (Plugin, error) {
+	return plugins.Get(name, capability)
+}

+ 384 - 0
plugin/manager.go

@@ -0,0 +1,384 @@
+// +build experimental
+
+package plugin
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/libcontainerd"
+	"github.com/docker/docker/pkg/ioutils"
+	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/reference"
+	"github.com/docker/docker/registry"
+	"github.com/docker/docker/restartmanager"
+	"github.com/docker/engine-api/types"
+)
+
+const (
+	defaultPluginRuntimeDestination = "/run/docker/plugins"
+	defaultPluginStateDestination   = "/state"
+)
+
+var manager *Manager
+
+// ErrNotFound indicates that a plugin was not found locally.
+type ErrNotFound string
+
+func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
+
+// ErrInadequateCapability indicates that a plugin was found but did not have the requested capability.
+type ErrInadequateCapability struct {
+	name       string
+	capability string
+}
+
+func (e ErrInadequateCapability) Error() string {
+	return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability)
+}
+
+type plugin struct {
+	//sync.RWMutex TODO
+	p                 types.Plugin
+	client            *plugins.Client
+	restartManager    restartmanager.RestartManager
+	stateSourcePath   string
+	runtimeSourcePath string
+}
+
+func (p *plugin) Client() *plugins.Client {
+	return p.client
+}
+
+func (p *plugin) Name() string {
+	return p.p.Name
+}
+
+func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin {
+	p := &plugin{
+		p: types.Plugin{
+			Name: ref.Name(),
+			ID:   id,
+		},
+		stateSourcePath:   filepath.Join(pm.libRoot, id, "state"),
+		runtimeSourcePath: filepath.Join(pm.runRoot, id),
+	}
+	if ref, ok := ref.(reference.NamedTagged); ok {
+		p.p.Tag = ref.Tag()
+	}
+	return p
+}
+
+// TODO: figure out why save() doesn't json encode *plugin object
+type pluginMap map[string]*plugin
+
+// Manager controls the plugin subsystem.
+type Manager struct {
+	sync.RWMutex
+	libRoot          string
+	runRoot          string
+	plugins          pluginMap // TODO: figure out why save() doesn't json encode *plugin object
+	nameToID         map[string]string
+	handlers         map[string]func(string, *plugins.Client)
+	containerdClient libcontainerd.Client
+	registryService  registry.Service
+	handleLegacy     bool
+}
+
+// GetManager returns the singleton plugin Manager
+func GetManager() *Manager {
+	return manager
+}
+
+// Init (was NewManager) instantiates the singleton Manager.
+// TODO: revert this to NewManager once we get rid of all the singletons.
+func Init(root, execRoot string, remote libcontainerd.Remote, rs registry.Service) (err error) {
+	if manager != nil {
+		return nil
+	}
+
+	root = filepath.Join(root, "plugins")
+	execRoot = filepath.Join(execRoot, "plugins")
+	for _, dir := range []string{root, execRoot} {
+		if err := os.MkdirAll(dir, 0700); err != nil {
+			return err
+		}
+	}
+
+	manager = &Manager{
+		libRoot:         root,
+		runRoot:         execRoot,
+		plugins:         make(map[string]*plugin),
+		nameToID:        make(map[string]string),
+		handlers:        make(map[string]func(string, *plugins.Client)),
+		registryService: rs,
+		handleLegacy:    true,
+	}
+	if err := os.MkdirAll(manager.runRoot, 0700); err != nil {
+		return err
+	}
+	if err := manager.init(); err != nil {
+		return err
+	}
+	manager.containerdClient, err = remote.Client(manager)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// Handle sets a callback for a given capability. The callback will be called for every plugin with a given capability.
+// TODO: append instead of set?
+func Handle(capability string, callback func(string, *plugins.Client)) {
+	pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
+	manager.handlers[pluginType] = callback
+	if manager.handleLegacy {
+		plugins.Handle(capability, callback)
+	}
+}
+
+func (pm *Manager) get(name string) (*plugin, error) {
+	pm.RLock()
+	id, nameOk := pm.nameToID[name]
+	p, idOk := pm.plugins[id]
+	pm.RUnlock()
+	if !nameOk || !idOk {
+		return nil, ErrNotFound(name)
+	}
+	return p, nil
+}
+
+// FindWithCapability returns a list of plugins matching the given capability.
+func FindWithCapability(capability string) ([]Plugin, error) {
+	handleLegacy := true
+	result := make([]Plugin, 0, 1)
+	if manager != nil {
+		handleLegacy = manager.handleLegacy
+		manager.RLock()
+		defer manager.RUnlock()
+	pluginLoop:
+		for _, p := range manager.plugins {
+			for _, typ := range p.p.Manifest.Interface.Types {
+				if typ.Capability != capability || typ.Prefix != "docker" {
+					continue pluginLoop
+				}
+			}
+			result = append(result, p)
+		}
+	}
+	if handleLegacy {
+		pl, err := plugins.GetAll(capability)
+		if err != nil {
+			return nil, fmt.Errorf("legacy plugin: %v", err)
+		}
+		for _, p := range pl {
+			if _, ok := manager.nameToID[p.Name()]; !ok {
+				result = append(result, p)
+			}
+		}
+	}
+	return result, nil
+}
+
+// LookupWithCapability returns a plugin matching the given name and capability.
+func LookupWithCapability(name, capability string) (Plugin, error) {
+	var (
+		p   *plugin
+		err error
+	)
+	handleLegacy := true
+	if manager != nil {
+		p, err = manager.get(name)
+		if err != nil {
+			if _, ok := err.(ErrNotFound); !ok {
+				return nil, err
+			}
+			handleLegacy = manager.handleLegacy
+		} else {
+			handleLegacy = false
+		}
+	}
+	if handleLegacy {
+		p, err := plugins.Get(name, capability)
+		if err != nil {
+			return nil, fmt.Errorf("legacy plugin: %v", err)
+		}
+		return p, nil
+	} else if err != nil {
+		return nil, err
+	}
+
+	capability = strings.ToLower(capability)
+	for _, typ := range p.p.Manifest.Interface.Types {
+		if typ.Capability == capability && typ.Prefix == "docker" {
+			return p, nil
+		}
+	}
+	return nil, ErrInadequateCapability{name, capability}
+}
+
+// StateChanged updates daemon inter...
+func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
+	logrus.Debugf("plugin statechanged %s %#v", id, e)
+
+	return nil
+}
+
+// AttachStreams attaches io streams to the plugin
+func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error {
+	iop.Stdin.Close()
+
+	logger := logrus.New()
+	logger.Hooks.Add(logHook{id})
+	// TODO: cache writer per id
+	w := logger.Writer()
+	go func() {
+		io.Copy(w, iop.Stdout)
+	}()
+	go func() {
+		// TODO: update logrus and use logger.WriterLevel
+		io.Copy(w, iop.Stderr)
+	}()
+	return nil
+}
+
+func (pm *Manager) init() error {
+	dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil
+		}
+		return err
+	}
+	// TODO: Populate pm.plugins
+	if err := json.NewDecoder(dt).Decode(&pm.nameToID); err != nil {
+		return err
+	}
+	// FIXME: validate, restore
+
+	return nil
+}
+
+func (pm *Manager) initPlugin(p *plugin) error {
+	dt, err := os.Open(filepath.Join(pm.libRoot, p.p.ID, "manifest.json"))
+	if err != nil {
+		return err
+	}
+	err = json.NewDecoder(dt).Decode(&p.p.Manifest)
+	dt.Close()
+	if err != nil {
+		return err
+	}
+
+	p.p.Config.Mounts = make([]types.PluginMount, len(p.p.Manifest.Mounts))
+	for i, mount := range p.p.Manifest.Mounts {
+		p.p.Config.Mounts[i] = mount
+	}
+	p.p.Config.Env = make([]string, 0, len(p.p.Manifest.Env))
+	for _, env := range p.p.Manifest.Env {
+		if env.Value != nil {
+			p.p.Config.Env = append(p.p.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
+		}
+	}
+	copy(p.p.Config.Args, p.p.Manifest.Args.Value)
+
+	f, err := os.Create(filepath.Join(pm.libRoot, p.p.ID, "plugin-config.json"))
+	if err != nil {
+		return err
+	}
+	err = json.NewEncoder(f).Encode(&p.p.Config)
+	f.Close()
+	return err
+}
+
+func (pm *Manager) remove(p *plugin) error {
+	if p.p.Active {
+		return fmt.Errorf("plugin %s is active", p.p.Name)
+	}
+	pm.Lock() // fixme: lock single record
+	defer pm.Unlock()
+	os.RemoveAll(p.stateSourcePath)
+	delete(pm.plugins, p.p.Name)
+	pm.save()
+	return nil
+}
+
+func (pm *Manager) set(p *plugin, args []string) error {
+	m := make(map[string]string, len(args))
+	for _, arg := range args {
+		i := strings.Index(arg, "=")
+		if i < 0 {
+			return fmt.Errorf("No equal sign '=' found in %s", arg)
+		}
+		m[arg[:i]] = arg[i+1:]
+	}
+	return errors.New("not implemented")
+}
+
+// fixme: not safe
+func (pm *Manager) save() error {
+	filePath := filepath.Join(pm.libRoot, "plugins.json")
+
+	jsonData, err := json.Marshal(pm.nameToID)
+	if err != nil {
+		logrus.Debugf("Error in json.Marshal: %v", err)
+		return err
+	}
+	ioutils.AtomicWriteFile(filePath, jsonData, 0600)
+	return nil
+}
+
+type logHook struct{ id string }
+
+func (logHook) Levels() []logrus.Level {
+	return logrus.AllLevels
+}
+
+func (l logHook) Fire(entry *logrus.Entry) error {
+	entry.Data = logrus.Fields{"plugin": l.id}
+	return nil
+}
+
+func computePrivileges(m *types.PluginManifest) types.PluginPrivileges {
+	var privileges types.PluginPrivileges
+	if m.Network.Type != "null" && m.Network.Type != "bridge" {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "network",
+			Description: "",
+			Value:       []string{m.Network.Type},
+		})
+	}
+	for _, mount := range m.Mounts {
+		if mount.Source != nil {
+			privileges = append(privileges, types.PluginPrivilege{
+				Name:        "mount",
+				Description: "",
+				Value:       []string{*mount.Source},
+			})
+		}
+	}
+	for _, device := range m.Devices {
+		if device.Path != nil {
+			privileges = append(privileges, types.PluginPrivilege{
+				Name:        "device",
+				Description: "",
+				Value:       []string{*device.Path},
+			})
+		}
+	}
+	if len(m.Capabilities) > 0 {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "capabilities",
+			Description: "",
+			Value:       m.Capabilities,
+		})
+	}
+	return privileges
+}

+ 126 - 0
plugin/manager_linux.go

@@ -0,0 +1,126 @@
+// +build linux,experimental
+
+package plugin
+
+import (
+	"os"
+	"path/filepath"
+	"syscall"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/libcontainerd"
+	"github.com/docker/docker/oci"
+	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/pkg/system"
+	"github.com/docker/docker/restartmanager"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/container"
+	"github.com/opencontainers/specs/specs-go"
+)
+
+func (pm *Manager) enable(p *plugin) error {
+	spec, err := pm.initSpec(p)
+	if err != nil {
+		return err
+	}
+
+	p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
+	if err := pm.containerdClient.Create(p.p.ID, libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.restartManager)); err != nil { // POC-only
+		return err
+	}
+
+	socket := p.p.Manifest.Interface.Socket
+	p.client, err = plugins.NewClient("unix://"+filepath.Join(p.runtimeSourcePath, socket), nil)
+	if err != nil {
+		return err
+	}
+
+	//TODO: check net.Dial
+
+	pm.Lock() // fixme: lock single record
+	p.p.Active = true
+	pm.save()
+	pm.Unlock()
+
+	for _, typ := range p.p.Manifest.Interface.Types {
+		if handler := pm.handlers[typ.String()]; handler != nil {
+			handler(p.Name(), p.Client())
+		}
+	}
+
+	return nil
+}
+
+func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
+	s := oci.DefaultSpec()
+
+	rootfs := filepath.Join(pm.libRoot, p.p.ID, "rootfs")
+	s.Root = specs.Root{
+		Path:     rootfs,
+		Readonly: false, // TODO: all plugins should be readonly? settable in manifest?
+	}
+
+	mounts := append(p.p.Config.Mounts, types.PluginMount{
+		Source:      &p.runtimeSourcePath,
+		Destination: defaultPluginRuntimeDestination,
+		Type:        "bind",
+		Options:     []string{"rbind", "rshared"},
+	}, types.PluginMount{
+		Source:      &p.stateSourcePath,
+		Destination: defaultPluginStateDestination,
+		Type:        "bind",
+		Options:     []string{"rbind", "rshared"},
+	})
+	for _, mount := range mounts {
+		m := specs.Mount{
+			Destination: mount.Destination,
+			Type:        mount.Type,
+			Options:     mount.Options,
+		}
+		// TODO: if nil, then it's required and user didn't set it
+		if mount.Source != nil {
+			m.Source = *mount.Source
+		}
+		if m.Source != "" && m.Type == "bind" {
+			fi, err := os.Lstat(filepath.Join(rootfs, string(os.PathSeparator), m.Destination)) // TODO: followsymlinks
+			if err != nil {
+				return nil, err
+			}
+			if fi.IsDir() {
+				if err := os.MkdirAll(m.Source, 0700); err != nil {
+					return nil, err
+				}
+			}
+		}
+		s.Mounts = append(s.Mounts, m)
+	}
+
+	envs := make([]string, 1, len(p.p.Config.Env)+1)
+	envs[0] = "PATH=" + system.DefaultPathEnv
+	envs = append(envs, p.p.Config.Env...)
+
+	args := append(p.p.Manifest.Entrypoint, p.p.Config.Args...)
+	s.Process = specs.Process{
+		Terminal: false,
+		Args:     args,
+		Cwd:      "/", // TODO: add in manifest?
+		Env:      envs,
+	}
+
+	return &s, nil
+}
+
+func (pm *Manager) disable(p *plugin) error {
+	if err := p.restartManager.Cancel(); err != nil {
+		logrus.Error(err)
+	}
+	if err := pm.containerdClient.Signal(p.p.ID, int(syscall.SIGKILL)); err != nil {
+		logrus.Error(err)
+	}
+	os.RemoveAll(p.runtimeSourcePath)
+	pm.Lock() // fixme: lock single record
+	defer pm.Unlock()
+	p.p.Active = false
+	pm.save()
+	return nil
+}

+ 21 - 0
plugin/manager_windows.go

@@ -0,0 +1,21 @@
+// +build windows,experimental
+
+package plugin
+
+import (
+	"fmt"
+
+	"github.com/opencontainers/specs/specs-go"
+)
+
+func (pm *Manager) enable(p *plugin) error {
+	return fmt.Errorf("Not implemented")
+}
+
+func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
+	return nil, fmt.Errorf("Not implemented")
+}
+
+func (pm *Manager) disable(p *plugin) error {
+	return fmt.Errorf("Not implemented")
+}

+ 15 - 0
vendor/src/github.com/docker/engine-api/client/errors.go

@@ -171,3 +171,18 @@ func IsErrTaskNotFound(err error) bool {
 	_, ok := err.(taskNotFoundError)
 	_, ok := err.(taskNotFoundError)
 	return ok
 	return ok
 }
 }
+
+type pluginPermissionDenied struct {
+	name string
+}
+
+func (e pluginPermissionDenied) Error() string {
+	return "Permission denied while installing plugin " + e.name
+}
+
+// IsErrPluginPermissionDenied returns true if the error is caused
+// when a user denies a plugin's permissions
+func IsErrPluginPermissionDenied(err error) bool {
+	_, ok := err.(pluginPermissionDenied)
+	return ok
+}

+ 3 - 7
vendor/src/github.com/docker/engine-api/client/interface.go

@@ -4,18 +4,17 @@ import (
 	"io"
 	"io"
 	"time"
 	"time"
 
 
-	"golang.org/x/net/context"
-
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/filters"
 	"github.com/docker/engine-api/types/network"
 	"github.com/docker/engine-api/types/network"
 	"github.com/docker/engine-api/types/registry"
 	"github.com/docker/engine-api/types/registry"
 	"github.com/docker/engine-api/types/swarm"
 	"github.com/docker/engine-api/types/swarm"
+	"golang.org/x/net/context"
 )
 )
 
 
-// APIClient is an interface that clients that talk with a docker server must implement.
-type APIClient interface {
+// CommonAPIClient is the common methods between stable and experimental versions of APIClient.
+type CommonAPIClient interface {
 	ClientVersion() string
 	ClientVersion() string
 	CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error
 	CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error
 	CheckpointDelete(ctx context.Context, container string, checkpointID string) error
 	CheckpointDelete(ctx context.Context, container string, checkpointID string) error
@@ -97,6 +96,3 @@ type APIClient interface {
 	VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error)
 	VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error)
 	VolumeRemove(ctx context.Context, volumeID string) error
 	VolumeRemove(ctx context.Context, volumeID string) error
 }
 }
-
-// Ensure that Client always implements APIClient.
-var _ APIClient = &Client{}

+ 26 - 0
vendor/src/github.com/docker/engine-api/client/interface_experimental.go

@@ -0,0 +1,26 @@
+// +build experimental
+
+package client
+
+import (
+	"io"
+
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// APIClient is an interface that clients that talk with a docker server must implement.
+type APIClient interface {
+	CommonAPIClient
+	PluginList(ctx context.Context) (types.PluginsListResponse, error)
+	PluginRemove(ctx context.Context, name string) error
+	PluginEnable(ctx context.Context, name string) error
+	PluginDisable(ctx context.Context, name string) error
+	PluginInstall(ctx context.Context, name, registryAuth string, acceptAllPermissions, noEnable bool, in io.ReadCloser, out io.Writer) error
+	PluginPush(ctx context.Context, name string, registryAuth string) error
+	PluginSet(ctx context.Context, name string, args []string) error
+	PluginInspect(ctx context.Context, name string) (*types.Plugin, error)
+}
+
+// Ensure that Client always implements APIClient.
+var _ APIClient = &Client{}

+ 11 - 0
vendor/src/github.com/docker/engine-api/client/interface_stable.go

@@ -0,0 +1,11 @@
+// +build !experimental
+
+package client
+
+// APIClient is an interface that clients that talk with a docker server must implement.
+type APIClient interface {
+	CommonAPIClient
+}
+
+// Ensure that Client always implements APIClient.
+var _ APIClient = &Client{}

+ 14 - 0
vendor/src/github.com/docker/engine-api/client/plugin_disable.go

@@ -0,0 +1,14 @@
+// +build experimental
+
+package client
+
+import (
+	"golang.org/x/net/context"
+)
+
+// PluginDisable disables a plugin
+func (cli *Client) PluginDisable(ctx context.Context, name string) error {
+	resp, err := cli.post(ctx, "/plugins/"+name+"/disable", nil, nil, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 14 - 0
vendor/src/github.com/docker/engine-api/client/plugin_enable.go

@@ -0,0 +1,14 @@
+// +build experimental
+
+package client
+
+import (
+	"golang.org/x/net/context"
+)
+
+// PluginEnable enables a plugin
+func (cli *Client) PluginEnable(ctx context.Context, name string) error {
+	resp, err := cli.post(ctx, "/plugins/"+name+"/enable", nil, nil, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 22 - 0
vendor/src/github.com/docker/engine-api/client/plugin_inspect.go

@@ -0,0 +1,22 @@
+// +build experimental
+
+package client
+
+import (
+	"encoding/json"
+
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// PluginInspect inspects an existing plugin
+func (cli *Client) PluginInspect(ctx context.Context, name string) (*types.Plugin, error) {
+	var p types.Plugin
+	resp, err := cli.get(ctx, "/plugins/"+name, nil, nil)
+	if err != nil {
+		return nil, err
+	}
+	err = json.NewDecoder(resp.body).Decode(&p)
+	ensureReaderClosed(resp)
+	return &p, err
+}

+ 54 - 0
vendor/src/github.com/docker/engine-api/client/plugin_install.go

@@ -0,0 +1,54 @@
+// +build experimental
+
+package client
+
+import (
+	"bufio"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/url"
+	"strings"
+
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// PluginInstall installs a plugin
+func (cli *Client) PluginInstall(ctx context.Context, name, registryAuth string, acceptAllPermissions, noEnable bool, in io.ReadCloser, out io.Writer) error {
+	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
+	resp, err := cli.post(ctx, "/plugins/pull", url.Values{"name": []string{name}}, nil, headers)
+	if err != nil {
+		ensureReaderClosed(resp)
+		return err
+	}
+	var privileges types.PluginPrivileges
+	if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil {
+		return err
+	}
+	ensureReaderClosed(resp)
+
+	if !acceptAllPermissions && len(privileges) > 0 {
+
+		fmt.Fprintf(out, "Plugin %q requested the following privileges:\n", name)
+		for _, privilege := range privileges {
+			fmt.Fprintf(out, " - %s: %v\n", privilege.Name, privilege.Value)
+		}
+
+		fmt.Fprint(out, "Do you grant the above permissions? [y/N] ")
+		reader := bufio.NewReader(in)
+		line, _, err := reader.ReadLine()
+		if err != nil {
+			return err
+		}
+		if strings.ToLower(string(line)) != "y" {
+			resp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil)
+			ensureReaderClosed(resp)
+			return pluginPermissionDenied{name}
+		}
+	}
+	if noEnable {
+		return nil
+	}
+	return cli.PluginEnable(ctx, name)
+}

+ 23 - 0
vendor/src/github.com/docker/engine-api/client/plugin_list.go

@@ -0,0 +1,23 @@
+// +build experimental
+
+package client
+
+import (
+	"encoding/json"
+
+	"github.com/docker/engine-api/types"
+	"golang.org/x/net/context"
+)
+
+// PluginList returns the installed plugins
+func (cli *Client) PluginList(ctx context.Context) (types.PluginsListResponse, error) {
+	var plugins types.PluginsListResponse
+	resp, err := cli.get(ctx, "/plugins", nil, nil)
+	if err != nil {
+		return plugins, err
+	}
+
+	err = json.NewDecoder(resp.body).Decode(&plugins)
+	ensureReaderClosed(resp)
+	return plugins, err
+}

+ 15 - 0
vendor/src/github.com/docker/engine-api/client/plugin_push.go

@@ -0,0 +1,15 @@
+// +build experimental
+
+package client
+
+import (
+	"golang.org/x/net/context"
+)
+
+// PluginPush pushes a plugin to a registry
+func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) error {
+	headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
+	resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers)
+	ensureReaderClosed(resp)
+	return err
+}

+ 14 - 0
vendor/src/github.com/docker/engine-api/client/plugin_remove.go

@@ -0,0 +1,14 @@
+// +build experimental
+
+package client
+
+import (
+	"golang.org/x/net/context"
+)
+
+// PluginRemove removes a plugin
+func (cli *Client) PluginRemove(ctx context.Context, name string) error {
+	resp, err := cli.delete(ctx, "/plugins/"+name, nil, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 14 - 0
vendor/src/github.com/docker/engine-api/client/plugin_set.go

@@ -0,0 +1,14 @@
+// +build experimental
+
+package client
+
+import (
+	"golang.org/x/net/context"
+)
+
+// PluginSet modifies settings for an existing plugin
+func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error {
+	resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil)
+	ensureReaderClosed(resp)
+	return err
+}

+ 160 - 0
vendor/src/github.com/docker/engine-api/types/plugin.go

@@ -0,0 +1,160 @@
+// +build experimental
+
+package types
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// PluginConfig represents the values of settings potentially modifiable by a user
+type PluginConfig struct {
+	Mounts  []PluginMount
+	Env     []string
+	Args    []string
+	Devices []PluginDevice
+}
+
+// Plugin represents a Docker plugin for the remote API
+type Plugin struct {
+	ID       string `json:"Id,omitempty"`
+	Name     string
+	Tag      string
+	Active   bool
+	Config   PluginConfig
+	Manifest PluginManifest
+}
+
+// PluginsListResponse contains the response for the remote API
+type PluginsListResponse []*Plugin
+
+const (
+	authzDriver   = "AuthzDriver"
+	graphDriver   = "GraphDriver"
+	ipamDriver    = "IpamDriver"
+	networkDriver = "NetworkDriver"
+	volumeDriver  = "VolumeDriver"
+)
+
+// PluginInterfaceType represents a type that a plugin implements.
+type PluginInterfaceType struct {
+	Prefix     string // This is always "docker"
+	Capability string // Capability should be validated against the above list.
+	Version    string // Plugin API version. Depends on the capability
+}
+
+// UnmarshalJSON implements json.Unmarshaler for PluginInterfaceType
+func (t *PluginInterfaceType) UnmarshalJSON(p []byte) error {
+	versionIndex := len(p)
+	prefixIndex := 0
+	if len(p) < 2 || p[0] != '"' || p[len(p)-1] != '"' {
+		return fmt.Errorf("%q is not a plugin interface type", p)
+	}
+	p = p[1 : len(p)-1]
+loop:
+	for i, b := range p {
+		switch b {
+		case '.':
+			prefixIndex = i
+		case '/':
+			versionIndex = i
+			break loop
+		}
+	}
+	t.Prefix = string(p[:prefixIndex])
+	t.Capability = string(p[prefixIndex+1 : versionIndex])
+	if versionIndex < len(p) {
+		t.Version = string(p[versionIndex+1:])
+	}
+	return nil
+}
+
+// MarshalJSON implements json.Marshaler for PluginInterfaceType
+func (t *PluginInterfaceType) MarshalJSON() ([]byte, error) {
+	return json.Marshal(t.String())
+}
+
+// String implements fmt.Stringer for PluginInterfaceType
+func (t PluginInterfaceType) String() string {
+	return fmt.Sprintf("%s.%s/%s", t.Prefix, t.Capability, t.Version)
+}
+
+// PluginInterface describes the interface between Docker and plugin
+type PluginInterface struct {
+	Types  []PluginInterfaceType
+	Socket string
+}
+
+// PluginSetting is to be embedded in other structs, if they are supposed to be
+// modifiable by the user.
+type PluginSetting struct {
+	Name        string
+	Description string
+	Settable    []string
+}
+
+// PluginNetwork represents the network configuration for a plugin
+type PluginNetwork struct {
+	Type string
+}
+
+// PluginMount represents the mount configuration for a plugin
+type PluginMount struct {
+	PluginSetting
+	Source      *string
+	Destination string
+	Type        string
+	Options     []string
+}
+
+// PluginEnv represents an environment variable for a plugin
+type PluginEnv struct {
+	PluginSetting
+	Value *string
+}
+
+// PluginArgs represents the command line arguments for a plugin
+type PluginArgs struct {
+	PluginSetting
+	Value []string
+}
+
+// PluginDevice represents a device for a plugin
+type PluginDevice struct {
+	PluginSetting
+	Path *string
+}
+
+// PluginUser represents the user for the plugin's process
+type PluginUser struct {
+	UID uint32 `json:"Uid,omitempty"`
+	GID uint32 `json:"Gid,omitempty"`
+}
+
+// PluginManifest represents the manifest of a plugin
+type PluginManifest struct {
+	ManifestVersion string
+	Description     string
+	Documentation   string
+	Interface       PluginInterface
+	Entrypoint      []string
+	Workdir         string
+	User            PluginUser `json:",omitempty"`
+	Network         PluginNetwork
+	Capabilities    []string
+	Mounts          []PluginMount
+	Devices         []PluginDevice
+	Env             []PluginEnv
+	Args            PluginArgs
+}
+
+// PluginPrivilege describes a permission the user has to accept
+// upon installing a plugin.
+type PluginPrivilege struct {
+	Name        string
+	Description string
+	Value       []string
+}
+
+// PluginPrivileges is a list of PluginPrivilege
+type PluginPrivileges []PluginPrivilege

+ 12 - 11
volume/drivers/extpoint.go

@@ -7,7 +7,7 @@ import (
 	"sync"
 	"sync"
 
 
 	"github.com/docker/docker/pkg/locker"
 	"github.com/docker/docker/pkg/locker"
-	"github.com/docker/docker/pkg/plugins"
+	"github.com/docker/docker/plugin"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 )
 )
 
 
@@ -88,10 +88,10 @@ func Unregister(name string) bool {
 	return true
 	return true
 }
 }
 
 
-// Lookup returns the driver associated with the given name. If a
+// lookup returns the driver associated with the given name. If a
 // driver with the given name has not been registered it checks if
 // driver with the given name has not been registered it checks if
 // there is a VolumeDriver plugin available with the given name.
 // there is a VolumeDriver plugin available with the given name.
-func Lookup(name string) (volume.Driver, error) {
+func lookup(name string) (volume.Driver, error) {
 	drivers.driverLock.Lock(name)
 	drivers.driverLock.Lock(name)
 	defer drivers.driverLock.Unlock(name)
 	defer drivers.driverLock.Unlock(name)
 
 
@@ -102,7 +102,7 @@ func Lookup(name string) (volume.Driver, error) {
 		return ext, nil
 		return ext, nil
 	}
 	}
 
 
-	pl, err := plugins.Get(name, extName)
+	p, err := plugin.LookupWithCapability(name, extName)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
 		return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
 	}
 	}
@@ -113,7 +113,7 @@ func Lookup(name string) (volume.Driver, error) {
 		return ext, nil
 		return ext, nil
 	}
 	}
 
 
-	d := NewVolumeDriver(name, pl.Client)
+	d := NewVolumeDriver(name, p.Client())
 	if err := validateDriver(d); err != nil {
 	if err := validateDriver(d); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -136,7 +136,7 @@ func GetDriver(name string) (volume.Driver, error) {
 	if name == "" {
 	if name == "" {
 		name = volume.DefaultDriverName
 		name = volume.DefaultDriverName
 	}
 	}
-	return Lookup(name)
+	return lookup(name)
 }
 }
 
 
 // GetDriverList returns list of volume drivers registered.
 // GetDriverList returns list of volume drivers registered.
@@ -153,9 +153,9 @@ func GetDriverList() []string {
 
 
 // GetAllDrivers lists all the registered drivers
 // GetAllDrivers lists all the registered drivers
 func GetAllDrivers() ([]volume.Driver, error) {
 func GetAllDrivers() ([]volume.Driver, error) {
-	plugins, err := plugins.GetAll(extName)
+	plugins, err := plugin.FindWithCapability(extName)
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("error listing plugins: %v", err)
 	}
 	}
 	var ds []volume.Driver
 	var ds []volume.Driver
 
 
@@ -167,13 +167,14 @@ func GetAllDrivers() ([]volume.Driver, error) {
 	}
 	}
 
 
 	for _, p := range plugins {
 	for _, p := range plugins {
-		ext, ok := drivers.extensions[p.Name]
+		name := p.Name()
+		ext, ok := drivers.extensions[name]
 		if ok {
 		if ok {
 			continue
 			continue
 		}
 		}
 
 
-		ext = NewVolumeDriver(p.Name, p.Client)
-		drivers.extensions[p.Name] = ext
+		ext = NewVolumeDriver(name, p.Client())
+		drivers.extensions[name] = ext
 		ds = append(ds, ext)
 		ds = append(ds, ext)
 	}
 	}
 	return ds, nil
 	return ds, nil

+ 1 - 1
volume/drivers/proxy_test.go

@@ -58,7 +58,7 @@ func TestVolumeRequestError(t *testing.T) {
 	})
 	})
 
 
 	u, _ := url.Parse(server.URL)
 	u, _ := url.Parse(server.URL)
-	client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true})
+	client, err := plugins.NewClient("tcp://"+u.Host, &tlsconfig.Options{InsecureSkipVerify: true})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 5 - 4
volume/store/store.go

@@ -157,15 +157,16 @@ func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
 
 
 // list goes through each volume driver and asks for its list of volumes.
 // list goes through each volume driver and asks for its list of volumes.
 func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
 func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
-	drivers, err := volumedrivers.GetAllDrivers()
-	if err != nil {
-		return nil, nil, err
-	}
 	var (
 	var (
 		ls       []volume.Volume
 		ls       []volume.Volume
 		warnings []string
 		warnings []string
 	)
 	)
 
 
+	drivers, err := volumedrivers.GetAllDrivers()
+	if err != nil {
+		return nil, nil, err
+	}
+
 	type vols struct {
 	type vols struct {
 		vols       []volume.Volume
 		vols       []volume.Volume
 		err        error
 		err        error