Browse code

plugins: experimental support for new plugin management

This patch introduces a new experimental engine-level plugin management
with a new API and command line. Plugins can be distributed via a Docker
registry, and their lifecycle is managed by the engine.
This makes plugins a first-class construct.

For more background, have a look at issue #20363.

Documentation is in a separate commit. If you want to understand how the
new plugin system works, you can start by reading the documentation.

Note: backwards compatibility with existing plugins is maintained,
albeit they won't benefit from the advantages of the new system.

Signed-off-by: Tibor Vass <tibor@docker.com>
Signed-off-by: Anusha Ragunathan <anusha@docker.com>

Tibor Vass authored on 2016/05/17 00:50:55
Showing 47 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+// +build !experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"github.com/docker/docker/api/client"
6
+	"github.com/spf13/cobra"
7
+)
8
+
9
+// NewPluginCommand returns a cobra command for `plugin` subcommands
10
+func NewPluginCommand(cmd *cobra.Command, dockerCli *client.DockerCli) {
11
+}
0 12
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"github.com/docker/docker/api/client"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/spf13/cobra"
10
+)
11
+
12
+// NewPluginCommand returns a cobra command for `plugin` subcommands
13
+func NewPluginCommand(rootCmd *cobra.Command, dockerCli *client.DockerCli) {
14
+	cmd := &cobra.Command{
15
+		Use:   "plugin",
16
+		Short: "Manage Docker plugins",
17
+		Args:  cli.NoArgs,
18
+		Run: func(cmd *cobra.Command, args []string) {
19
+			fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
20
+		},
21
+	}
22
+
23
+	cmd.AddCommand(
24
+		newDisableCommand(dockerCli),
25
+		newEnableCommand(dockerCli),
26
+		newInspectCommand(dockerCli),
27
+		newInstallCommand(dockerCli),
28
+		newListCommand(dockerCli),
29
+		newRemoveCommand(dockerCli),
30
+		newSetCommand(dockerCli),
31
+		newPushCommand(dockerCli),
32
+	)
33
+
34
+	rootCmd.AddCommand(cmd)
35
+}
0 36
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"github.com/docker/docker/api/client"
6
+	"github.com/docker/docker/cli"
7
+	"github.com/spf13/cobra"
8
+	"golang.org/x/net/context"
9
+)
10
+
11
+func newDisableCommand(dockerCli *client.DockerCli) *cobra.Command {
12
+	cmd := &cobra.Command{
13
+		Use:   "disable",
14
+		Short: "Disable a plugin",
15
+		Args:  cli.ExactArgs(1),
16
+		RunE: func(cmd *cobra.Command, args []string) error {
17
+			return dockerCli.Client().PluginDisable(context.Background(), args[0])
18
+		},
19
+	}
20
+
21
+	return cmd
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"github.com/docker/docker/api/client"
6
+	"github.com/docker/docker/cli"
7
+	"github.com/spf13/cobra"
8
+	"golang.org/x/net/context"
9
+)
10
+
11
+func newEnableCommand(dockerCli *client.DockerCli) *cobra.Command {
12
+	cmd := &cobra.Command{
13
+		Use:   "enable",
14
+		Short: "Enable a plugin",
15
+		Args:  cli.ExactArgs(1),
16
+		RunE: func(cmd *cobra.Command, args []string) error {
17
+			return dockerCli.Client().PluginEnable(context.Background(), args[0])
18
+		},
19
+	}
20
+
21
+	return cmd
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"encoding/json"
6
+
7
+	"github.com/docker/docker/api/client"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/spf13/cobra"
10
+	"golang.org/x/net/context"
11
+)
12
+
13
+func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
14
+	cmd := &cobra.Command{
15
+		Use:   "inspect",
16
+		Short: "Inspect a plugin",
17
+		Args:  cli.ExactArgs(1),
18
+		RunE: func(cmd *cobra.Command, args []string) error {
19
+			return runInspect(dockerCli, args[0])
20
+		},
21
+	}
22
+
23
+	return cmd
24
+}
25
+
26
+func runInspect(dockerCli *client.DockerCli, name string) error {
27
+	p, err := dockerCli.Client().PluginInspect(context.Background(), name)
28
+	if err != nil {
29
+		return err
30
+	}
31
+
32
+	b, err := json.MarshalIndent(p, "", "\t")
33
+	if err != nil {
34
+		return err
35
+	}
36
+	_, err = dockerCli.Out().Write(b)
37
+	return err
38
+}
0 39
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"github.com/docker/docker/api/client"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/docker/docker/reference"
10
+	"github.com/docker/docker/registry"
11
+	"github.com/spf13/cobra"
12
+	"golang.org/x/net/context"
13
+)
14
+
15
+func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command {
16
+	cmd := &cobra.Command{
17
+		Use:   "install",
18
+		Short: "Install a plugin",
19
+		Args:  cli.RequiresMinArgs(1), // TODO: allow for set args
20
+		RunE: func(cmd *cobra.Command, args []string) error {
21
+			return runInstall(dockerCli, args[0], args[1:])
22
+		},
23
+	}
24
+
25
+	return cmd
26
+}
27
+
28
+func runInstall(dockerCli *client.DockerCli, name string, args []string) error {
29
+	named, err := reference.ParseNamed(name) // FIXME: validate
30
+	if err != nil {
31
+		return err
32
+	}
33
+	named = reference.WithDefaultTag(named)
34
+	ref, ok := named.(reference.NamedTagged)
35
+	if !ok {
36
+		return fmt.Errorf("invalid name: %s", named.String())
37
+	}
38
+
39
+	ctx := context.Background()
40
+
41
+	repoInfo, err := registry.ParseRepositoryInfo(named)
42
+	authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
43
+
44
+	encodedAuth, err := client.EncodeAuthToBase64(authConfig)
45
+	if err != nil {
46
+		return err
47
+	}
48
+	// TODO: pass acceptAllPermissions and noEnable flag
49
+	return dockerCli.Client().PluginInstall(ctx, ref.String(), encodedAuth, false, false, dockerCli.In(), dockerCli.Out())
50
+}
0 51
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"fmt"
6
+	"text/tabwriter"
7
+
8
+	"github.com/docker/docker/api/client"
9
+	"github.com/docker/docker/cli"
10
+	"github.com/spf13/cobra"
11
+	"golang.org/x/net/context"
12
+)
13
+
14
+func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
15
+	cmd := &cobra.Command{
16
+		Use:     "ls",
17
+		Short:   "List plugins",
18
+		Aliases: []string{"list"},
19
+		Args:    cli.ExactArgs(0),
20
+		RunE: func(cmd *cobra.Command, args []string) error {
21
+			return runList(dockerCli)
22
+		},
23
+	}
24
+
25
+	return cmd
26
+}
27
+
28
+func runList(dockerCli *client.DockerCli) error {
29
+	plugins, err := dockerCli.Client().PluginList(context.Background())
30
+	if err != nil {
31
+		return err
32
+	}
33
+
34
+	w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
35
+	fmt.Fprintf(w, "NAME \tTAG \tACTIVE")
36
+	fmt.Fprintf(w, "\n")
37
+
38
+	for _, p := range plugins {
39
+		fmt.Fprintf(w, "%s\t%s\t%v\n", p.Name, p.Tag, p.Active)
40
+	}
41
+	w.Flush()
42
+	return nil
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,50 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"golang.org/x/net/context"
8
+
9
+	"github.com/docker/docker/api/client"
10
+	"github.com/docker/docker/cli"
11
+	"github.com/docker/docker/reference"
12
+	"github.com/docker/docker/registry"
13
+	"github.com/spf13/cobra"
14
+)
15
+
16
+func newPushCommand(dockerCli *client.DockerCli) *cobra.Command {
17
+	cmd := &cobra.Command{
18
+		Use:   "push",
19
+		Short: "Push a plugin",
20
+		Args:  cli.ExactArgs(1),
21
+		RunE: func(cmd *cobra.Command, args []string) error {
22
+			return runPush(dockerCli, args[0])
23
+		},
24
+	}
25
+	return cmd
26
+}
27
+
28
+func runPush(dockerCli *client.DockerCli, name string) error {
29
+	named, err := reference.ParseNamed(name) // FIXME: validate
30
+	if err != nil {
31
+		return err
32
+	}
33
+	named = reference.WithDefaultTag(named)
34
+	ref, ok := named.(reference.NamedTagged)
35
+	if !ok {
36
+		return fmt.Errorf("invalid name: %s", named.String())
37
+	}
38
+
39
+	ctx := context.Background()
40
+
41
+	repoInfo, err := registry.ParseRepositoryInfo(named)
42
+	authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
43
+
44
+	encodedAuth, err := client.EncodeAuthToBase64(authConfig)
45
+	if err != nil {
46
+		return err
47
+	}
48
+	return dockerCli.Client().PluginPush(ctx, ref.String(), encodedAuth)
49
+}
0 50
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"github.com/docker/docker/api/client"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/spf13/cobra"
10
+	"golang.org/x/net/context"
11
+)
12
+
13
+func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
14
+	cmd := &cobra.Command{
15
+		Use:     "rm",
16
+		Short:   "Remove a plugin",
17
+		Aliases: []string{"remove"},
18
+		Args:    cli.RequiresMinArgs(1),
19
+		RunE: func(cmd *cobra.Command, args []string) error {
20
+			return runRemove(dockerCli, args)
21
+		},
22
+	}
23
+
24
+	return cmd
25
+}
26
+
27
+func runRemove(dockerCli *client.DockerCli, names []string) error {
28
+	var errs cli.Errors
29
+	for _, name := range names {
30
+		// TODO: pass names to api instead of making multiple api calls
31
+		if err := dockerCli.Client().PluginRemove(context.Background(), name); err != nil {
32
+			errs = append(errs, err)
33
+			continue
34
+		}
35
+		fmt.Fprintln(dockerCli.Out(), name)
36
+	}
37
+	// Do not simplify to `return errs` because even if errs == nil, it is not a nil-error interface value.
38
+	if errs != nil {
39
+		return errs
40
+	}
41
+	return nil
42
+}
0 43
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"golang.org/x/net/context"
6
+
7
+	"github.com/docker/docker/api/client"
8
+	"github.com/docker/docker/cli"
9
+	"github.com/spf13/cobra"
10
+)
11
+
12
+func newSetCommand(dockerCli *client.DockerCli) *cobra.Command {
13
+	cmd := &cobra.Command{
14
+		Use:   "set",
15
+		Short: "Change settings for a plugin",
16
+		Args:  cli.RequiresMinArgs(2),
17
+		RunE: func(cmd *cobra.Command, args []string) error {
18
+			return runSet(dockerCli, args[0], args[1:])
19
+		},
20
+	}
21
+
22
+	return cmd
23
+}
24
+
25
+func runSet(dockerCli *client.DockerCli, name string, args []string) error {
26
+	return dockerCli.Client().PluginSet(context.Background(), name, args)
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"net/http"
6
+
7
+	enginetypes "github.com/docker/engine-api/types"
8
+)
9
+
10
+// Backend for Plugin
11
+type Backend interface {
12
+	Disable(name string) error
13
+	Enable(name string) error
14
+	List() ([]enginetypes.Plugin, error)
15
+	Inspect(name string) (enginetypes.Plugin, error)
16
+	Remove(name string) error
17
+	Set(name string, args []string) error
18
+	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
19
+	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package plugin
1
+
2
+import "github.com/docker/docker/api/server/router"
3
+
4
+// pluginRouter is a router to talk with the plugin controller
5
+type pluginRouter struct {
6
+	backend Backend
7
+	routes  []router.Route
8
+}
9
+
10
+// NewRouter initializes a new plugin router
11
+func NewRouter(b Backend) router.Router {
12
+	r := &pluginRouter{
13
+		backend: b,
14
+	}
15
+	r.initRoutes()
16
+	return r
17
+}
18
+
19
+// Routes returns the available routers to the plugin controller
20
+func (r *pluginRouter) Routes() []router.Route {
21
+	return r.routes
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"github.com/docker/docker/api/server/router"
6
+)
7
+
8
+func (r *pluginRouter) initRoutes() {
9
+	r.routes = []router.Route{
10
+		router.NewGetRoute("/plugins", r.listPlugins),
11
+		router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin),
12
+		router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
13
+		router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
14
+		router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),
15
+		router.NewPostRoute("/plugins/pull", r.pullPlugin),
16
+		router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin),
17
+		router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin),
18
+	}
19
+}
0 20
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+// +build !experimental
1
+
2
+package plugin
3
+
4
+func (r *pluginRouter) initRoutes() {}
5
+
6
+// Backend is empty so that the package can compile in non-experimental
7
+// (Needed by volume driver)
8
+type Backend interface{}
0 9
new file mode 100644
... ...
@@ -0,0 +1,103 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"encoding/base64"
6
+	"encoding/json"
7
+	"net/http"
8
+	"strings"
9
+
10
+	"github.com/docker/docker/api/server/httputils"
11
+	"github.com/docker/engine-api/types"
12
+	"golang.org/x/net/context"
13
+)
14
+
15
+func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
16
+	if err := httputils.ParseForm(r); err != nil {
17
+		return err
18
+	}
19
+
20
+	metaHeaders := map[string][]string{}
21
+	for k, v := range r.Header {
22
+		if strings.HasPrefix(k, "X-Meta-") {
23
+			metaHeaders[k] = v
24
+		}
25
+	}
26
+
27
+	// Get X-Registry-Auth
28
+	authEncoded := r.Header.Get("X-Registry-Auth")
29
+	authConfig := &types.AuthConfig{}
30
+	if authEncoded != "" {
31
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
32
+		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
33
+			authConfig = &types.AuthConfig{}
34
+		}
35
+	}
36
+
37
+	privileges, err := pr.backend.Pull(r.FormValue("name"), metaHeaders, authConfig)
38
+	if err != nil {
39
+		return err
40
+	}
41
+	return httputils.WriteJSON(w, http.StatusOK, privileges)
42
+}
43
+
44
+func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
45
+	return pr.backend.Enable(vars["name"])
46
+}
47
+
48
+func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
49
+	return pr.backend.Disable(vars["name"])
50
+}
51
+
52
+func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
53
+	return pr.backend.Remove(vars["name"])
54
+}
55
+
56
+func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
57
+	if err := httputils.ParseForm(r); err != nil {
58
+		return err
59
+	}
60
+
61
+	metaHeaders := map[string][]string{}
62
+	for k, v := range r.Header {
63
+		if strings.HasPrefix(k, "X-Meta-") {
64
+			metaHeaders[k] = v
65
+		}
66
+	}
67
+
68
+	// Get X-Registry-Auth
69
+	authEncoded := r.Header.Get("X-Registry-Auth")
70
+	authConfig := &types.AuthConfig{}
71
+	if authEncoded != "" {
72
+		authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
73
+		if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
74
+			authConfig = &types.AuthConfig{}
75
+		}
76
+	}
77
+	return pr.backend.Push(vars["name"], metaHeaders, authConfig)
78
+}
79
+
80
+func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
81
+	var args []string
82
+	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
83
+		return err
84
+	}
85
+	return pr.backend.Set(vars["name"], args)
86
+}
87
+
88
+func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
89
+	l, err := pr.backend.List()
90
+	if err != nil {
91
+		return err
92
+	}
93
+	return httputils.WriteJSON(w, http.StatusOK, l)
94
+}
95
+
96
+func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
97
+	result, err := pr.backend.Inspect(vars["name"])
98
+	if err != nil {
99
+		return err
100
+	}
101
+	return httputils.WriteJSON(w, http.StatusOK, result)
102
+}
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"github.com/docker/docker/api/client/image"
7 7
 	"github.com/docker/docker/api/client/network"
8 8
 	"github.com/docker/docker/api/client/node"
9
+	"github.com/docker/docker/api/client/plugin"
9 10
 	"github.com/docker/docker/api/client/registry"
10 11
 	"github.com/docker/docker/api/client/service"
11 12
 	"github.com/docker/docker/api/client/swarm"
... ...
@@ -81,6 +82,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
81 81
 		system.NewVersionCommand(dockerCli),
82 82
 		volume.NewVolumeCommand(dockerCli),
83 83
 	)
84
+	plugin.NewPluginCommand(rootCmd, dockerCli)
84 85
 
85 86
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
86 87
 	rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
87 88
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package cli
1
+
2
+import "bytes"
3
+
4
+// Errors is a list of errors.
5
+// Useful in a loop if you don't want to return the error right away and you want to display after the loop,
6
+// all the errors that happened during the loop.
7
+type Errors []error
8
+
9
+func (errs Errors) Error() string {
10
+	if len(errs) < 1 {
11
+		return ""
12
+	}
13
+	var buf bytes.Buffer
14
+	buf.WriteString(errs[0].Error())
15
+	for _, err := range errs[1:] {
16
+		buf.WriteString(", ")
17
+		buf.WriteString(err.Error())
18
+	}
19
+	return buf.String()
20
+}
... ...
@@ -262,6 +262,10 @@ func (cli *DaemonCli) start() (err error) {
262 262
 		<-stopc // wait for daemonCli.start() to return
263 263
 	})
264 264
 
265
+	if err := pluginInit(cli.Config, containerdRemote, registryService); err != nil {
266
+		return err
267
+	}
268
+
265 269
 	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
266 270
 	if err != nil {
267 271
 		return fmt.Errorf("Error starting daemon: %v", err)
... ...
@@ -418,6 +422,7 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
418 418
 	if d.NetworkControllerEnabled() {
419 419
 		routers = append(routers, network.NewRouter(d, c))
420 420
 	}
421
+	routers = addExperimentalRouters(routers)
421 422
 
422 423
 	s.InitRouter(utils.IsDebugEnabled(), routers...)
423 424
 }
... ...
@@ -1,8 +1,8 @@
1
+// +build linux
2
+
1 3
 package main
2 4
 
3
-import (
4
-	systemdDaemon "github.com/coreos/go-systemd/daemon"
5
-)
5
+import systemdDaemon "github.com/coreos/go-systemd/daemon"
6 6
 
7 7
 // notifySystem sends a message to the host when the server is ready to be used
