mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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>
This commit is contained in:
parent
e5b7d36e99
commit
f37117045c
47 changed files with 1740 additions and 58 deletions
12
api/client/plugin/cmd.go
Normal file
12
api/client/plugin/cmd.go
Normal 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) {
|
||||
}
|
36
api/client/plugin/cmd_experimental.go
Normal file
36
api/client/plugin/cmd_experimental.go
Normal 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)
|
||||
}
|
23
api/client/plugin/disable.go
Normal file
23
api/client/plugin/disable.go
Normal 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
|
||||
}
|
23
api/client/plugin/enable.go
Normal file
23
api/client/plugin/enable.go
Normal 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
|
||||
}
|
39
api/client/plugin/inspect.go
Normal file
39
api/client/plugin/inspect.go
Normal 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
|
||||
}
|
51
api/client/plugin/install.go
Normal file
51
api/client/plugin/install.go
Normal 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
44
api/client/plugin/list.go
Normal 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
50
api/client/plugin/push.go
Normal 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)
|
||||
}
|
43
api/client/plugin/remove.go
Normal file
43
api/client/plugin/remove.go
Normal 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
28
api/client/plugin/set.go
Normal 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)
|
||||
}
|
21
api/server/router/plugin/backend.go
Normal file
21
api/server/router/plugin/backend.go
Normal 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
|
||||
}
|
23
api/server/router/plugin/plugin.go
Normal file
23
api/server/router/plugin/plugin.go
Normal 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
|
||||
}
|
20
api/server/router/plugin/plugin_experimental.go
Normal file
20
api/server/router/plugin/plugin_experimental.go
Normal 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),
|
||||
}
|
||||
}
|
9
api/server/router/plugin/plugin_regular.go
Normal file
9
api/server/router/plugin/plugin_regular.go
Normal 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{}
|
103
api/server/router/plugin/plugin_routes.go
Normal file
103
api/server/router/plugin/plugin_routes.go
Normal 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)
|
||||
}
|
|
@ -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
21
cli/error.go
Normal 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()
|
||||
}
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
13
cmd/dockerd/daemon_no_plugin_support.go
Normal file
13
cmd/dockerd/daemon_no_plugin_support.go
Normal 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
|
||||
}
|
14
cmd/dockerd/daemon_plugin_support.go
Normal file
14
cmd/dockerd/daemon_plugin_support.go
Normal 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
9
cmd/dockerd/routes.go
Normal 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
|
||||
}
|
13
cmd/dockerd/routes_experimental.go
Normal file
13
cmd/dockerd/routes_experimental.go
Normal 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()))
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
139
plugin/backend.go
Normal 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
208
plugin/distribution/pull.go
Normal 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
134
plugin/distribution/push.go
Normal 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))
|
||||
}
|
16
plugin/distribution/types.go
Normal file
16
plugin/distribution/types.go
Normal 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
9
plugin/interface.go
Normal 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
23
plugin/legacy.go
Normal 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
384
plugin/manager.go
Normal 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
126
plugin/manager_linux.go
Normal 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
21
plugin/manager_windows.go
Normal 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")
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue