Merge pull request #23446 from tiborvass/plugins-experimental

Plugins: experimental support for new plugin management
This commit is contained in:
Brian Goff 2016-06-14 20:32:14 -04:00 committed by GitHub
commit 6ed921dc38
67 changed files with 2497 additions and 66 deletions

12
api/client/plugin/cmd.go Normal file
View File

@ -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) {
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
api/client/plugin/list.go Normal file
View File

@ -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
api/client/plugin/push.go Normal file
View File

@ -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)
}

View File

@ -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
api/client/plugin/set.go Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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),
}
}

View File

@ -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{}

View File

@ -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)
}

View File

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

21
cli/error.go Normal file
View File

@ -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()
}

View File

@ -262,6 +262,10 @@ func (cli *DaemonCli) start() (err error) {
<-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)
if err != nil {
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() {
routers = append(routers, network.NewRouter(d, c))
}
routers = addExperimentalRouters(routers)
s.InitRouter(utils.IsDebugEnabled(), routers...)
}

View File

@ -1,8 +1,8 @@
// +build linux
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
func notifySystem() {

View File

@ -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
}

View File

@ -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
cmd/dockerd/routes.go Normal file
View File

@ -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
}

View File

@ -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()))
}

View File

@ -486,7 +486,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
}
// Configure the volumes driver
volStore, err := configureVolumes(config, rootUID, rootGID)
volStore, err := d.configureVolumes(rootUID, rootGID)
if err != nil {
return nil, err
}
@ -768,8 +768,8 @@ func setDefaultMtu(config *Config) {
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 {
return nil, err
}
@ -777,7 +777,7 @@ func configureVolumes(config *Config, rootUID, rootGID int) (*store.VolumeStore,
if !volumedrivers.Register(volumesDriver, volumesDriver.Name()) {
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

View File

@ -23,7 +23,7 @@ func lookupPlugin(name, home string, opts []string) (Driver, error) {
if err != nil {
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) {

View File

@ -84,7 +84,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
}
// makes sure name is not empty or `scratch`
if err := validateRepoName(repoInfo.Name()); err != nil {
if err := ValidateRepoName(repoInfo.Name()); err != nil {
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 == "" {
return fmt.Errorf("Repository name can't be empty")
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
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/imdario/mergo 0.2.1

View File

@ -77,7 +77,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPluginViaJSONFile(c *check.C) {
mux := http.NewServeMux()
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)
c.Assert(err, check.IsNil)

View File

@ -203,18 +203,17 @@ func TestResponseModifierOverride(t *testing.T) {
// createTestPlugin creates a new sample authorization plugin
func createTestPlugin(t *testing.T) *authorizationPlugin {
plugin := &plugins.Plugin{Name: "authz"}
pwd, err := os.Getwd()
if err != nil {
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 {
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

View File

@ -35,7 +35,7 @@ func NewPlugins(names []string) []Plugin {
// authorizationPlugin is an internal adapter to docker plugin system
type authorizationPlugin struct {
plugin *plugins.Plugin
plugin *plugins.Client
name string
once sync.Once
}
@ -54,7 +54,7 @@ func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error)
}
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
}
@ -67,7 +67,7 @@ func (a *authorizationPlugin) AuthZResponse(authReq *Request) (*Response, error)
}
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
}
@ -80,7 +80,12 @@ func (a *authorizationPlugin) initPlugin() error {
var err error
a.once.Do(func() {
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

View File

@ -20,14 +20,16 @@ const (
)
// 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{}
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)
if err != nil {

View File

@ -31,7 +31,7 @@ func teardownRemotePluginServer() {
}
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)
if err == nil {
t.Fatal("Unexpected successful connection")
@ -55,7 +55,7 @@ func TestEchoInputOutput(t *testing.T) {
io.Copy(w, r.Body)
})
c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
c, _ := NewClient(addr, &tlsconfig.Options{InsecureSkipVerify: true})
var output Manifest
err := c.Call("Test.Echo", m, &output)
if err != nil {

View File

@ -64,7 +64,7 @@ func (l *localRegistry) Plugin(name string) (*Plugin, error) {
for _, p := range socketpaths {
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 newLocalPlugin(name, addr), nil
return NewLocalPlugin(name, addr), nil
}
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 {
return nil, err
}
p.Name = name
p.name = name
if len(p.TLSConfig.CAFile) == 0 {
p.TLSConfig.InsecureSkipVerify = true
}

View File

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

View File

@ -45,7 +45,7 @@ func TestLocalSocket(t *testing.T) {
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)
}

View File

@ -55,13 +55,13 @@ type Manifest struct {
// Plugin is the definition of a docker plugin.
type Plugin struct {
// Name of the plugin
Name string `json:"-"`
name string
// Address of the plugin
Addr string
// TLS configuration of the plugin
TLSConfig tlsconfig.Options
TLSConfig *tlsconfig.Options
// Client attached to the plugin
Client *Client `json:"-"`
client *Client
// Manifest of the plugin (see above)
Manifest *Manifest `json:"-"`
@ -73,11 +73,23 @@ type Plugin struct {
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{
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{}),
}
}
@ -102,10 +114,10 @@ func (p *Plugin) activateWithLock() error {
if err != nil {
return err
}
p.Client = c
p.client = c
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
}
@ -116,7 +128,7 @@ func (p *Plugin) activateWithLock() error {
if !handled {
continue
}
handler(p.Name, p.Client)
handler(p.name, p.client)
}
return nil
}

139
plugin/backend.go Normal file
View File

@ -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
plugin/distribution/pull.go Normal file
View File

@ -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
plugin/distribution/push.go Normal file
View File

@ -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))
}

View File

@ -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
plugin/interface.go Normal file
View File

@ -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
plugin/legacy.go Normal file
View File

@ -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
plugin/manager.go Normal file
View File

@ -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
plugin/manager_linux.go Normal file
View File

@ -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
plugin/manager_windows.go Normal file
View File

@ -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")
}

View File

@ -171,3 +171,18 @@ func IsErrTaskNotFound(err error) bool {
_, ok := err.(taskNotFoundError)
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
}

View File

@ -4,18 +4,17 @@ import (
"io"
"time"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/registry"
"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
CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) 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)
VolumeRemove(ctx context.Context, volumeID string) error
}
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -7,7 +7,7 @@ import (
"sync"
"github.com/docker/docker/pkg/locker"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/plugin"
"github.com/docker/docker/volume"
)
@ -88,10 +88,10 @@ func Unregister(name string) bool {
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
// 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)
defer drivers.driverLock.Unlock(name)
@ -102,7 +102,7 @@ func Lookup(name string) (volume.Driver, error) {
return ext, nil
}
pl, err := plugins.Get(name, extName)
p, err := plugin.LookupWithCapability(name, extName)
if err != nil {
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
}
d := NewVolumeDriver(name, pl.Client)
d := NewVolumeDriver(name, p.Client())
if err := validateDriver(d); err != nil {
return nil, err
}
@ -136,7 +136,7 @@ func GetDriver(name string) (volume.Driver, error) {
if name == "" {
name = volume.DefaultDriverName
}
return Lookup(name)
return lookup(name)
}
// GetDriverList returns list of volume drivers registered.
@ -153,9 +153,9 @@ func GetDriverList() []string {
// GetAllDrivers lists all the registered drivers
func GetAllDrivers() ([]volume.Driver, error) {
plugins, err := plugins.GetAll(extName)
plugins, err := plugin.FindWithCapability(extName)
if err != nil {
return nil, err
return nil, fmt.Errorf("error listing plugins: %v", err)
}
var ds []volume.Driver
@ -167,13 +167,14 @@ func GetAllDrivers() ([]volume.Driver, error) {
}
for _, p := range plugins {
ext, ok := drivers.extensions[p.Name]
name := p.Name()
ext, ok := drivers.extensions[name]
if ok {
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)
}
return ds, nil

View File

@ -58,7 +58,7 @@ func TestVolumeRequestError(t *testing.T) {
})
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 {
t.Fatal(err)
}

View File

@ -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.
func (s *VolumeStore) list() ([]volume.Volume, []string, error) {
drivers, err := volumedrivers.GetAllDrivers()
if err != nil {
return nil, nil, err
}
var (
ls []volume.Volume
warnings []string
)
drivers, err := volumedrivers.GetAllDrivers()
if err != nil {
return nil, nil, err
}
type vols struct {
vols []volume.Volume
err error