8 8
 func notifySystem() {
9 9
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+// +build !experimental !linux
1
+
2
+package main
3
+
4
+import (
5
+	"github.com/docker/docker/daemon"
6
+	"github.com/docker/docker/libcontainerd"
7
+	"github.com/docker/docker/registry"
8
+)
9
+
10
+func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
11
+	return nil
12
+}
0 13
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+// +build linux,experimental
1
+
2
+package main
3
+
4
+import (
5
+	"github.com/docker/docker/daemon"
6
+	"github.com/docker/docker/libcontainerd"
7
+	"github.com/docker/docker/plugin"
8
+	"github.com/docker/docker/registry"
9
+)
10
+
11
+func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
12
+	return plugin.Init(config.Root, config.ExecRoot, remote, rs)
13
+}
0 14
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+// +build !experimental
1
+
2
+package main
3
+
4
+import "github.com/docker/docker/api/server/router"
5
+
6
+func addExperimentalRouters(routers []router.Router) []router.Router {
7
+	return routers
8
+}
0 9
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+// +build experimental
1
+
2
+package main
3
+
4
+import (
5
+	"github.com/docker/docker/api/server/router"
6
+	pluginrouter "github.com/docker/docker/api/server/router/plugin"
7
+	"github.com/docker/docker/plugin"
8
+)
9
+
10
+func addExperimentalRouters(routers []router.Router) []router.Router {
11
+	return append(routers, pluginrouter.NewRouter(plugin.GetManager()))
12
+}
... ...
@@ -486,7 +486,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
486 486
 	}
487 487
 
488 488
 	// Configure the volumes driver
489
-	volStore, err := configureVolumes(config, rootUID, rootGID)
489
+	volStore, err := d.configureVolumes(rootUID, rootGID)
490 490
 	if err != nil {
491 491
 		return nil, err
492 492
 	}
... ...
@@ -768,8 +768,8 @@ func setDefaultMtu(config *Config) {
768 768
 	config.Mtu = defaultNetworkMtu
769 769
 }
770 770
 
771
-func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore, error) {
772
-	volumesDriver, err := local.New(config.Root, rootUID, rootGID)
771
+func (daemon *Daemon) configureVolumes(rootUID, rootGID int) (*store.VolumeStore, error) {
772
+	volumesDriver, err := local.New(daemon.configStore.Root, rootUID, rootGID)
773 773
 	if err != nil {
774 774
 		return nil, err
775 775
 	}
... ...
@@ -777,7 +777,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
777 777
 	if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) {
778 778
 		return nil, fmt.Errorf("local volume driver could not be registered")
779 779
 	}
780
-	return store.New(config.Root)
780
+	return store.New(daemon.configStore.Root)
781 781
 }
782 782
 
783 783
 // IsShuttingDown tells whether the daemon is shutting down or not
... ...
@@ -23,7 +23,7 @@ func lookupPlugin(name, home string, opts []string) (Driver, error) {
23 23
 	if err != nil {
24 24
 		return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err)
25 25
 	}
26
-	return newPluginDriver(name, home, opts, pl.Client)
26
+	return newPluginDriver(name, home, opts, pl.Client())
27 27
 }
28 28
 
29 29
 func newPluginDriver(name, home string, opts []string, c pluginClient) (Driver, error) {
... ...
@@ -84,7 +84,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
84 84
 	}
85 85
 
86 86
 	// makes sure name is not empty or `scratch`
87
-	if err := validateRepoName(repoInfo.Name()); err != nil {
87
+	if err := ValidateRepoName(repoInfo.Name()); err != nil {
88 88
 		return err
89 89
 	}
90 90
 
... ...
@@ -193,8 +193,8 @@ func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool
193 193
 	}
194 194
 }
195 195
 
196
-// validateRepoName validates the name of a repository.
197
-func validateRepoName(name string) error {
196
+// ValidateRepoName validates the name of a repository.
197
+func ValidateRepoName(name string) error {
198 198
 	if name == "" {
199 199
 		return fmt.Errorf("Repository name can't be empty")
200 200
 	}
... ...
@@ -77,7 +77,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPluginViaJSONFile(c *check.C) {
77 77
 	mux := http.NewServeMux()
78 78
 	s.jserver = httptest.NewServer(mux)
79 79
 
80
-	p := plugins.Plugin{Name: "json-external-graph-driver", Addr: s.jserver.URL}
80
+	p := plugins.NewLocalPlugin("json-external-graph-driver", s.jserver.URL)
81 81
 	b, err := json.Marshal(p)
82 82
 	c.Assert(err, check.IsNil)
83 83
 
... ...
@@ -203,18 +203,17 @@ func TestResponseModifierOverride(t *testing.T) {
203 203
 
204 204
 // createTestPlugin creates a new sample authorization plugin
205 205
 func createTestPlugin(t *testing.T) *authorizationPlugin {
206
-	plugin := &plugins.Plugin{Name: "authz"}
207 206
 	pwd, err := os.Getwd()
208 207
 	if err != nil {
209 208
 		t.Fatal(err)
210 209
 	}
211 210
 
212
-	plugin.Client, err = plugins.NewClient("unix:///"+path.Join(pwd, pluginAddress), tlsconfig.Options{InsecureSkipVerify: true})
211
+	client, err := plugins.NewClient("unix:///"+path.Join(pwd, pluginAddress), &tlsconfig.Options{InsecureSkipVerify: true})
213 212
 	if err != nil {
214 213
 		t.Fatalf("Failed to create client %v", err)
215 214
 	}
216 215
 
217
-	return &authorizationPlugin{name: "plugin", plugin: plugin}
216
+	return &authorizationPlugin{name: "plugin", plugin: client}
218 217
 }
219 218
 
220 219
 // AuthZPluginTestServer is a simple server that implements the authZ plugin interface
... ...
@@ -35,7 +35,7 @@ func NewPlugins(names []string) []Plugin {
35 35
 
36 36
 // authorizationPlugin is an internal adapter to docker plugin system
37 37
 type authorizationPlugin struct {
38
-	plugin *plugins.Plugin
38
+	plugin *plugins.Client
39 39
 	name   string
40 40
 	once   sync.Once
41 41
 }
... ...
@@ -54,7 +54,7 @@ func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error)
54 54
 	}
55 55
 
56 56
 	authRes := &Response{}
57
-	if err := a.plugin.Client.Call(AuthZApiRequest, authReq, authRes); err != nil {
57
+	if err := a.plugin.Call(AuthZApiRequest, authReq, authRes); err != nil {
58 58
 		return nil, err
59 59
 	}
60 60
 
... ...
@@ -67,7 +67,7 @@ func (a *authorizationPlugin) AuthZResponse(authReq *Request) (*Response, error)
67 67
 	}
68 68
 
69 69
 	authRes := &Response{}
70
-	if err := a.plugin.Client.Call(AuthZApiResponse, authReq, authRes); err != nil {
70
+	if err := a.plugin.Call(AuthZApiResponse, authReq, authRes); err != nil {
71 71
 		return nil, err
72 72
 	}
73 73
 
... ...
@@ -80,7 +80,12 @@ func (a *authorizationPlugin) initPlugin() error {
80 80
 	var err error
81 81
 	a.once.Do(func() {
82 82
 		if a.plugin == nil {
83
-			a.plugin, err = plugins.Get(a.name, AuthZApiImplements)
83
+			plugin, e := plugins.Get(a.name, AuthZApiImplements)
84
+			if e != nil {
85
+				err = e
86
+				return
87
+			}
88
+			a.plugin = plugin.Client()
84 89
 		}
85 90
 	})
86 91
 	return err
... ...
@@ -20,14 +20,16 @@ const (
20 20
 )
21 21
 
22 22
 // NewClient creates a new plugin client (http).
23
-func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
23
+func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
24 24
 	tr := &http.Transport{}
25 25
 
26
-	c, err := tlsconfig.Client(tlsConfig)
27
-	if err != nil {
28
-		return nil, err
26
+	if tlsConfig != nil {
27
+		c, err := tlsconfig.Client(*tlsConfig)
28
+		if err != nil {
29
+			return nil, err
30
+		}
31
+		tr.TLSClientConfig = c
29 32
 	}
30
-	tr.TLSClientConfig = c
31 33
 
32 34
 	u, err := url.Parse(addr)
33 35
 	if err != nil {
... ...
@@ -31,7 +31,7 @@ func teardownRemotePluginServer() {
31 31
 }
32 32
 
33 33
 func TestFailedConnection(t *testing.T) {
34
-	c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true})
34
+	c, _ := NewClient("tcp://127.0.0.1:1", &tlsconfig.Options{InsecureSkipVerify: true})
35 35
 	_, err := c.callWithRetry("Service.Method", nil, false)
36 36
 	if err == nil {
37 37
 		t.Fatal("Unexpected successful connection")
... ...
@@ -55,7 +55,7 @@ func TestEchoInputOutput(t *testing.T) {
55 55
 		io.Copy(w, r.Body)
56 56
 	})
57 57
 
58
-	c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
58
+	c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true})
59 59
 	var output Manifest
60 60
 	err := c.Call("Test.Echo", m, &output)
61 61
 	if err != nil {
... ...
@@ -64,7 +64,7 @@ func (l *localRegistry) Plugin(name string) (*Plugin, error) {
64 64
 
65 65
 	for _, p := range socketpaths {
66 66
 		if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
67
-			return newLocalPlugin(name, "unix://"+p), nil
67
+			return NewLocalPlugin(name, "unix://"+p), nil
68 68
 		}
69 69
 	}
70 70
 
... ...
@@ -101,7 +101,7 @@ func readPluginInfo(name, path string) (*Plugin, error) {
101 101
 		return nil, fmt.Errorf("Unknown protocol")
102 102
 	}
103 103
 
104
-	return newLocalPlugin(name, addr), nil
104
+	return NewLocalPlugin(name, addr), nil
105 105
 }
106 106
 
107 107
 func readPluginJSONInfo(name, path string) (*Plugin, error) {
... ...
@@ -115,7 +115,7 @@ func readPluginJSONInfo(name, path string) (*Plugin, error) {
115 115
 	if err := json.NewDecoder(f).Decode(&p); err != nil {
116 116
 		return nil, err
117 117
 	}
118
-	p.Name = name
118
+	p.name = name
119 119
 	if len(p.TLSConfig.CAFile) == 0 {
120 120
 		p.TLSConfig.InsecureSkipVerify = true
121 121
 	}
... ...
@@ -58,7 +58,7 @@ func TestFileSpecPlugin(t *testing.T) {
58 58
 			t.Fatal(err)
59 59
 		}
60 60
 
61
-		if p.Name != c.name {
61
+		if p.name != c.name {
62 62
 			t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
63 63
 		}
64 64
 
... ...
@@ -97,7 +97,7 @@ func TestFileJSONSpecPlugin(t *testing.T) {
97 97
 		t.Fatal(err)
98 98
 	}
99 99
 
100
-	if plugin.Name != "example" {
100
+	if plugin.name != "example" {
101 101
 		t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name)
102 102
 	}
103 103
 
... ...
@@ -45,7 +45,7 @@ func TestLocalSocket(t *testing.T) {
45 45
 			t.Fatalf("Expected %v, was %v\n", p, pp)
46 46
 		}
47 47
 
48
-		if p.Name != "echo" {
48
+		if p.name != "echo" {
49 49
 			t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
50 50
 		}
51 51
 
... ...
@@ -55,13 +55,13 @@ type Manifest struct {
55 55
 // Plugin is the definition of a docker plugin.
56 56
 type Plugin struct {
57 57
 	// Name of the plugin
58
-	Name string `json:"-"`
58
+	name string
59 59
 	// Address of the plugin
60 60
 	Addr string
61 61
 	// TLS configuration of the plugin
62
-	TLSConfig tlsconfig.Options
62
+	TLSConfig *tlsconfig.Options
63 63
 	// Client attached to the plugin
64
-	Client *Client `json:"-"`
64
+	client *Client
65 65
 	// Manifest of the plugin (see above)
66 66
 	Manifest *Manifest `json:"-"`
67 67
 
... ...
@@ -73,11 +73,23 @@ type Plugin struct {
73 73
 	activateWait *sync.Cond
74 74
 }
75 75
 
76
-func newLocalPlugin(name, addr string) *Plugin {
76
+// Name returns the name of the plugin.
77
+func (p *Plugin) Name() string {
78
+	return p.name
79
+}
80
+
81
+// Client returns a ready-to-use plugin client that can be used to communicate with the plugin.
82
+func (p *Plugin) Client() *Client {
83
+	return p.client
84
+}
85
+
86
+// NewLocalPlugin creates a new local plugin.
87
+func NewLocalPlugin(name, addr string) *Plugin {
77 88
 	return &Plugin{
78
-		Name:         name,
79
-		Addr:         addr,
80
-		TLSConfig:    tlsconfig.Options{InsecureSkipVerify: true},
89
+		name: name,
90
+		Addr: addr,
91
+		// TODO: change to nil
92
+		TLSConfig:    &tlsconfig.Options{InsecureSkipVerify: true},
81 93
 		activateWait: sync.NewCond(&sync.Mutex{}),
82 94
 	}
83 95
 }
... ...
@@ -102,10 +114,10 @@ func (p *Plugin) activateWithLock() error {
102 102
 	if err != nil {
103 103
 		return err
104 104
 	}
105
-	p.Client = c
105
+	p.client = c
106 106
 
107 107
 	m := new(Manifest)
108
-	if err = p.Client.Call("Plugin.Activate", nil, m); err != nil {
108
+	if err = p.client.Call("Plugin.Activate", nil, m); err != nil {
109 109
 		return err
110 110
 	}
111 111
 
... ...
@@ -116,7 +128,7 @@ func (p *Plugin) activateWithLock() error {
116 116
 		if !handled {
117 117
 			continue
118 118
 		}
119
-		handler(p.Name, p.Client)
119
+		handler(p.name, p.client)
120 120
 	}
121 121
 	return nil
122 122
 }
123 123
new file mode 100644
... ...
@@ -0,0 +1,139 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"fmt"
6
+	"net/http"
7
+	"os"
8
+	"path/filepath"
9
+
10
+	"github.com/Sirupsen/logrus"
11
+	"github.com/docker/docker/pkg/archive"
12
+	"github.com/docker/docker/pkg/stringid"
13
+	"github.com/docker/docker/plugin/distribution"
14
+	"github.com/docker/docker/reference"
15
+	"github.com/docker/engine-api/types"
16
+)
17
+
18
+// Disable deactivates a plugin, which implies that they cannot be used by containers.
19
+func (pm *Manager) Disable(name string) error {
20
+	p, err := pm.get(name)
21
+	if err != nil {
22
+		return err
23
+	}
24
+	return pm.disable(p)
25
+}
26
+
27
+// Enable activates a plugin, which implies that they are ready to be used by containers.
28
+func (pm *Manager) Enable(name string) error {
29
+	p, err := pm.get(name)
30
+	if err != nil {
31
+		return err
32
+	}
33
+	return pm.enable(p)
34
+}
35
+
36
+// Inspect examines a plugin manifest
37
+func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
38
+	p, err := pm.get(name)
39
+	if err != nil {
40
+		return tp, err
41
+	}
42
+	return p.p, nil
43
+}
44
+
45
+// Pull pulls a plugin and enables it.
46
+func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
47
+	ref, err := reference.ParseNamed(name)
48
+	if err != nil {
49
+		logrus.Debugf("error in reference.ParseNamed: %v", err)
50
+		return nil, err
51
+	}
52
+	name = ref.String()
53
+
54
+	if p, _ := pm.get(name); p != nil {
55
+		logrus.Debugf("plugin already exists")
56
+		return nil, fmt.Errorf("%s exists", name)
57
+	}
58
+
59
+	pluginID := stringid.GenerateNonCryptoID()
60
+
61
+	if err := os.MkdirAll(filepath.Join(pm.libRoot, pluginID), 0755); err != nil {
62
+		logrus.Debugf("error in MkdirAll: %v", err)
63
+		return nil, err
64
+	}
65
+
66
+	pd, err := distribution.Pull(name, pm.registryService, metaHeader, authConfig)
67
+	if err != nil {
68
+		logrus.Debugf("error in distribution.Pull(): %v", err)
69
+		return nil, err
70
+	}
71
+
72
+	if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil {
73
+		logrus.Debugf("error in distribution.WritePullData(): %v", err)
74
+		return nil, err
75
+	}
76
+
77
+	p := pm.newPlugin(ref, pluginID)
78
+	if ref, ok := ref.(reference.NamedTagged); ok {
79
+		p.p.Tag = ref.Tag()
80
+	}
81
+
82
+	if err := pm.initPlugin(p); err != nil {
83
+		return nil, err
84
+	}
85
+
86
+	pm.Lock()
87
+	pm.plugins[pluginID] = p
88
+	pm.nameToID[name] = pluginID
89
+	pm.save()
90
+	pm.Unlock()
91
+
92
+	return computePrivileges(&p.p.Manifest), nil
93
+}
94
+
95
+// List displays the list of plugins and associated metadata.
96
+func (pm *Manager) List() ([]types.Plugin, error) {
97
+	out := make([]types.Plugin, 0, len(pm.plugins))
98
+	for _, p := range pm.plugins {
99
+		out = append(out, p.p)
100
+	}
101
+	return out, nil
102
+}
103
+
104
+// Push pushes a plugin to the store.
105
+func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.AuthConfig) error {
106
+	p, err := pm.get(name)
107
+	dest := filepath.Join(pm.libRoot, p.p.ID)
108
+	config, err := os.Open(filepath.Join(dest, "manifest.json"))
109
+	if err != nil {
110
+		return err
111
+	}
112
+	rootfs, err := archive.Tar(filepath.Join(dest, "rootfs"), archive.Gzip)
113
+	if err != nil {
114
+		return err
115
+	}
116
+	_, err = distribution.Push(name, pm.registryService, metaHeader, authConfig, config, rootfs)
117
+	// XXX: Ignore returning digest for now.
118
+	// Since digest needs to be written to the ProgressWriter.
119
+	return nil
120
+}
121
+
122
+// Remove deletes plugin's root directory.
123
+func (pm *Manager) Remove(name string) error {
124
+	p, err := pm.get(name)
125
+	if err != nil {
126
+		return err
127
+	}
128
+	return pm.remove(p)
129
+}
130
+
131
+// Set sets plugin args
132
+func (pm *Manager) Set(name string, args []string) error {
133
+	p, err := pm.get(name)
134
+	if err != nil {
135
+		return err
136
+	}
137
+	return pm.set(p, args)
138
+}
0 139
new file mode 100644
... ...
@@ -0,0 +1,208 @@
0
+// +build experimental
1
+
2
+package distribution
3
+
4
+import (
5
+	"encoding/json"
6
+	"fmt"
7
+	"io"
8
+	"io/ioutil"
9
+	"net/http"
10
+	"os"
11
+	"path/filepath"
12
+
13
+	"github.com/Sirupsen/logrus"
14
+	"github.com/docker/distribution"
15
+	"github.com/docker/distribution/manifest/schema2"
16
+	dockerdist "github.com/docker/docker/distribution"
17
+	archive "github.com/docker/docker/pkg/chrootarchive"
18
+	"github.com/docker/docker/reference"
19
+	"github.com/docker/docker/registry"
20
+	"github.com/docker/engine-api/types"
21
+	"golang.org/x/net/context"
22
+)
23
+
24
+// PullData is the plugin manifest and the rootfs
25
+type PullData interface {
26
+	Config() ([]byte, error)
27
+	Layer() (io.ReadCloser, error)
28
+}
29
+
30
+type pullData struct {
31
+	repository distribution.Repository
32
+	manifest   schema2.Manifest
33
+	index      int
34
+}
35
+
36
+func (pd *pullData) Config() ([]byte, error) {
37
+	blobs := pd.repository.Blobs(context.Background())
38
+	config, err := blobs.Get(context.Background(), pd.manifest.Config.Digest)
39
+	if err != nil {
40
+		return nil, err
41
+	}
42
+	// validate
43
+	var p types.Plugin
44
+	if err := json.Unmarshal(config, &p); err != nil {
45
+		return nil, err
46
+	}
47
+	return config, nil
48
+}
49
+
50
+func (pd *pullData) Layer() (io.ReadCloser, error) {
51
+	if pd.index >= len(pd.manifest.Layers) {
52
+		return nil, io.EOF
53
+	}
54
+
55
+	blobs := pd.repository.Blobs(context.Background())
56
+	rsc, err := blobs.Open(context.Background(), pd.manifest.Layers[pd.index].Digest)
57
+	if err != nil {
58
+		return nil, err
59
+	}
60
+	pd.index++
61
+	return rsc, nil
62
+}
63
+
64
+// Pull downloads the plugin from Store
65
+func Pull(name string, rs registry.Service, metaheader http.Header, authConfig *types.AuthConfig) (PullData, error) {
66
+	ref, err := reference.ParseNamed(name)
67
+	if err != nil {
68
+		logrus.Debugf("pull.go: error in ParseNamed: %v", err)
69
+		return nil, err
70
+	}
71
+
72
+	repoInfo, err := rs.ResolveRepository(ref)
73
+	if err != nil {
74
+		logrus.Debugf("pull.go: error in ResolveRepository: %v", err)
75
+		return nil, err
76
+	}
77
+
78
+	if err := dockerdist.ValidateRepoName(repoInfo.Name()); err != nil {
79
+		logrus.Debugf("pull.go: error in ValidateRepoName: %v", err)
80
+		return nil, err
81
+	}
82
+
83
+	endpoints, err := rs.LookupPullEndpoints(repoInfo.Hostname())
84
+	if err != nil {
85
+		logrus.Debugf("pull.go: error in LookupPullEndpoints: %v", err)
86
+		return nil, err
87
+	}
88
+
89
+	var confirmedV2 bool
90
+	var repository distribution.Repository
91
+
92
+	for _, endpoint := range endpoints {
93
+		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
94
+			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
95
+			continue
96
+		}
97
+
98
+		// TODO: reuse contexts
99
+		repository, confirmedV2, err = dockerdist.NewV2Repository(context.Background(), repoInfo, endpoint, metaheader, authConfig, "pull")
100
+		if err != nil {
101
+			logrus.Debugf("pull.go: error in NewV2Repository: %v", err)
102
+			return nil, err
103
+		}
104
+		if !confirmedV2 {
105
+			logrus.Debugf("pull.go: !confirmedV2")
106
+			return nil, ErrUnSupportedRegistry
107
+		}
108
+		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
109
+		break
110
+	}
111
+
112
+	tag := DefaultTag
113
+	if ref, ok := ref.(reference.NamedTagged); ok {
114
+		tag = ref.Tag()
115
+	}
116
+
117
+	// tags := repository.Tags(context.Background())
118
+	// desc, err := tags.Get(context.Background(), tag)
119
+	// 	if err != nil {
120
+	// 		return nil, err
121
+	// 	}
122
+	//
123
+	msv, err := repository.Manifests(context.Background())
124
+	if err != nil {
125
+		logrus.Debugf("pull.go: error in repository.Manifests: %v", err)
126
+		return nil, err
127
+	}
128
+	manifest, err := msv.Get(context.Background(), "", distribution.WithTag(tag))
129
+	if err != nil {
130
+		// TODO: change 401 to 404
131
+		logrus.Debugf("pull.go: error in msv.Get(): %v", err)
132
+		return nil, err
133
+	}
134
+
135
+	_, pl, err := manifest.Payload()
136
+	if err != nil {
137
+		logrus.Debugf("pull.go: error in manifest.Payload(): %v", err)
138
+		return nil, err
139
+	}
140
+	var m schema2.Manifest
141
+	if err := json.Unmarshal(pl, &m); err != nil {
142
+		logrus.Debugf("pull.go: error in json.Unmarshal(): %v", err)
143
+		return nil, err
144
+	}
145
+
146
+	pd := &pullData{
147
+		repository: repository,
148
+		manifest:   m,
149
+	}
150
+
151
+	logrus.Debugf("manifest: %s", pl)
152
+	return pd, nil
153
+}
154
+
155
+// WritePullData extracts manifest and rootfs to the disk.
156
+func WritePullData(pd PullData, dest string, extract bool) error {
157
+	config, err := pd.Config()
158
+	if err != nil {
159
+		return err
160
+	}
161
+	var p types.Plugin
162
+	if err := json.Unmarshal(config, &p); err != nil {
163
+		return err
164
+	}
165
+	logrus.Debugf("%#v", p)
166
+
167
+	if err := os.MkdirAll(dest, 0700); err != nil {
168
+		return err
169
+	}
170
+
171
+	if extract {
172
+		if err := ioutil.WriteFile(filepath.Join(dest, "manifest.json"), config, 0600); err != nil {
173
+			return err
174
+		}
175
+
176
+		if err := os.MkdirAll(filepath.Join(dest, "rootfs"), 0700); err != nil {
177
+			return err
178
+		}
179
+	}
180
+
181
+	for i := 0; ; i++ {
182
+		l, err := pd.Layer()
183
+		if err == io.EOF {
184
+			break
185
+		}
186
+		if err != nil {
187
+			return err
188
+		}
189
+
190
+		if !extract {
191
+			f, err := os.Create(filepath.Join(dest, fmt.Sprintf("layer%d.tar", i)))
192
+			if err != nil {
193
+				return err
194
+			}
195
+			io.Copy(f, l)
196
+			l.Close()
197
+			f.Close()
198
+			continue
199
+		}
200
+
201
+		if _, err := archive.ApplyLayer(filepath.Join(dest, "rootfs"), l); err != nil {
202
+			return err
203
+		}
204
+
205
+	}
206
+	return nil
207
+}
0 208
new file mode 100644
... ...
@@ -0,0 +1,134 @@
0
+// +build experimental
1
+
2
+package distribution
3
+
4
+import (
5
+	"crypto/sha256"
6
+	"io"
7
+	"net/http"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+	"github.com/docker/distribution"
11
+	"github.com/docker/distribution/digest"
12
+	"github.com/docker/distribution/manifest/schema2"
13
+	dockerdist "github.com/docker/docker/distribution"
14
+	"github.com/docker/docker/reference"
15
+	"github.com/docker/docker/registry"
16
+	"github.com/docker/engine-api/types"
17
+	"golang.org/x/net/context"
18
+)
19
+
20
+// Push pushes a plugin to a registry.
21
+func Push(name string, rs registry.Service, metaHeader http.Header, authConfig *types.AuthConfig, config io.ReadCloser, layers io.ReadCloser) (digest.Digest, error) {
22
+	ref, err := reference.ParseNamed(name)
23
+	if err != nil {
24
+		return "", err
25
+	}
26
+
27
+	repoInfo, err := rs.ResolveRepository(ref)
28
+	if err != nil {
29
+		return "", err
30
+	}
31
+
32
+	if err := dockerdist.ValidateRepoName(repoInfo.Name()); err != nil {
33
+		return "", err
34
+	}
35
+
36
+	endpoints, err := rs.LookupPushEndpoints(repoInfo.Hostname())
37
+	if err != nil {
38
+		return "", err
39
+	}
40
+
41
+	var confirmedV2 bool
42
+	var repository distribution.Repository
43
+	for _, endpoint := range endpoints {
44
+		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
45
+			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
46
+			continue
47
+		}
48
+		repository, confirmedV2, err = dockerdist.NewV2Repository(context.Background(), repoInfo, endpoint, metaHeader, authConfig, "push", "pull")
49
+		if err != nil {
50
+			return "", err
51
+		}
52
+		if !confirmedV2 {
53
+			return "", ErrUnSupportedRegistry
54
+		}
55
+		logrus.Debugf("Trying to push %s to %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
56
+		// This means that we found an endpoint. and we are ready to push
57
+		break
58
+	}
59
+
60
+	// Returns a reference to the repository's blob service.
61
+	blobs := repository.Blobs(context.Background())
62
+
63
+	// Descriptor = {mediaType, size, digest}
64
+	var descs []distribution.Descriptor
65
+
66
+	for i, f := range []io.ReadCloser{config, layers} {
67
+		bw, err := blobs.Create(context.Background())
68
+		if err != nil {
69
+			logrus.Debugf("Error in blobs.Create: %v", err)
70
+			return "", err
71
+		}
72
+		h := sha256.New()
73
+		r := io.TeeReader(f, h)
74
+		_, err = io.Copy(bw, r)
75
+		if err != nil {
76
+			logrus.Debugf("Error in io.Copy: %v", err)
77
+			return "", err
78
+		}
79
+		f.Close()
80
+		mt := MediaTypeLayer
81
+		if i == 0 {
82
+			mt = MediaTypeConfig
83
+		}
84
+		// Commit completes the write process to the BlobService.
85
+		// The descriptor arg to Commit is called the "provisional" descriptor and
86
+		// used for validation.
87
+		// The returned descriptor should be the one used. Its called the "Canonical"
88
+		// descriptor.
89
+		desc, err := bw.Commit(context.Background(), distribution.Descriptor{
90
+			MediaType: mt,
91
+			// XXX: What about the Size?
92
+			Digest: digest.NewDigest("sha256", h),
93
+		})
94
+		if err != nil {
95
+			logrus.Debugf("Error in bw.Commit: %v", err)
96
+			return "", err
97
+		}
98
+		// The canonical descriptor is set the mediatype again, just in case.
99
+		// Dont touch the digest or the size here.
100
+		desc.MediaType = mt
101
+		logrus.Debugf("pushed blob: %s %s", desc.MediaType, desc.Digest)
102
+		descs = append(descs, desc)
103
+	}
104
+
105
+	// XXX: schema2.Versioned needs a MediaType as well.
106
+	// "application/vnd.docker.distribution.manifest.v2+json"
107
+	m, err := schema2.FromStruct(schema2.Manifest{Versioned: schema2.SchemaVersion, Config: descs[0], Layers: descs[1:]})
108
+	if err != nil {
109
+		logrus.Debugf("error in schema2.FromStruct: %v", err)
110
+		return "", err
111
+	}
112
+
113
+	msv, err := repository.Manifests(context.Background())
114
+	if err != nil {
115
+		logrus.Debugf("error in repository.Manifests: %v", err)
116
+		return "", err
117
+	}
118
+
119
+	_, pl, err := m.Payload()
120
+	if err != nil {
121
+		logrus.Debugf("error in m.Payload: %v", err)
122
+		return "", err
123
+	}
124
+
125
+	logrus.Debugf("Pushed manifest: %s", pl)
126
+
127
+	tag := DefaultTag
128
+	if tagged, ok := ref.(reference.NamedTagged); ok {
129
+		tag = tagged.Tag()
130
+	}
131
+
132
+	return msv.Put(context.Background(), m, distribution.WithTag(tag))
133
+}
0 134
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+// +build experimental
1
+
2
+package distribution
3
+
4
+import "errors"
5
+
6
+// ErrUnSupportedRegistry indicates that the registry does not support v2 protocol
7
+var ErrUnSupportedRegistry = errors.New("Only V2 repositories are supported for plugin distribution")
8
+
9
+// Plugin related media types
10
+const (
11
+	MediaTypeManifest = "application/vnd.docker.distribution.manifest.v2+json"
12
+	MediaTypeConfig   = "application/vnd.docker.plugin.v0+json"
13
+	MediaTypeLayer    = "application/vnd.docker.image.rootfs.diff.tar.gzip"
14
+	DefaultTag        = "latest"
15
+)
0 16
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+package plugin
1
+
2
+import "github.com/docker/docker/pkg/plugins"
3
+
4
+// Plugin represents a plugin. It is used to abstract from an older plugin architecture (in pkg/plugins).
5
+type Plugin interface {
6
+	Client() *plugins.Client
7
+	Name() string
8
+}
0 9
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// +build !experimental
1
+
2
+package plugin
3
+
4
+import "github.com/docker/docker/pkg/plugins"
5
+
6
+// FindWithCapability returns a list of plugins matching the given capability.
7
+func FindWithCapability(capability string) ([]Plugin, error) {
8
+	pl, err := plugins.GetAll(capability)
9
+	if err != nil {
10
+		return nil, err
11
+	}
12
+	result := make([]Plugin, len(pl))
13
+	for i, p := range pl {
14
+		result[i] = p
15
+	}
16
+	return result, nil
17
+}
18
+
19
+// LookupWithCapability returns a plugin matching the given name and capability.
20
+func LookupWithCapability(name, capability string) (Plugin, error) {
21
+	return plugins.Get(name, capability)
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,384 @@
0
+// +build experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"encoding/json"
6
+	"errors"
7
+	"fmt"
8
+	"io"
9
+	"os"
10
+	"path/filepath"
11
+	"strings"
12
+	"sync"
13
+
14
+	"github.com/Sirupsen/logrus"
15
+	"github.com/docker/docker/libcontainerd"
16
+	"github.com/docker/docker/pkg/ioutils"
17
+	"github.com/docker/docker/pkg/plugins"
18
+	"github.com/docker/docker/reference"
19
+	"github.com/docker/docker/registry"
20
+	"github.com/docker/docker/restartmanager"
21
+	"github.com/docker/engine-api/types"
22
+)
23
+
24
+const (
25
+	defaultPluginRuntimeDestination = "/run/docker/plugins"
26
+	defaultPluginStateDestination   = "/state"
27
+)
28
+
29
+var manager *Manager
30
+
31
+// ErrNotFound indicates that a plugin was not found locally.
32
+type ErrNotFound string
33
+
34
+func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
35
+
36
+// ErrInadequateCapability indicates that a plugin was found but did not have the requested capability.
37
+type ErrInadequateCapability struct {
38
+	name       string
39
+	capability string
40
+}
41
+
42
+func (e ErrInadequateCapability) Error() string {
43
+	return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability)
44
+}
45
+
46
+type plugin struct {
47
+	//sync.RWMutex TODO
48
+	p                 types.Plugin
49
+	client            *plugins.Client
50
+	restartManager    restartmanager.RestartManager
51
+	stateSourcePath   string
52
+	runtimeSourcePath string
53
+}
54
+
55
+func (p *plugin) Client() *plugins.Client {
56
+	return p.client
57
+}
58
+
59
+func (p *plugin) Name() string {
60
+	return p.p.Name
61
+}
62
+
63
+func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin {
64
+	p := &plugin{
65
+		p: types.Plugin{
66
+			Name: ref.Name(),
67
+			ID:   id,
68
+		},
69
+		stateSourcePath:   filepath.Join(pm.libRoot, id, "state"),
70
+		runtimeSourcePath: filepath.Join(pm.runRoot, id),
71
+	}
72
+	if ref, ok := ref.(reference.NamedTagged); ok {
73
+		p.p.Tag = ref.Tag()
74
+	}
75
+	return p
76
+}
77
+
78
+// TODO: figure out why save() doesn't json encode *plugin object
79
+type pluginMap map[string]*plugin
80
+
81
+// Manager controls the plugin subsystem.
82
+type Manager struct {
83
+	sync.RWMutex
84
+	libRoot          string
85
+	runRoot          string
86
+	plugins          pluginMap // TODO: figure out why save() doesn't json encode *plugin object
87
+	nameToID         map[string]string
88
+	handlers         map[string]func(string, *plugins.Client)
89
+	containerdClient libcontainerd.Client
90
+	registryService  registry.Service
91
+	handleLegacy     bool
92
+}
93
+
94
+// GetManager returns the singleton plugin Manager
95
+func GetManager() *Manager {
96
+	return manager
97
+}
98
+
99
+// Init (was NewManager) instantiates the singleton Manager.
100
+// TODO: revert this to NewManager once we get rid of all the singletons.
101
+func Init(root, execRoot string, remote libcontainerd.Remote, rs registry.Service) (err error) {
102
+	if manager != nil {
103
+		return nil
104
+	}
105
+
106
+	root = filepath.Join(root, "plugins")
107
+	execRoot = filepath.Join(execRoot, "plugins")
108
+	for _, dir := range []string{root, execRoot} {
109
+		if err := os.MkdirAll(dir, 0700); err != nil {
110
+			return err
111
+		}
112
+	}
113
+
114
+	manager = &Manager{
115
+		libRoot:         root,
116
+		runRoot:         execRoot,
117
+		plugins:         make(map[string]*plugin),
118
+		nameToID:        make(map[string]string),
119
+		handlers:        make(map[string]func(string, *plugins.Client)),
120
+		registryService: rs,
121
+		handleLegacy:    true,
122
+	}
123
+	if err := os.MkdirAll(manager.runRoot, 0700); err != nil {
124
+		return err
125
+	}
126
+	if err := manager.init(); err != nil {
127
+		return err
128
+	}
129
+	manager.containerdClient, err = remote.Client(manager)
130
+	if err != nil {
131
+		return err
132
+	}
133
+	return nil
134
+}
135
+
136
+// Handle sets a callback for a given capability. The callback will be called for every plugin with a given capability.
137
+// TODO: append instead of set?
138
+func Handle(capability string, callback func(string, *plugins.Client)) {
139
+	pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
140
+	manager.handlers[pluginType] = callback
141
+	if manager.handleLegacy {
142
+		plugins.Handle(capability, callback)
143
+	}
144
+}
145
+
146
+func (pm *Manager) get(name string) (*plugin, error) {
147
+	pm.RLock()
148
+	id, nameOk := pm.nameToID[name]
149
+	p, idOk := pm.plugins[id]
150
+	pm.RUnlock()
151
+	if !nameOk || !idOk {
152
+		return nil, ErrNotFound(name)
153
+	}
154
+	return p, nil
155
+}
156
+
157
+// FindWithCapability returns a list of plugins matching the given capability.
158
+func FindWithCapability(capability string) ([]Plugin, error) {
159
+	handleLegacy := true
160
+	result := make([]Plugin, 0, 1)
161
+	if manager != nil {
162
+		handleLegacy = manager.handleLegacy
163
+		manager.RLock()
164
+		defer manager.RUnlock()
165
+	pluginLoop:
166
+		for _, p := range manager.plugins {
167
+			for _, typ := range p.p.Manifest.Interface.Types {
168
+				if typ.Capability != capability || typ.Prefix != "docker" {
169
+					continue pluginLoop
170
+				}
171
+			}
172
+			result = append(result, p)
173
+		}
174
+	}
175
+	if handleLegacy {
176
+		pl, err := plugins.GetAll(capability)
177
+		if err != nil {
178
+			return nil, fmt.Errorf("legacy plugin: %v", err)
179
+		}
180
+		for _, p := range pl {
181
+			if _, ok := manager.nameToID[p.Name()]; !ok {
182
+				result = append(result, p)
183
+			}
184
+		}
185
+	}
186
+	return result, nil
187
+}
188
+
189
+// LookupWithCapability returns a plugin matching the given name and capability.
190
+func LookupWithCapability(name, capability string) (Plugin, error) {
191
+	var (
192
+		p   *plugin
193
+		err error
194
+	)
195
+	handleLegacy := true
196
+	if manager != nil {
197
+		p, err = manager.get(name)
198
+		if err != nil {
199
+			if _, ok := err.(ErrNotFound); !ok {
200
+				return nil, err
201
+			}
202
+			handleLegacy = manager.handleLegacy
203
+		} else {
204
+			handleLegacy = false
205
+		}
206
+	}
207
+	if handleLegacy {
208
+		p, err := plugins.Get(name, capability)
209
+		if err != nil {
210
+			return nil, fmt.Errorf("legacy plugin: %v", err)
211
+		}
212
+		return p, nil
213
+	} else if err != nil {
214
+		return nil, err
215
+	}
216
+
217
+	capability = strings.ToLower(capability)
218
+	for _, typ := range p.p.Manifest.Interface.Types {
219
+		if typ.Capability == capability && typ.Prefix == "docker" {
220
+			return p, nil
221
+		}
222
+	}
223
+	return nil, ErrInadequateCapability{name, capability}
224
+}
225
+
226
+// StateChanged updates daemon inter...
227
+func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
228
+	logrus.Debugf("plugin statechanged %s %#v", id, e)
229
+
230
+	return nil
231
+}
232
+
233
+// AttachStreams attaches io streams to the plugin
234
+func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error {
235
+	iop.Stdin.Close()
236
+
237
+	logger := logrus.New()
238
+	logger.Hooks.Add(logHook{id})
239
+	// TODO: cache writer per id
240
+	w := logger.Writer()
241
+	go func() {
242
+		io.Copy(w, iop.Stdout)
243
+	}()
244
+	go func() {
245
+		// TODO: update logrus and use logger.WriterLevel
246
+		io.Copy(w, iop.Stderr)
247
+	}()
248
+	return nil
249
+}
250
+
251
+func (pm *Manager) init() error {
252
+	dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
253
+	if err != nil {
254
+		if os.IsNotExist(err) {
255
+			return nil
256
+		}
257
+		return err
258
+	}
259
+	// TODO: Populate pm.plugins
260
+	if err := json.NewDecoder(dt).Decode(&pm.nameToID); err != nil {
261
+		return err
262
+	}
263
+	// FIXME: validate, restore
264
+
265
+	return nil
266
+}
267
+
268
+func (pm *Manager) initPlugin(p *plugin) error {
269
+	dt, err := os.Open(filepath.Join(pm.libRoot, p.p.ID, "manifest.json"))
270
+	if err != nil {
271
+		return err
272
+	}
273
+	err = json.NewDecoder(dt).Decode(&p.p.Manifest)
274
+	dt.Close()
275
+	if err != nil {
276
+		return err
277
+	}
278
+
279
+	p.p.Config.Mounts = make([]types.PluginMount, len(p.p.Manifest.Mounts))
280
+	for i, mount := range p.p.Manifest.Mounts {
281
+		p.p.Config.Mounts[i] = mount
282
+	}
283
+	p.p.Config.Env = make([]string, 0, len(p.p.Manifest.Env))
284
+	for _, env := range p.p.Manifest.Env {
285
+		if env.Value != nil {
286
+			p.p.Config.Env = append(p.p.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
287
+		}
288
+	}
289
+	copy(p.p.Config.Args, p.p.Manifest.Args.Value)
290
+
291
+	f, err := os.Create(filepath.Join(pm.libRoot, p.p.ID, "plugin-config.json"))
292
+	if err != nil {
293
+		return err
294
+	}
295
+	err = json.NewEncoder(f).Encode(&p.p.Config)
296
+	f.Close()
297
+	return err
298
+}
299
+
300
+func (pm *Manager) remove(p *plugin) error {
301
+	if p.p.Active {
302
+		return fmt.Errorf("plugin %s is active", p.p.Name)
303
+	}
304
+	pm.Lock() // fixme: lock single record
305
+	defer pm.Unlock()
306
+	os.RemoveAll(p.stateSourcePath)
307
+	delete(pm.plugins, p.p.Name)
308
+	pm.save()
309
+	return nil
310
+}
311
+
312
+func (pm *Manager) set(p *plugin, args []string) error {
313
+	m := make(map[string]string, len(args))
314
+	for _, arg := range args {
315
+		i := strings.Index(arg, "=")
316
+		if i < 0 {
317
+			return fmt.Errorf("No equal sign '=' found in %s", arg)
318
+		}
319
+		m[arg[:i]] = arg[i+1:]
320
+	}
321
+	return errors.New("not implemented")
322
+}
323
+
324
+// fixme: not safe
325
+func (pm *Manager) save() error {
326
+	filePath := filepath.Join(pm.libRoot, "plugins.json")
327
+
328
+	jsonData, err := json.Marshal(pm.nameToID)
329
+	if err != nil {
330
+		logrus.Debugf("Error in json.Marshal: %v", err)
331
+		return err
332
+	}
333
+	ioutils.AtomicWriteFile(filePath, jsonData, 0600)
334
+	return nil
335
+}
336
+
337
+type logHook struct{ id string }
338
+
339
+func (logHook) Levels() []logrus.Level {
340
+	return logrus.AllLevels
341
+}
342
+
343
+func (l logHook) Fire(entry *logrus.Entry) error {
344
+	entry.Data = logrus.Fields{"plugin": l.id}
345
+	return nil
346
+}
347
+
348
+func computePrivileges(m *types.PluginManifest) types.PluginPrivileges {
349
+	var privileges types.PluginPrivileges
350
+	if m.Network.Type != "null" && m.Network.Type != "bridge" {
351
+		privileges = append(privileges, types.PluginPrivilege{
352
+			Name:        "network",
353
+			Description: "",
354
+			Value:       []string{m.Network.Type},
355
+		})
356
+	}
357
+	for _, mount := range m.Mounts {
358
+		if mount.Source != nil {
359
+			privileges = append(privileges, types.PluginPrivilege{
360
+				Name:        "mount",
361
+				Description: "",
362
+				Value:       []string{*mount.Source},
363
+			})
364
+		}
365
+	}
366
+	for _, device := range m.Devices {
367
+		if device.Path != nil {
368
+			privileges = append(privileges, types.PluginPrivilege{
369
+				Name:        "device",
370
+				Description: "",
371
+				Value:       []string{*device.Path},
372
+			})
373
+		}
374
+	}
375
+	if len(m.Capabilities) > 0 {
376
+		privileges = append(privileges, types.PluginPrivilege{
377
+			Name:        "capabilities",
378
+			Description: "",
379
+			Value:       m.Capabilities,
380
+		})
381
+	}
382
+	return privileges
383
+}
0 384
new file mode 100644
... ...
@@ -0,0 +1,126 @@
0
+// +build linux,experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"os"
6
+	"path/filepath"
7
+	"syscall"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+	"github.com/docker/docker/libcontainerd"
11
+	"github.com/docker/docker/oci"
12
+	"github.com/docker/docker/pkg/plugins"
13
+	"github.com/docker/docker/pkg/system"
14
+	"github.com/docker/docker/restartmanager"
15
+	"github.com/docker/engine-api/types"
16
+	"github.com/docker/engine-api/types/container"
17
+	"github.com/opencontainers/specs/specs-go"
18
+)
19
+
20
+func (pm *Manager) enable(p *plugin) error {
21
+	spec, err := pm.initSpec(p)
22
+	if err != nil {
23
+		return err
24
+	}
25
+
26
+	p.restartManager = restartmanager.New(container.RestartPolicy{Name: "always"}, 0)
27
+	if err := pm.containerdClient.Create(p.p.ID, libcontainerd.Spec(*spec), libcontainerd.WithRestartManager(p.restartManager)); err != nil { // POC-only
28
+		return err
29
+	}
30
+
31
+	socket := p.p.Manifest.Interface.Socket
32
+	p.client, err = plugins.NewClient("unix://"+filepath.Join(p.runtimeSourcePath, socket), nil)
33
+	if err != nil {
34
+		return err
35
+	}
36
+
37
+	//TODO: check net.Dial
38
+
39
+	pm.Lock() // fixme: lock single record
40
+	p.p.Active = true
41
+	pm.save()
42
+	pm.Unlock()
43
+
44
+	for _, typ := range p.p.Manifest.Interface.Types {
45
+		if handler := pm.handlers[typ.String()]; handler != nil {
46
+			handler(p.Name(), p.Client())
47
+		}
48
+	}
49
+
50
+	return nil
51
+}
52
+
53
+func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
54
+	s := oci.DefaultSpec()
55
+
56
+	rootfs := filepath.Join(pm.libRoot, p.p.ID, "rootfs")
57
+	s.Root = specs.Root{
58
+		Path:     rootfs,
59
+		Readonly: false, // TODO: all plugins should be readonly? settable in manifest?
60
+	}
61
+
62
+	mounts := append(p.p.Config.Mounts, types.PluginMount{
63
+		Source:      &p.runtimeSourcePath,
64
+		Destination: defaultPluginRuntimeDestination,
65
+		Type:        "bind",
66
+		Options:     []string{"rbind", "rshared"},
67
+	}, types.PluginMount{
68
+		Source:      &p.stateSourcePath,
69
+		Destination: defaultPluginStateDestination,
70
+		Type:        "bind",
71
+		Options:     []string{"rbind", "rshared"},
72
+	})
73
+	for _, mount := range mounts {
74
+		m := specs.Mount{
75
+			Destination: mount.Destination,
76
+			Type:        mount.Type,
77
+			Options:     mount.Options,
78
+		}
79
+		// TODO: if nil, then it's required and user didn't set it
80
+		if mount.Source != nil {
81
+			m.Source = *mount.Source
82
+		}
83
+		if m.Source != "" && m.Type == "bind" {
84
+			fi, err := os.Lstat(filepath.Join(rootfs, string(os.PathSeparator), m.Destination)) // TODO: followsymlinks
85
+			if err != nil {
86
+				return nil, err
87
+			}
88
+			if fi.IsDir() {
89
+				if err := os.MkdirAll(m.Source, 0700); err != nil {
90
+					return nil, err
91
+				}
92
+			}
93
+		}
94
+		s.Mounts = append(s.Mounts, m)
95
+	}
96
+
97
+	envs := make([]string, 1, len(p.p.Config.Env)+1)
98
+	envs[0] = "PATH=" + system.DefaultPathEnv
99
+	envs = append(envs, p.p.Config.Env...)
100
+
101
+	args := append(p.p.Manifest.Entrypoint, p.p.Config.Args...)
102
+	s.Process = specs.Process{
103
+		Terminal: false,
104
+		Args:     args,
105
+		Cwd:      "/", // TODO: add in manifest?
106
+		Env:      envs,
107
+	}
108
+
109
+	return &s, nil
110
+}
111
+
112
+func (pm *Manager) disable(p *plugin) error {
113
+	if err := p.restartManager.Cancel(); err != nil {
114
+		logrus.Error(err)
115
+	}
116
+	if err := pm.containerdClient.Signal(p.p.ID, int(syscall.SIGKILL)); err != nil {
117
+		logrus.Error(err)
118
+	}
119
+	os.RemoveAll(p.runtimeSourcePath)
120
+	pm.Lock() // fixme: lock single record
121
+	defer pm.Unlock()
122
+	p.p.Active = false
123
+	pm.save()
124
+	return nil
125
+}
0 126
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+// +build windows,experimental
1
+
2
+package plugin
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"github.com/opencontainers/specs/specs-go"
8
+)
9
+
10
+func (pm *Manager) enable(p *plugin) error {
11
+	return fmt.Errorf("Not implemented")
12
+}
13
+
14
+func (pm *Manager) initSpec(p *plugin) (*specs.Spec, error) {
15
+	return nil, fmt.Errorf("Not implemented")
16
+}
17
+
18
+func (pm *Manager) disable(p *plugin) error {
19
+	return fmt.Errorf("Not implemented")
20
+}
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	"sync"
8 8
 
9 9
 	"github.com/docker/docker/pkg/locker"
10
-	"github.com/docker/docker/pkg/plugins"
10
+	"github.com/docker/docker/plugin"
11 11
 	"github.com/docker/docker/volume"
12 12
 )
13 13
 
... ...
@@ -88,10 +88,10 @@ func Unregister(name string) bool {
88 88
 	return true
89 89
 }
90 90
 
91
-// Lookup returns the driver associated with the given name. If a
91
+// lookup returns the driver associated with the given name. If a
92 92
 // driver with the given name has not been registered it checks if
93 93
 // there is a VolumeDriver plugin available with the given name.
94
-func Lookup(name string) (volume.Driver, error) {
94
+func lookup(name string) (volume.Driver, error) {
95 95
 	drivers.driverLock.Lock(name)
96 96
 	defer drivers.driverLock.Unlock(name)
97 97
 
... ...
@@ -102,7 +102,7 @@ func Lookup(name string) (volume.Driver, error) {
102 102
 		return ext, nil
103 103
 	}
104 104
 
105
-	pl, err := plugins.Get(name, extName)
105
+	p, err := plugin.LookupWithCapability(name, extName)
106 106
 	if err != nil {
107 107
 		return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
108 108
 	}
... ...
@@ -113,7 +113,7 @@ func Lookup(name string) (volume.Driver, error) {
113 113
 		return ext, nil
114 114
 	}
115 115
 
116
-	d := NewVolumeDriver(name, pl.Client)
116
+	d := NewVolumeDriver(name, p.Client())
117 117
 	if err := validateDriver(d); err != nil {
118 118
 		return nil, err
119 119
 	}
... ...
@@ -136,7 +136,7 @@ func GetDriver(name string) (volume.Driver, error) {
136 136
 	if name == "" {
137 137
 		name = volume.DefaultDriverName
138 138
 	}
139
-	return Lookup(name)
139
+	return lookup(name)
140 140
 }
141 141
 
142 142
 // GetDriverList returns list of volume drivers registered.
... ...
@@ -153,9 +153,9 @@ func GetDriverList() []string {
153 153
 
154 154
 // GetAllDrivers lists all the registered drivers
155 155
 func GetAllDrivers() ([]volume.Driver, error) {
156
-	plugins, err := plugins.GetAll(extName)
156
+	plugins, err := plugin.FindWithCapability(extName)
157 157
 	if err != nil {
158
-		return nil, err
158
+		return nil, fmt.Errorf("error listing plugins: %v", err)
159 159
 	}
160 160
 	var ds []volume.Driver
161 161
 
... ...
@@ -167,13 +167,14 @@ func GetAllDrivers() ([]volume.Driver, error) {
167 167
 	}
168 168
 
169 169
 	for _, p := range plugins {
170
-		ext, ok := drivers.extensions[p.Name]
170
+		name := p.Name()
171
+		ext, ok := drivers.extensions[name]
171 172
 		if ok {
172 173
 			continue
173 174
 		}
174 175
 
175
-		ext = NewVolumeDriver(p.Name, p.Client)
176
-		drivers.extensions[p.Name] = ext
176
+		ext = NewVolumeDriver(name, p.Client())
177
+		drivers.extensions[name] = ext
177 178
 		ds = append(ds, ext)
178 179
 	}
179 180
 	return ds, nil
... ...
@@ -58,7 +58,7 @@ func TestVolumeRequestError(t *testing.T) {
58 58
 	})
59 59
 
60 60
 	u, _ := url.Parse(server.URL)
61
-	client, err := plugins.NewClient("tcp://"+u.Host, tlsconfig.Options{InsecureSkipVerify: true})
61
+	client, err := plugins.NewClient("tcp://"+u.Host, &tlsconfig.Options{InsecureSkipVerify: true})
62 62
 	if err != nil {
63 63
 		t.Fatal(err)
64 64
 	}
... ...
@@ -157,15 +157,16 @@ func (s *VolumeStore) List() ([]volume.Volume, []string, error) {
157 157
 
158 158
 // list goes through each volume driver and asks for its list of volumes.
159 159
 func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
160
-	drivers, err := volumedrivers.GetAllDrivers()
161
-	if err != nil {
162
-		return nil, nil, err
163
-	}
164 160
 	var (
165 161
 		ls       []volume.Volume
166 162
 		warnings []string
167 163
 	)
168 164
 
165
+	drivers, err := volumedrivers.GetAllDrivers()
166
+	if err != nil {
167
+		return nil, nil, err
168
+	}
169
+
169 170
 	type vols struct {
170 171
 		vols       []volume.Volume
171 172
 		err        error