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…
	
	Add table
		Add a link
		
	
		Reference in a new issue