Skip to content

Commit

Permalink
plugins: experimental support for new plugin management
Browse files Browse the repository at this point in the history
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 docker#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 <[email protected]>
Signed-off-by: Anusha Ragunathan <[email protected]>
  • Loading branch information
Tibor Vass committed Jun 14, 2016
1 parent e5b7d36 commit f371170
Show file tree
Hide file tree
Showing 47 changed files with 1,740 additions and 58 deletions.
12 changes: 12 additions & 0 deletions api/client/plugin/cmd.go
Original file line number Diff line number Diff line change
@@ -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 changes: 36 additions & 0 deletions api/client/plugin/cmd_experimental.go
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions api/client/plugin/disable.go
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions api/client/plugin/enable.go
Original file line number Diff line number Diff line change
@@ -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 changes: 39 additions & 0 deletions api/client/plugin/inspect.go
Original file line number Diff line number Diff line change
@@ -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 changes: 51 additions & 0 deletions api/client/plugin/install.go
Original file line number Diff line number Diff line change
@@ -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 changes: 44 additions & 0 deletions api/client/plugin/list.go
Original file line number Diff line number Diff line change
@@ -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 changes: 50 additions & 0 deletions api/client/plugin/push.go
Original file line number Diff line number Diff line change
@@ -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 changes: 43 additions & 0 deletions api/client/plugin/remove.go
Original file line number Diff line number Diff line change
@@ -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 changes: 28 additions & 0 deletions api/client/plugin/set.go
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions api/server/router/plugin/backend.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit f371170

Please sign in to comment.