Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,29 @@ |
| 0 |
+package middleware |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "net/http" |
|
| 4 |
+ |
|
| 5 |
+ "golang.org/x/net/context" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// ExperimentalMiddleware is a the middleware in charge of adding the |
|
| 9 |
+// 'Docker-Experimental' header to every outgoing request |
|
| 10 |
+type ExperimentalMiddleware struct {
|
|
| 11 |
+ experimental string |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+// NewExperimentalMiddleware creates a new ExperimentalMiddleware |
|
| 15 |
+func NewExperimentalMiddleware(experimentalEnabled bool) ExperimentalMiddleware {
|
|
| 16 |
+ if experimentalEnabled {
|
|
| 17 |
+ return ExperimentalMiddleware{"true"}
|
|
| 18 |
+ } |
|
| 19 |
+ return ExperimentalMiddleware{"false"}
|
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// WrapHandler returns a new handler function wrapping the previous one in the request chain. |
|
| 23 |
+func (e ExperimentalMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 24 |
+ return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 25 |
+ w.Header().Set("Docker-Experimental", e.experimental)
|
|
| 26 |
+ return handler(ctx, w, r, vars) |
|
| 27 |
+ } |
|
| 28 |
+} |
| ... | ... |
@@ -26,3 +26,11 @@ func NewRouter(b Backend, decoder httputils.ContainerDecoder) router.Router {
|
| 26 | 26 |
func (r *checkpointRouter) Routes() []router.Route {
|
| 27 | 27 |
return r.routes |
| 28 | 28 |
} |
| 29 |
+ |
|
| 30 |
+func (r *checkpointRouter) initRoutes() {
|
|
| 31 |
+ r.routes = []router.Route{
|
|
| 32 |
+ router.NewGetRoute("/containers/{name:.*}/checkpoints", r.getContainerCheckpoints),
|
|
| 33 |
+ router.NewPostRoute("/containers/{name:.*}/checkpoints", r.postContainerCheckpoint),
|
|
| 34 |
+ router.NewDeleteRoute("/containers/{name}/checkpoints/{checkpoint}", r.deleteContainerCheckpoint),
|
|
| 35 |
+ } |
|
| 36 |
+} |
| 29 | 37 |
deleted file mode 100644 |
| ... | ... |
@@ -1,15 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package checkpoint |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "github.com/docker/docker/api/server/router" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-func (r *checkpointRouter) initRoutes() {
|
|
| 10 |
- r.routes = []router.Route{
|
|
| 11 |
- router.NewGetRoute("/containers/{name:.*}/checkpoints", r.getContainerCheckpoints),
|
|
| 12 |
- router.NewPostRoute("/containers/{name:.*}/checkpoints", r.postContainerCheckpoint),
|
|
| 13 |
- router.NewDeleteRoute("/containers/{name}/checkpoints/{checkpoint}", r.deleteContainerCheckpoint),
|
|
| 14 |
- } |
|
| 15 |
-} |
| ... | ... |
@@ -21,3 +21,16 @@ func NewRouter(b Backend) router.Router {
|
| 21 | 21 |
func (r *pluginRouter) Routes() []router.Route {
|
| 22 | 22 |
return r.routes |
| 23 | 23 |
} |
| 24 |
+ |
|
| 25 |
+func (r *pluginRouter) initRoutes() {
|
|
| 26 |
+ r.routes = []router.Route{
|
|
| 27 |
+ router.NewGetRoute("/plugins", r.listPlugins),
|
|
| 28 |
+ router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin),
|
|
| 29 |
+ router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
|
|
| 30 |
+ router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
|
|
| 31 |
+ router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),
|
|
| 32 |
+ router.NewPostRoute("/plugins/pull", r.pullPlugin),
|
|
| 33 |
+ router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin),
|
|
| 34 |
+ router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin),
|
|
| 35 |
+ } |
|
| 36 |
+} |
| 24 | 37 |
deleted file mode 100644 |
| ... | ... |
@@ -1,20 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package plugin |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "github.com/docker/docker/api/server/router" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-func (r *pluginRouter) initRoutes() {
|
|
| 10 |
- r.routes = []router.Route{
|
|
| 11 |
- router.NewGetRoute("/plugins", r.listPlugins),
|
|
| 12 |
- router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin),
|
|
| 13 |
- router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin),
|
|
| 14 |
- router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH?
|
|
| 15 |
- router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin),
|
|
| 16 |
- router.NewPostRoute("/plugins/pull", r.pullPlugin),
|
|
| 17 |
- router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin),
|
|
| 18 |
- router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin),
|
|
| 19 |
- } |
|
| 20 |
-} |
| ... | ... |
@@ -1,13 +1,27 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 | 1 |
package checkpoint |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/cli" |
|
| 6 | 7 |
"github.com/docker/docker/cli/command" |
| 7 | 8 |
"github.com/spf13/cobra" |
| 8 | 9 |
) |
| 9 | 10 |
|
| 10 | 11 |
// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental) |
| 11 | 12 |
func NewCheckpointCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 12 |
- return &cobra.Command{}
|
|
| 13 |
+ cmd := &cobra.Command{
|
|
| 14 |
+ Use: "checkpoint", |
|
| 15 |
+ Short: "Manage checkpoints", |
|
| 16 |
+ Args: cli.NoArgs, |
|
| 17 |
+ Run: func(cmd *cobra.Command, args []string) {
|
|
| 18 |
+ fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ }, |
|
| 20 |
+ } |
|
| 21 |
+ cmd.AddCommand( |
|
| 22 |
+ newCreateCommand(dockerCli), |
|
| 23 |
+ newListCommand(dockerCli), |
|
| 24 |
+ newRemoveCommand(dockerCli), |
|
| 25 |
+ ) |
|
| 26 |
+ return cmd |
|
| 13 | 27 |
} |
| 14 | 28 |
deleted file mode 100644 |
| ... | ... |
@@ -1,30 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package checkpoint |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "fmt" |
|
| 7 |
- |
|
| 8 |
- "github.com/spf13/cobra" |
|
| 9 |
- |
|
| 10 |
- "github.com/docker/docker/cli" |
|
| 11 |
- "github.com/docker/docker/cli/command" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental) |
|
| 15 |
-func NewCheckpointCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 16 |
- cmd := &cobra.Command{
|
|
| 17 |
- Use: "checkpoint", |
|
| 18 |
- Short: "Manage checkpoints", |
|
| 19 |
- Args: cli.NoArgs, |
|
| 20 |
- Run: func(cmd *cobra.Command, args []string) {
|
|
| 21 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 22 |
- }, |
|
| 23 |
- } |
|
| 24 |
- cmd.AddCommand( |
|
| 25 |
- newCreateCommand(dockerCli), |
|
| 26 |
- newListCommand(dockerCli), |
|
| 27 |
- newRemoveCommand(dockerCli), |
|
| 28 |
- ) |
|
| 29 |
- return cmd |
|
| 30 |
-} |
| ... | ... |
@@ -19,6 +19,7 @@ import ( |
| 19 | 19 |
dopts "github.com/docker/docker/opts" |
| 20 | 20 |
"github.com/docker/go-connections/sockets" |
| 21 | 21 |
"github.com/docker/go-connections/tlsconfig" |
| 22 |
+ "golang.org/x/net/context" |
|
| 22 | 23 |
) |
| 23 | 24 |
|
| 24 | 25 |
// Streams is an interface which exposes the standard input and output streams |
| ... | ... |
@@ -31,12 +32,27 @@ type Streams interface {
|
| 31 | 31 |
// DockerCli represents the docker command line client. |
| 32 | 32 |
// Instances of the client can be returned from NewDockerCli. |
| 33 | 33 |
type DockerCli struct {
|
| 34 |
- configFile *configfile.ConfigFile |
|
| 35 |
- in *InStream |
|
| 36 |
- out *OutStream |
|
| 37 |
- err io.Writer |
|
| 38 |
- keyFile string |
|
| 39 |
- client client.APIClient |
|
| 34 |
+ configFile *configfile.ConfigFile |
|
| 35 |
+ in *InStream |
|
| 36 |
+ out *OutStream |
|
| 37 |
+ err io.Writer |
|
| 38 |
+ keyFile string |
|
| 39 |
+ client client.APIClient |
|
| 40 |
+ hasExperimental *bool |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// HasExperimental returns true if experimental features are accessible |
|
| 44 |
+func (cli *DockerCli) HasExperimental() bool {
|
|
| 45 |
+ if cli.hasExperimental == nil {
|
|
| 46 |
+ if cli.client == nil {
|
|
| 47 |
+ cli.Initialize(cliflags.NewClientOptions()) |
|
| 48 |
+ } |
|
| 49 |
+ enabled := false |
|
| 50 |
+ cli.hasExperimental = &enabled |
|
| 51 |
+ enabled, _ = cli.client.Ping(context.Background()) |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ return *cli.hasExperimental |
|
| 40 | 55 |
} |
| 41 | 56 |
|
| 42 | 57 |
// Client returns the APIClient |
| ... | ... |
@@ -24,8 +24,6 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
|
| 24 | 24 |
cmd.AddCommand( |
| 25 | 25 |
node.NewNodeCommand(dockerCli), |
| 26 | 26 |
service.NewServiceCommand(dockerCli), |
| 27 |
- stack.NewStackCommand(dockerCli), |
|
| 28 |
- stack.NewTopLevelDeployCommand(dockerCli), |
|
| 29 | 27 |
swarm.NewSwarmCommand(dockerCli), |
| 30 | 28 |
container.NewContainerCommand(dockerCli), |
| 31 | 29 |
image.NewImageCommand(dockerCli), |
| ... | ... |
@@ -72,9 +70,17 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
|
| 72 | 72 |
hide(image.NewSaveCommand(dockerCli)), |
| 73 | 73 |
hide(image.NewTagCommand(dockerCli)), |
| 74 | 74 |
hide(system.NewInspectCommand(dockerCli)), |
| 75 |
- checkpoint.NewCheckpointCommand(dockerCli), |
|
| 76 |
- plugin.NewPluginCommand(dockerCli), |
|
| 77 | 75 |
) |
| 76 |
+ |
|
| 77 |
+ if dockerCli.HasExperimental() {
|
|
| 78 |
+ cmd.AddCommand( |
|
| 79 |
+ stack.NewStackCommand(dockerCli), |
|
| 80 |
+ stack.NewTopLevelDeployCommand(dockerCli), |
|
| 81 |
+ checkpoint.NewCheckpointCommand(dockerCli), |
|
| 82 |
+ plugin.NewPluginCommand(dockerCli), |
|
| 83 |
+ ) |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 78 | 86 |
} |
| 79 | 87 |
|
| 80 | 88 |
func hide(cmd *cobra.Command) *cobra.Command {
|
| ... | ... |
@@ -44,7 +44,9 @@ func NewStartCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 44 | 44 |
flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN") |
| 45 | 45 |
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") |
| 46 | 46 |
|
| 47 |
- addExperimentalStartFlags(flags, &opts) |
|
| 47 |
+ if dockerCli.HasExperimental() {
|
|
| 48 |
+ flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") |
|
| 49 |
+ } |
|
| 48 | 50 |
|
| 49 | 51 |
return cmd |
| 50 | 52 |
} |
| 9 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,9 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package container |
|
| 4 |
- |
|
| 5 |
-import "github.com/spf13/pflag" |
|
| 6 |
- |
|
| 7 |
-func addExperimentalStartFlags(flags *pflag.FlagSet, opts *startOptions) {
|
|
| 8 |
- flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") |
|
| 9 |
-} |
| ... | ... |
@@ -1,13 +1,33 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 | 1 |
package plugin |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/cli" |
|
| 6 | 7 |
"github.com/docker/docker/cli/command" |
| 7 | 8 |
"github.com/spf13/cobra" |
| 8 | 9 |
) |
| 9 | 10 |
|
| 10 | 11 |
// NewPluginCommand returns a cobra command for `plugin` subcommands |
| 11 | 12 |
func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 12 |
- return &cobra.Command{}
|
|
| 13 |
+ cmd := &cobra.Command{
|
|
| 14 |
+ Use: "plugin", |
|
| 15 |
+ Short: "Manage plugins", |
|
| 16 |
+ Args: cli.NoArgs, |
|
| 17 |
+ Run: func(cmd *cobra.Command, args []string) {
|
|
| 18 |
+ fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ }, |
|
| 20 |
+ } |
|
| 21 |
+ |
|
| 22 |
+ cmd.AddCommand( |
|
| 23 |
+ newDisableCommand(dockerCli), |
|
| 24 |
+ newEnableCommand(dockerCli), |
|
| 25 |
+ newInspectCommand(dockerCli), |
|
| 26 |
+ newInstallCommand(dockerCli), |
|
| 27 |
+ newListCommand(dockerCli), |
|
| 28 |
+ newRemoveCommand(dockerCli), |
|
| 29 |
+ newSetCommand(dockerCli), |
|
| 30 |
+ newPushCommand(dockerCli), |
|
| 31 |
+ ) |
|
| 32 |
+ return cmd |
|
| 13 | 33 |
} |
| 14 | 34 |
deleted file mode 100644 |
| ... | ... |
@@ -1,35 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package plugin |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "fmt" |
|
| 7 |
- |
|
| 8 |
- "github.com/docker/docker/cli" |
|
| 9 |
- "github.com/docker/docker/cli/command" |
|
| 10 |
- "github.com/spf13/cobra" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-// NewPluginCommand returns a cobra command for `plugin` subcommands |
|
| 14 |
-func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 15 |
- cmd := &cobra.Command{
|
|
| 16 |
- Use: "plugin", |
|
| 17 |
- Short: "Manage plugins", |
|
| 18 |
- Args: cli.NoArgs, |
|
| 19 |
- Run: func(cmd *cobra.Command, args []string) {
|
|
| 20 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 21 |
- }, |
|
| 22 |
- } |
|
| 23 |
- |
|
| 24 |
- cmd.AddCommand( |
|
| 25 |
- newDisableCommand(dockerCli), |
|
| 26 |
- newEnableCommand(dockerCli), |
|
| 27 |
- newInspectCommand(dockerCli), |
|
| 28 |
- newInstallCommand(dockerCli), |
|
| 29 |
- newListCommand(dockerCli), |
|
| 30 |
- newRemoveCommand(dockerCli), |
|
| 31 |
- newSetCommand(dockerCli), |
|
| 32 |
- newPushCommand(dockerCli), |
|
| 33 |
- ) |
|
| 34 |
- return cmd |
|
| 35 |
-} |
| ... | ... |
@@ -1,18 +1,38 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 | 1 |
package stack |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/cli" |
|
| 6 | 7 |
"github.com/docker/docker/cli/command" |
| 7 | 8 |
"github.com/spf13/cobra" |
| 8 | 9 |
) |
| 9 | 10 |
|
| 10 |
-// NewStackCommand returns no command |
|
| 11 |
+// NewStackCommand returns a cobra command for `stack` subcommands |
|
| 11 | 12 |
func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 12 |
- return &cobra.Command{}
|
|
| 13 |
+ cmd := &cobra.Command{
|
|
| 14 |
+ Use: "stack", |
|
| 15 |
+ Short: "Manage Docker stacks", |
|
| 16 |
+ Args: cli.NoArgs, |
|
| 17 |
+ Run: func(cmd *cobra.Command, args []string) {
|
|
| 18 |
+ fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 19 |
+ }, |
|
| 20 |
+ } |
|
| 21 |
+ cmd.AddCommand( |
|
| 22 |
+ newConfigCommand(dockerCli), |
|
| 23 |
+ newDeployCommand(dockerCli), |
|
| 24 |
+ newListCommand(dockerCli), |
|
| 25 |
+ newRemoveCommand(dockerCli), |
|
| 26 |
+ newServicesCommand(dockerCli), |
|
| 27 |
+ newPsCommand(dockerCli), |
|
| 28 |
+ ) |
|
| 29 |
+ return cmd |
|
| 13 | 30 |
} |
| 14 | 31 |
|
| 15 |
-// NewTopLevelDeployCommand returns no command |
|
| 32 |
+// NewTopLevelDeployCommand returns a command for `docker deploy` |
|
| 16 | 33 |
func NewTopLevelDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 17 |
- return &cobra.Command{}
|
|
| 34 |
+ cmd := newDeployCommand(dockerCli) |
|
| 35 |
+ // Remove the aliases at the top level |
|
| 36 |
+ cmd.Aliases = []string{}
|
|
| 37 |
+ return cmd |
|
| 18 | 38 |
} |
| 19 | 39 |
deleted file mode 100644 |
| ... | ... |
@@ -1,40 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package stack |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "fmt" |
|
| 7 |
- |
|
| 8 |
- "github.com/docker/docker/cli" |
|
| 9 |
- "github.com/docker/docker/cli/command" |
|
| 10 |
- "github.com/spf13/cobra" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-// NewStackCommand returns a cobra command for `stack` subcommands |
|
| 14 |
-func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 15 |
- cmd := &cobra.Command{
|
|
| 16 |
- Use: "stack", |
|
| 17 |
- Short: "Manage Docker stacks", |
|
| 18 |
- Args: cli.NoArgs, |
|
| 19 |
- Run: func(cmd *cobra.Command, args []string) {
|
|
| 20 |
- fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) |
|
| 21 |
- }, |
|
| 22 |
- } |
|
| 23 |
- cmd.AddCommand( |
|
| 24 |
- newConfigCommand(dockerCli), |
|
| 25 |
- newDeployCommand(dockerCli), |
|
| 26 |
- newListCommand(dockerCli), |
|
| 27 |
- newRemoveCommand(dockerCli), |
|
| 28 |
- newServicesCommand(dockerCli), |
|
| 29 |
- newPsCommand(dockerCli), |
|
| 30 |
- ) |
|
| 31 |
- return cmd |
|
| 32 |
-} |
|
| 33 |
- |
|
| 34 |
-// NewTopLevelDeployCommand returns a command for `docker deploy` |
|
| 35 |
-func NewTopLevelDeployCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|
| 36 |
- cmd := newDeployCommand(dockerCli) |
|
| 37 |
- // Remove the aliases at the top level |
|
| 38 |
- cmd.Aliases = []string{}
|
|
| 39 |
- return cmd |
|
| 40 |
-} |
| ... | ... |
@@ -225,7 +225,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
|
| 225 | 225 |
} |
| 226 | 226 |
} |
| 227 | 227 |
|
| 228 |
- ioutils.FprintfIfTrue(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild) |
|
| 228 |
+ fmt.Fprintf(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild) |
|
| 229 | 229 |
if info.ClusterStore != "" {
|
| 230 | 230 |
fmt.Fprintf(dockerCli.Out(), "Cluster Store: %s\n", info.ClusterStore) |
| 231 | 231 |
} |
| ... | ... |
@@ -10,7 +10,6 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/cli" |
| 11 | 11 |
"github.com/docker/docker/cli/command" |
| 12 | 12 |
"github.com/docker/docker/dockerversion" |
| 13 |
- "github.com/docker/docker/utils" |
|
| 14 | 13 |
"github.com/docker/docker/utils/templates" |
| 15 | 14 |
"github.com/spf13/cobra" |
| 16 | 15 |
) |
| ... | ... |
@@ -21,8 +20,7 @@ var versionTemplate = `Client: |
| 21 | 21 |
Go version: {{.Client.GoVersion}}
|
| 22 | 22 |
Git commit: {{.Client.GitCommit}}
|
| 23 | 23 |
Built: {{.Client.BuildTime}}
|
| 24 |
- OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}}
|
|
| 25 |
- Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}}
|
|
| 24 |
+ OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .ServerOK}}
|
|
| 26 | 25 |
|
| 27 | 26 |
Server: |
| 28 | 27 |
Version: {{.Server.Version}}
|
| ... | ... |
@@ -30,8 +28,8 @@ Server: |
| 30 | 30 |
Go version: {{.Server.GoVersion}}
|
| 31 | 31 |
Git commit: {{.Server.GitCommit}}
|
| 32 | 32 |
Built: {{.Server.BuildTime}}
|
| 33 |
- OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
|
|
| 34 |
- Experimental: {{.Server.Experimental}}{{end}}{{end}}`
|
|
| 33 |
+ OS/Arch: {{.Server.Os}}/{{.Server.Arch}}
|
|
| 34 |
+ Experimental: {{.Server.Experimental}}{{end}}`
|
|
| 35 | 35 |
|
| 36 | 36 |
type versionOptions struct {
|
| 37 | 37 |
format string |
| ... | ... |
@@ -73,14 +71,13 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
|
| 73 | 73 |
|
| 74 | 74 |
vd := types.VersionResponse{
|
| 75 | 75 |
Client: &types.Version{
|
| 76 |
- Version: dockerversion.Version, |
|
| 77 |
- APIVersion: dockerCli.Client().ClientVersion(), |
|
| 78 |
- GoVersion: runtime.Version(), |
|
| 79 |
- GitCommit: dockerversion.GitCommit, |
|
| 80 |
- BuildTime: dockerversion.BuildTime, |
|
| 81 |
- Os: runtime.GOOS, |
|
| 82 |
- Arch: runtime.GOARCH, |
|
| 83 |
- Experimental: utils.ExperimentalBuild(), |
|
| 76 |
+ Version: dockerversion.Version, |
|
| 77 |
+ APIVersion: dockerCli.Client().ClientVersion(), |
|
| 78 |
+ GoVersion: runtime.Version(), |
|
| 79 |
+ GitCommit: dockerversion.GitCommit, |
|
| 80 |
+ BuildTime: dockerversion.BuildTime, |
|
| 81 |
+ Os: runtime.GOOS, |
|
| 82 |
+ Arch: runtime.GOARCH, |
|
| 84 | 83 |
}, |
| 85 | 84 |
} |
| 86 | 85 |
|
| ... | ... |
@@ -127,6 +127,7 @@ type SystemAPIClient interface {
|
| 127 | 127 |
Info(ctx context.Context) (types.Info, error) |
| 128 | 128 |
RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) |
| 129 | 129 |
DiskUsage(ctx context.Context) (types.DiskUsage, error) |
| 130 |
+ Ping(ctx context.Context) (bool, error) |
|
| 130 | 131 |
} |
| 131 | 132 |
|
| 132 | 133 |
// VolumeAPIClient defines API client methods for the volumes |
| ... | ... |
@@ -1,5 +1,3 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 | 1 |
package client |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| ... | ... |
@@ -7,9 +5,7 @@ import ( |
| 7 | 7 |
"golang.org/x/net/context" |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 |
-// APIClient is an interface that clients that talk with a docker server must implement. |
|
| 11 |
-type APIClient interface {
|
|
| 12 |
- CommonAPIClient |
|
| 10 |
+type apiClientExperimental interface {
|
|
| 13 | 11 |
CheckpointAPIClient |
| 14 | 12 |
PluginAPIClient |
| 15 | 13 |
} |
| ... | ... |
@@ -32,6 +28,3 @@ type PluginAPIClient interface {
|
| 32 | 32 |
PluginSet(ctx context.Context, name string, args []string) error |
| 33 | 33 |
PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) |
| 34 | 34 |
} |
| 35 |
- |
|
| 36 |
-// Ensure that Client always implements APIClient. |
|
| 37 |
-var _ APIClient = &Client{}
|
| ... | ... |
@@ -1,10 +1,9 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 | 1 |
package client |
| 4 | 2 |
|
| 5 | 3 |
// APIClient is an interface that clients that talk with a docker server must implement. |
| 6 | 4 |
type APIClient interface {
|
| 7 | 5 |
CommonAPIClient |
| 6 |
+ apiClientExperimental |
|
| 8 | 7 |
} |
| 9 | 8 |
|
| 10 | 9 |
// Ensure that Client always implements APIClient. |
| 11 | 10 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,19 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import "golang.org/x/net/context" |
|
| 3 |
+ |
|
| 4 |
+// Ping pings the server and return the value of the "Docker-Experimental" header |
|
| 5 |
+func (cli *Client) Ping(ctx context.Context) (bool, error) {
|
|
| 6 |
+ serverResp, err := cli.get(ctx, "/_ping", nil, nil) |
|
| 7 |
+ if err != nil {
|
|
| 8 |
+ return false, err |
|
| 9 |
+ } |
|
| 10 |
+ defer ensureReaderClosed(serverResp) |
|
| 11 |
+ |
|
| 12 |
+ exp := serverResp.header.Get("Docker-Experimental")
|
|
| 13 |
+ if exp != "true" {
|
|
| 14 |
+ return false, nil |
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ return true, nil |
|
| 18 |
+} |
| ... | ... |
@@ -91,11 +91,7 @@ func main() {
|
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 | 93 |
func showVersion() {
|
| 94 |
- if utils.ExperimentalBuild() {
|
|
| 95 |
- fmt.Printf("Docker version %s, build %s, experimental\n", dockerversion.Version, dockerversion.GitCommit)
|
|
| 96 |
- } else {
|
|
| 97 |
- fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit)
|
|
| 98 |
- } |
|
| 94 |
+ fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit)
|
|
| 99 | 95 |
} |
| 100 | 96 |
|
| 101 | 97 |
func dockerPreRun(opts *cliflags.ClientOptions) {
|
| ... | ... |
@@ -129,7 +129,7 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
|
| 129 | 129 |
utils.EnableDebug() |
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 |
- if utils.ExperimentalBuild() {
|
|
| 132 |
+ if cli.Config.Experimental {
|
|
| 133 | 133 |
logrus.Warn("Running experimental build")
|
| 134 | 134 |
} |
| 135 | 135 |
|
| ... | ... |
@@ -435,6 +435,9 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
|
| 435 | 435 |
func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config) {
|
| 436 | 436 |
v := cfg.Version |
| 437 | 437 |
|
| 438 |
+ exp := middleware.NewExperimentalMiddleware(cli.d.HasExperimental()) |
|
| 439 |
+ s.UseMiddleware(exp) |
|
| 440 |
+ |
|
| 438 | 441 |
vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion) |
| 439 | 442 |
s.UseMiddleware(vm) |
| 440 | 443 |
|
| ... | ... |
@@ -13,7 +13,6 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/dockerversion" |
| 14 | 14 |
"github.com/docker/docker/pkg/reexec" |
| 15 | 15 |
"github.com/docker/docker/pkg/term" |
| 16 |
- "github.com/docker/docker/utils" |
|
| 17 | 16 |
"github.com/spf13/cobra" |
| 18 | 17 |
"github.com/spf13/pflag" |
| 19 | 18 |
) |
| ... | ... |
@@ -85,11 +84,7 @@ func runDaemon(opts daemonOptions) error {
|
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 | 87 |
func showVersion() {
|
| 88 |
- if utils.ExperimentalBuild() {
|
|
| 89 |
- fmt.Printf("Docker version %s, build %s, experimental\n", dockerversion.Version, dockerversion.GitCommit)
|
|
| 90 |
- } else {
|
|
| 91 |
- fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit)
|
|
| 92 |
- } |
|
| 88 |
+ fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit)
|
|
| 93 | 89 |
} |
| 94 | 90 |
|
| 95 | 91 |
func main() {
|
| 96 | 92 |
deleted file mode 100644 |
| ... | ... |
@@ -1,13 +0,0 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 |
-package main |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "github.com/docker/docker/api/server/httputils" |
|
| 7 |
- "github.com/docker/docker/api/server/router" |
|
| 8 |
- "github.com/docker/docker/daemon" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-func addExperimentalRouters(routers []router.Router, d *daemon.Daemon, decoder httputils.ContainerDecoder) []router.Router {
|
|
| 12 |
- return routers |
|
| 13 |
-} |
| ... | ... |
@@ -1,5 +1,3 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 | 1 |
package main |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| ... | ... |
@@ -12,5 +10,8 @@ import ( |
| 12 | 12 |
) |
| 13 | 13 |
|
| 14 | 14 |
func addExperimentalRouters(routers []router.Router, d *daemon.Daemon, decoder httputils.ContainerDecoder) []router.Router {
|
| 15 |
+ if !d.HasExperimental() {
|
|
| 16 |
+ return []router.Router{}
|
|
| 17 |
+ } |
|
| 15 | 18 |
return append(routers, checkpointrouter.NewRouter(d, decoder), pluginrouter.NewRouter(plugin.GetManager())) |
| 16 | 19 |
} |
| ... | ... |
@@ -153,6 +153,8 @@ type CommonConfig struct {
|
| 153 | 153 |
|
| 154 | 154 |
reloadLock sync.Mutex |
| 155 | 155 |
valuesSet map[string]interface{}
|
| 156 |
+ |
|
| 157 |
+ Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not |
|
| 156 | 158 |
} |
| 157 | 159 |
|
| 158 | 160 |
// InstallCommonFlags adds flags to the pflag.FlagSet to configure the daemon |
| ... | ... |
@@ -187,6 +189,7 @@ func (config *Config) InstallCommonFlags(flags *pflag.FlagSet) {
|
| 187 | 187 |
flags.IntVar(&config.ShutdownTimeout, "shutdown-timeout", defaultShutdownTimeout, "Set the default shutdown timeout") |
| 188 | 188 |
|
| 189 | 189 |
flags.StringVar(&config.SwarmDefaultAdvertiseAddr, "swarm-default-advertise-addr", "", "Set default address or interface for swarm advertised address") |
| 190 |
+ flags.BoolVar(&config.Experimental, "experimental", false, "Enable experimental features") |
|
| 190 | 191 |
|
| 191 | 192 |
config.MaxConcurrentDownloads = &maxConcurrentDownloads |
| 192 | 193 |
config.MaxConcurrentUploads = &maxConcurrentUploads |
| ... | ... |
@@ -104,6 +104,14 @@ type Daemon struct {
|
| 104 | 104 |
clusterProvider cluster.Provider |
| 105 | 105 |
} |
| 106 | 106 |
|
| 107 |
+// HasExperimental returns whether the experimental features of the daemon are enabled or not |
|
| 108 |
+func (daemon *Daemon) HasExperimental() bool {
|
|
| 109 |
+ if daemon.configStore != nil && daemon.configStore.Experimental {
|
|
| 110 |
+ return true |
|
| 111 |
+ } |
|
| 112 |
+ return false |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 107 | 115 |
func (daemon *Daemon) restore() error {
|
| 108 | 116 |
var ( |
| 109 | 117 |
currentDriver = daemon.GraphDriverName() |
| ... | ... |
@@ -667,7 +675,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot |
| 667 | 667 |
} |
| 668 | 668 |
|
| 669 | 669 |
// Plugin system initialization should happen before restore. Do not change order. |
| 670 |
- if err := pluginInit(d, config, containerdRemote); err != nil {
|
|
| 670 |
+ if err := d.pluginInit(config, containerdRemote); err != nil {
|
|
| 671 | 671 |
return nil, err |
| 672 | 672 |
} |
| 673 | 673 |
|
| ... | ... |
@@ -775,7 +783,7 @@ func (daemon *Daemon) Shutdown() error {
|
| 775 | 775 |
} |
| 776 | 776 |
|
| 777 | 777 |
// Shutdown plugins after containers. Dont change the order. |
| 778 |
- pluginShutdown() |
|
| 778 |
+ daemon.pluginShutdown() |
|
| 779 | 779 |
|
| 780 | 780 |
// trigger libnetwork Stop only if it's initialized |
| 781 | 781 |
if daemon.netController != nil {
|
| ... | ... |
@@ -1022,7 +1030,6 @@ func (daemon *Daemon) Reload(config *Config) (err error) {
|
| 1022 | 1022 |
if err := daemon.containerdRemote.UpdateOptions(libcontainerd.WithLiveRestore(config.LiveRestoreEnabled)); err != nil {
|
| 1023 | 1023 |
return err |
| 1024 | 1024 |
} |
| 1025 |
- |
|
| 1026 | 1025 |
} |
| 1027 | 1026 |
|
| 1028 | 1027 |
// If no value is set for max-concurrent-downloads we assume it is the default value |
| ... | ... |
@@ -1,5 +1,3 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 | 1 |
package daemon |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| ... | ... |
@@ -12,11 +10,17 @@ func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container. |
| 12 | 12 |
return nil, nil |
| 13 | 13 |
} |
| 14 | 14 |
|
| 15 |
-func pluginInit(d *Daemon, cfg *Config, remote libcontainerd.Remote) error {
|
|
| 16 |
- return plugin.Init(cfg.Root, d.PluginStore, remote, d.RegistryService, cfg.LiveRestoreEnabled, d.LogPluginEvent) |
|
| 15 |
+func (daemon *Daemon) pluginInit(cfg *Config, remote libcontainerd.Remote) error {
|
|
| 16 |
+ if !daemon.HasExperimental() {
|
|
| 17 |
+ return nil |
|
| 18 |
+ } |
|
| 19 |
+ return plugin.Init(cfg.Root, daemon.PluginStore, remote, daemon.RegistryService, cfg.LiveRestoreEnabled, daemon.LogPluginEvent) |
|
| 17 | 20 |
} |
| 18 | 21 |
|
| 19 |
-func pluginShutdown() {
|
|
| 22 |
+func (daemon *Daemon) pluginShutdown() {
|
|
| 23 |
+ if !daemon.HasExperimental() {
|
|
| 24 |
+ return |
|
| 25 |
+ } |
|
| 20 | 26 |
manager := plugin.GetManager() |
| 21 | 27 |
// Check for a valid manager object. In error conditions, daemon init can fail |
| 22 | 28 |
// and shutdown called, before plugin manager is initialized. |
| 23 | 29 |
deleted file mode 100644 |
| ... | ... |
@@ -1,19 +0,0 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 |
-package daemon |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "github.com/docker/docker/api/types/container" |
|
| 7 |
- "github.com/docker/docker/libcontainerd" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) {
|
|
| 11 |
- return nil, nil |
|
| 12 |
-} |
|
| 13 |
- |
|
| 14 |
-func pluginInit(d *Daemon, config *Config, remote libcontainerd.Remote) error {
|
|
| 15 |
- return nil |
|
| 16 |
-} |
|
| 17 |
- |
|
| 18 |
-func pluginShutdown() {
|
|
| 19 |
-} |
| 6 | 4 |
deleted file mode 100644 |
| ... | ... |
@@ -1,9 +0,0 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 |
-package graphdriver |
|
| 4 |
- |
|
| 5 |
-import "github.com/docker/docker/pkg/plugingetter" |
|
| 6 |
- |
|
| 7 |
-func lookupPlugin(name, home string, opts []string, pg plugingetter.PluginGetter) (Driver, error) {
|
|
| 8 |
- return nil, ErrNotSupported |
|
| 9 |
-} |
| ... | ... |
@@ -109,7 +109,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
| 109 | 109 |
MemTotal: meminfo.MemTotal, |
| 110 | 110 |
DockerRootDir: daemon.configStore.Root, |
| 111 | 111 |
Labels: daemon.configStore.Labels, |
| 112 |
- ExperimentalBuild: utils.ExperimentalBuild(), |
|
| 112 |
+ ExperimentalBuild: daemon.configStore.Experimental, |
|
| 113 | 113 |
ServerVersion: dockerversion.Version, |
| 114 | 114 |
ClusterStore: daemon.configStore.ClusterStore, |
| 115 | 115 |
ClusterAdvertise: daemon.configStore.ClusterAdvertise, |
| ... | ... |
@@ -158,7 +158,7 @@ func (daemon *Daemon) SystemVersion() types.Version {
|
| 158 | 158 |
Os: runtime.GOOS, |
| 159 | 159 |
Arch: runtime.GOARCH, |
| 160 | 160 |
BuildTime: dockerversion.BuildTime, |
| 161 |
- Experimental: utils.ExperimentalBuild(), |
|
| 161 |
+ Experimental: daemon.configStore.Experimental, |
|
| 162 | 162 |
} |
| 163 | 163 |
|
| 164 | 164 |
kernelVersion := "<unknown>" |
| ... | ... |
@@ -157,7 +157,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 157 | 157 |
* `POST /containers/prune` prunes stopped containers. |
| 158 | 158 |
* `POST /images/prune` prunes unused images. |
| 159 | 159 |
* `POST /volumes/prune` prunes unused volumes. |
| 160 |
- |
|
| 160 |
+* Every API response now includes a `Docker-Experimental` header specifying if experimental features are enabled (value can be `true` or `false`). |
|
| 161 | 161 |
|
| 162 | 162 |
### v1.24 API changes |
| 163 | 163 |
|
| ... | ... |
@@ -45,6 +45,7 @@ Options: |
| 45 | 45 |
--dns-search value DNS search domains to use (default []) |
| 46 | 46 |
--exec-opt value Runtime execution options (default []) |
| 47 | 47 |
--exec-root string Root directory for execution state files (default "/var/run/docker") |
| 48 |
+ --experimental Enable experimental features |
|
| 48 | 49 |
--fixed-cidr string IPv4 subnet for fixed IPs |
| 49 | 50 |
--fixed-cidr-v6 string IPv6 subnet for fixed IPs |
| 50 | 51 |
-g, --graph string Root of the Docker runtime (default "/var/lib/docker") |
| ... | ... |
@@ -1114,6 +1115,7 @@ This is a full example of the allowed configuration options on Linux: |
| 1114 | 1114 |
"dns-search": [], |
| 1115 | 1115 |
"exec-opts": [], |
| 1116 | 1116 |
"exec-root": "", |
| 1117 |
+ "experimental": false, |
|
| 1117 | 1118 |
"storage-driver": "", |
| 1118 | 1119 |
"storage-opts": [], |
| 1119 | 1120 |
"labels": [], |
| ... | ... |
@@ -1195,6 +1197,7 @@ This is a full example of the allowed configuration options on Windows: |
| 1195 | 1195 |
"dns-opts": [], |
| 1196 | 1196 |
"dns-search": [], |
| 1197 | 1197 |
"exec-opts": [], |
| 1198 |
+ "experimental": false, |
|
| 1198 | 1199 |
"storage-driver": "", |
| 1199 | 1200 |
"storage-opts": [], |
| 1200 | 1201 |
"labels": [], |
| ... | ... |
@@ -126,12 +126,6 @@ if [ ! "$GOPATH" ]; then |
| 126 | 126 |
exit 1 |
| 127 | 127 |
fi |
| 128 | 128 |
|
| 129 |
-if [ "$DOCKER_EXPERIMENTAL" ]; then |
|
| 130 |
- echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features' |
|
| 131 |
- echo >&2 |
|
| 132 |
- DOCKER_BUILDTAGS+=" experimental" |
|
| 133 |
-fi |
|
| 134 |
- |
|
| 135 | 129 |
DOCKER_BUILDTAGS+=" daemon" |
| 136 | 130 |
if ${PKG_CONFIG} 'libsystemd >= 209' 2> /dev/null ; then
|
| 137 | 131 |
DOCKER_BUILDTAGS+=" journald" |
| ... | ... |
@@ -50,6 +50,11 @@ if [ "$DOCKER_REMAP_ROOT" ]; then |
| 50 | 50 |
extra_params="--userns-remap $DOCKER_REMAP_ROOT" |
| 51 | 51 |
fi |
| 52 | 52 |
|
| 53 |
+if [ "$DOCKER_EXPERIMENTAL" ]; then |
|
| 54 |
+ echo >&2 '# DOCKER_EXPERIMENTAL is set: starting daemon with experimental features enabled! ' |
|
| 55 |
+ extra_params="$extra_params --experimental" |
|
| 56 |
+fi |
|
| 57 |
+ |
|
| 53 | 58 |
if [ -z "$DOCKER_TEST_HOST" ]; then |
| 54 | 59 |
# Start apparmor if it is enabled |
| 55 | 60 |
if [ -e "/sys/module/apparmor/parameters/enabled" ] && [ "$(cat /sys/module/apparmor/parameters/enabled)" == "Y" ]; then |
| ... | ... |
@@ -76,10 +76,6 @@ set -e |
| 76 | 76 |
# Install runc and containerd |
| 77 | 77 |
RUN ./hack/dockerfile/install-binaries.sh runc-dynamic containerd-dynamic |
| 78 | 78 |
EOF |
| 79 |
- |
|
| 80 |
- if [ "$DOCKER_EXPERIMENTAL" ]; then |
|
| 81 |
- echo 'ENV DOCKER_EXPERIMENTAL 1' >> "$DEST/$version/Dockerfile.build" |
|
| 82 |
- fi |
|
| 83 | 79 |
cat >> "$DEST/$version/Dockerfile.build" <<-EOF |
| 84 | 80 |
RUN cp -aL hack/make/.build-deb debian |
| 85 | 81 |
RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog
|
| ... | ... |
@@ -96,8 +96,7 @@ set -e |
| 96 | 96 |
# Install runc and containerd |
| 97 | 97 |
RUN ./hack/dockerfile/install-binaries.sh runc-dynamic containerd-dynamic |
| 98 | 98 |
EOF |
| 99 |
- |
|
| 100 |
- if [ "$DOCKER_EXPERIMENTAL" ]; then |
|
| 99 |
+ if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then |
|
| 101 | 100 |
echo 'ENV DOCKER_EXPERIMENTAL 1' >> "$DEST/$version/Dockerfile.build" |
| 102 | 101 |
fi |
| 103 | 102 |
cat >> "$DEST/$version/Dockerfile.build" <<-EOF |
| ... | ... |
@@ -39,7 +39,7 @@ if [[ "$VERSION" == *-rc* ]]; then |
| 39 | 39 |
component="testing" |
| 40 | 40 |
fi |
| 41 | 41 |
|
| 42 |
-if [ "$DOCKER_EXPERIMENTAL" ] || [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then |
|
| 42 |
+if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then |
|
| 43 | 43 |
component="experimental" |
| 44 | 44 |
fi |
| 45 | 45 |
|
| ... | ... |
@@ -25,7 +25,7 @@ if [[ "$VERSION" == *-rc* ]]; then |
| 25 | 25 |
release="testing" |
| 26 | 26 |
fi |
| 27 | 27 |
|
| 28 |
-if [ $DOCKER_EXPERIMENTAL ] || [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then |
|
| 28 |
+if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then |
|
| 29 | 29 |
release="experimental" |
| 30 | 30 |
fi |
| 31 | 31 |
|
| ... | ... |
@@ -266,7 +266,11 @@ func (s *DockerSwarmSuite) AddDaemon(c *check.C, joinSwarm, manager bool) *Swarm |
| 266 | 266 |
port: defaultSwarmPort + s.portIndex, |
| 267 | 267 |
} |
| 268 | 268 |
d.listenAddr = fmt.Sprintf("0.0.0.0:%d", d.port)
|
| 269 |
- err := d.StartWithBusybox("--iptables=false", "--swarm-default-advertise-addr=lo") // avoid networking conflicts
|
|
| 269 |
+ args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"} // avoid networking conflicts
|
|
| 270 |
+ if experimentalDaemon {
|
|
| 271 |
+ args = append(args, "--experimental") |
|
| 272 |
+ } |
|
| 273 |
+ err := d.StartWithBusybox(args...) |
|
| 270 | 274 |
c.Assert(err, check.IsNil) |
| 271 | 275 |
|
| 272 | 276 |
if joinSwarm == true {
|
| ... | ... |
@@ -153,6 +153,9 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
|
| 153 | 153 |
"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
|
| 154 | 154 |
fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
|
| 155 | 155 |
) |
| 156 |
+ if experimentalDaemon {
|
|
| 157 |
+ args = append(args, "--experimental") |
|
| 158 |
+ } |
|
| 156 | 159 |
if !(d.useDefaultHost || d.useDefaultTLSHost) {
|
| 157 | 160 |
args = append(args, []string{"--host", d.sock()}...)
|
| 158 | 161 |
} |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-// +build linux, experimental |
|
| 1 |
+// +build linux |
|
| 2 | 2 |
|
| 3 | 3 |
package main |
| 4 | 4 |
|
| ... | ... |
@@ -17,7 +17,8 @@ var pluginName = "tiborvass/no-remove" |
| 17 | 17 |
|
| 18 | 18 |
// TestDaemonRestartWithPluginEnabled tests state restore for an enabled plugin |
| 19 | 19 |
func (s *DockerDaemonSuite) TestDaemonRestartWithPluginEnabled(c *check.C) {
|
| 20 |
- testRequires(c, Network) |
|
| 20 |
+ testRequires(c, Network, ExperimentalDaemon) |
|
| 21 |
+ |
|
| 21 | 22 |
if err := s.d.Start(); err != nil {
|
| 22 | 23 |
c.Fatalf("Could not start daemon: %v", err)
|
| 23 | 24 |
} |
| ... | ... |
@@ -49,7 +50,8 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginEnabled(c *check.C) {
|
| 49 | 49 |
|
| 50 | 50 |
// TestDaemonRestartWithPluginDisabled tests state restore for a disabled plugin |
| 51 | 51 |
func (s *DockerDaemonSuite) TestDaemonRestartWithPluginDisabled(c *check.C) {
|
| 52 |
- testRequires(c, Network) |
|
| 52 |
+ testRequires(c, Network, ExperimentalDaemon) |
|
| 53 |
+ |
|
| 53 | 54 |
if err := s.d.Start(); err != nil {
|
| 54 | 55 |
c.Fatalf("Could not start daemon: %v", err)
|
| 55 | 56 |
} |
| ... | ... |
@@ -79,7 +81,8 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginDisabled(c *check.C) {
|
| 79 | 79 |
// TestDaemonKillLiveRestoreWithPlugins SIGKILLs daemon started with --live-restore. |
| 80 | 80 |
// Plugins should continue to run. |
| 81 | 81 |
func (s *DockerDaemonSuite) TestDaemonKillLiveRestoreWithPlugins(c *check.C) {
|
| 82 |
- testRequires(c, Network) |
|
| 82 |
+ testRequires(c, Network, ExperimentalDaemon) |
|
| 83 |
+ |
|
| 83 | 84 |
if err := s.d.Start("--live-restore"); err != nil {
|
| 84 | 85 |
c.Fatalf("Could not start daemon: %v", err)
|
| 85 | 86 |
} |
| ... | ... |
@@ -111,7 +114,8 @@ func (s *DockerDaemonSuite) TestDaemonKillLiveRestoreWithPlugins(c *check.C) {
|
| 111 | 111 |
// TestDaemonShutdownLiveRestoreWithPlugins SIGTERMs daemon started with --live-restore. |
| 112 | 112 |
// Plugins should continue to run. |
| 113 | 113 |
func (s *DockerDaemonSuite) TestDaemonShutdownLiveRestoreWithPlugins(c *check.C) {
|
| 114 |
- testRequires(c, Network) |
|
| 114 |
+ testRequires(c, Network, ExperimentalDaemon) |
|
| 115 |
+ |
|
| 115 | 116 |
if err := s.d.Start("--live-restore"); err != nil {
|
| 116 | 117 |
c.Fatalf("Could not start daemon: %v", err)
|
| 117 | 118 |
} |
| ... | ... |
@@ -142,7 +146,8 @@ func (s *DockerDaemonSuite) TestDaemonShutdownLiveRestoreWithPlugins(c *check.C) |
| 142 | 142 |
|
| 143 | 143 |
// TestDaemonShutdownWithPlugins shuts down running plugins. |
| 144 | 144 |
func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) {
|
| 145 |
- testRequires(c, Network) |
|
| 145 |
+ testRequires(c, Network, ExperimentalDaemon) |
|
| 146 |
+ |
|
| 146 | 147 |
if err := s.d.Start(); err != nil {
|
| 147 | 148 |
c.Fatalf("Could not start daemon: %v", err)
|
| 148 | 149 |
} |
| ... | ... |
@@ -180,7 +185,8 @@ func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) {
|
| 180 | 180 |
|
| 181 | 181 |
// TestVolumePlugin tests volume creation using a plugin. |
| 182 | 182 |
func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) {
|
| 183 |
- testRequires(c, Network) |
|
| 183 |
+ testRequires(c, Network, ExperimentalDaemon) |
|
| 184 |
+ |
|
| 184 | 185 |
volName := "plugin-volume" |
| 185 | 186 |
volRoot := "/data" |
| 186 | 187 |
destDir := "/tmp/data/" |
| ... | ... |
@@ -1,21 +1,36 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 | 1 |
package main |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| 4 |
+ "strings" |
|
| 5 |
+ |
|
| 6 | 6 |
"github.com/docker/docker/pkg/integration/checker" |
| 7 | 7 |
"github.com/go-check/check" |
| 8 |
- "strings" |
|
| 9 | 8 |
) |
| 10 | 9 |
|
| 11 |
-func (s *DockerSuite) TestExperimentalVersion(c *check.C) {
|
|
| 10 |
+func (s *DockerSuite) TestExperimentalVersionTrue(c *check.C) {
|
|
| 11 |
+ testRequires(c, ExperimentalDaemon) |
|
| 12 |
+ |
|
| 12 | 13 |
out, _ := dockerCmd(c, "version") |
| 13 | 14 |
for _, line := range strings.Split(out, "\n") {
|
| 14 |
- if strings.HasPrefix(line, "Experimental (client):") || strings.HasPrefix(line, "Experimental (server):") {
|
|
| 15 |
+ if strings.HasPrefix(strings.TrimSpace(line), "Experimental:") {
|
|
| 15 | 16 |
c.Assert(line, checker.Matches, "*true") |
| 17 |
+ return |
|
| 18 |
+ } |
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ c.Fatal(`"Experimental" not found in version output`) |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func (s *DockerSuite) TestExperimentalVersionFalse(c *check.C) {
|
|
| 25 |
+ testRequires(c, NotExperimentalDaemon) |
|
| 26 |
+ |
|
| 27 |
+ out, _ := dockerCmd(c, "version") |
|
| 28 |
+ for _, line := range strings.Split(out, "\n") {
|
|
| 29 |
+ if strings.HasPrefix(strings.TrimSpace(line), "Experimental:") {
|
|
| 30 |
+ c.Assert(line, checker.Matches, "*false") |
|
| 31 |
+ return |
|
| 16 | 32 |
} |
| 17 | 33 |
} |
| 18 | 34 |
|
| 19 |
- out, _ = dockerCmd(c, "-v") |
|
| 20 |
- c.Assert(out, checker.Contains, ", experimental", check.Commentf("docker version did not contain experimental"))
|
|
| 35 |
+ c.Fatal(`"Experimental" not found in version output`) |
|
| 21 | 36 |
} |
| ... | ... |
@@ -1,4 +1,3 @@ |
| 1 |
-// +build experimental |
|
| 2 | 1 |
// +build !windows |
| 3 | 2 |
|
| 4 | 3 |
package main |
| ... | ... |
@@ -287,7 +286,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex |
| 287 | 287 |
|
| 288 | 288 |
mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
|
| 289 | 289 |
s.ec[ext].applydiff++ |
| 290 |
- var diff io.Reader = r.Body |
|
| 290 |
+ diff := r.Body |
|
| 291 | 291 |
defer r.Body.Close() |
| 292 | 292 |
|
| 293 | 293 |
id := r.URL.Query().Get("id")
|
| ... | ... |
@@ -338,6 +337,8 @@ func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) {
|
| 338 | 338 |
} |
| 339 | 339 |
|
| 340 | 340 |
func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) {
|
| 341 |
+ testRequires(c, ExperimentalDaemon) |
|
| 342 |
+ |
|
| 341 | 343 |
s.testExternalGraphDriver("test-external-graph-driver", "spec", c)
|
| 342 | 344 |
s.testExternalGraphDriver("json-external-graph-driver", "json", c)
|
| 343 | 345 |
} |
| ... | ... |
@@ -388,7 +389,8 @@ func (s *DockerExternalGraphdriverSuite) testExternalGraphDriver(name string, ex |
| 388 | 388 |
} |
| 389 | 389 |
|
| 390 | 390 |
func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriverPull(c *check.C) {
|
| 391 |
- testRequires(c, Network) |
|
| 391 |
+ testRequires(c, Network, ExperimentalDaemon) |
|
| 392 |
+ |
|
| 392 | 393 |
c.Assert(s.d.Start(), check.IsNil) |
| 393 | 394 |
|
| 394 | 395 |
out, err := s.d.Cmd("pull", "busybox:latest")
|
| ... | ... |
@@ -10,7 +10,6 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/pkg/homedir" |
| 11 | 11 |
"github.com/docker/docker/pkg/integration/checker" |
| 12 | 12 |
icmd "github.com/docker/docker/pkg/integration/cmd" |
| 13 |
- "github.com/docker/docker/utils" |
|
| 14 | 13 |
"github.com/go-check/check" |
| 15 | 14 |
) |
| 16 | 15 |
|
| ... | ... |
@@ -117,7 +116,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
| 117 | 117 |
cmdsToTest = append(cmdsToTest, "network ls") |
| 118 | 118 |
cmdsToTest = append(cmdsToTest, "network rm") |
| 119 | 119 |
|
| 120 |
- if utils.ExperimentalBuild() {
|
|
| 120 |
+ if experimentalDaemon {
|
|
| 121 | 121 |
cmdsToTest = append(cmdsToTest, "checkpoint create") |
| 122 | 122 |
cmdsToTest = append(cmdsToTest, "checkpoint ls") |
| 123 | 123 |
cmdsToTest = append(cmdsToTest, "checkpoint rm") |
| ... | ... |
@@ -7,7 +7,6 @@ import ( |
| 7 | 7 |
"strings" |
| 8 | 8 |
|
| 9 | 9 |
"github.com/docker/docker/pkg/integration/checker" |
| 10 |
- "github.com/docker/docker/utils" |
|
| 11 | 10 |
"github.com/go-check/check" |
| 12 | 11 |
) |
| 13 | 12 |
|
| ... | ... |
@@ -44,8 +43,10 @@ func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) {
|
| 44 | 44 |
stringsToCheck = append(stringsToCheck, "Runtimes:", "Default Runtime: runc") |
| 45 | 45 |
} |
| 46 | 46 |
|
| 47 |
- if utils.ExperimentalBuild() {
|
|
| 47 |
+ if experimentalDaemon {
|
|
| 48 | 48 |
stringsToCheck = append(stringsToCheck, "Experimental: true") |
| 49 |
+ } else {
|
|
| 50 |
+ stringsToCheck = append(stringsToCheck, "Experimental: false") |
|
| 49 | 51 |
} |
| 50 | 52 |
|
| 51 | 53 |
for _, linePrefix := range stringsToCheck {
|
| ... | ... |
@@ -1,5 +1,3 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 | 1 |
package main |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| ... | ... |
@@ -11,6 +9,7 @@ import ( |
| 11 | 11 |
) |
| 12 | 12 |
|
| 13 | 13 |
func (s *DockerSwarmSuite) TestStackRemove(c *check.C) {
|
| 14 |
+ testRequires(c, ExperimentalDaemon) |
|
| 14 | 15 |
d := s.AddDaemon(c, true, true) |
| 15 | 16 |
|
| 16 | 17 |
stackArgs := append([]string{"stack", "remove", "UNKNOWN_STACK"})
|
| ... | ... |
@@ -21,6 +20,7 @@ func (s *DockerSwarmSuite) TestStackRemove(c *check.C) {
|
| 21 | 21 |
} |
| 22 | 22 |
|
| 23 | 23 |
func (s *DockerSwarmSuite) TestStackTasks(c *check.C) {
|
| 24 |
+ testRequires(c, ExperimentalDaemon) |
|
| 24 | 25 |
d := s.AddDaemon(c, true, true) |
| 25 | 26 |
|
| 26 | 27 |
stackArgs := append([]string{"stack", "ps", "UNKNOWN_STACK"})
|
| ... | ... |
@@ -31,6 +31,7 @@ func (s *DockerSwarmSuite) TestStackTasks(c *check.C) {
|
| 31 | 31 |
} |
| 32 | 32 |
|
| 33 | 33 |
func (s *DockerSwarmSuite) TestStackServices(c *check.C) {
|
| 34 |
+ testRequires(c, ExperimentalDaemon) |
|
| 34 | 35 |
d := s.AddDaemon(c, true, true) |
| 35 | 36 |
|
| 36 | 37 |
stackArgs := append([]string{"stack", "services", "UNKNOWN_STACK"})
|
| ... | ... |
@@ -59,6 +60,7 @@ const testDAB = `{
|
| 59 | 59 |
}` |
| 60 | 60 |
|
| 61 | 61 |
func (s *DockerSwarmSuite) TestStackWithDAB(c *check.C) {
|
| 62 |
+ testRequires(c, ExperimentalDaemon) |
|
| 62 | 63 |
// setup |
| 63 | 64 |
testStackName := "test" |
| 64 | 65 |
testDABFileName := testStackName + ".dab" |
| ... | ... |
@@ -92,6 +94,7 @@ func (s *DockerSwarmSuite) TestStackWithDAB(c *check.C) {
|
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 | 94 |
func (s *DockerSwarmSuite) TestStackWithDABExtension(c *check.C) {
|
| 95 |
+ testRequires(c, ExperimentalDaemon) |
|
| 95 | 96 |
// setup |
| 96 | 97 |
testStackName := "test.dab" |
| 97 | 98 |
testDABFileName := testStackName |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-// +build experimental |
|
| 1 |
+// +build !windows |
|
| 2 | 2 |
|
| 3 | 3 |
package main |
| 4 | 4 |
|
| ... | ... |
@@ -50,7 +50,8 @@ var ( |
| 50 | 50 |
|
| 51 | 51 |
func (s *DockerNetworkSuite) TestDockerNetworkMacvlanPersistance(c *check.C) {
|
| 52 | 52 |
// verify the driver automatically provisions the 802.1q link (dm-dummy0.60) |
| 53 |
- testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 53 |
+ testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 54 |
+ |
|
| 54 | 55 |
// master dummy interface 'dm' abbreviation represents 'docker macvlan' |
| 55 | 56 |
master := "dm-dummy0" |
| 56 | 57 |
// simulate the master link the vlan tagged subinterface parent link will use |
| ... | ... |
@@ -69,7 +70,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanPersistance(c *check.C) {
|
| 69 | 69 |
|
| 70 | 70 |
func (s *DockerNetworkSuite) TestDockerNetworkIpvlanPersistance(c *check.C) {
|
| 71 | 71 |
// verify the driver automatically provisions the 802.1q link (di-dummy0.70) |
| 72 |
- testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 72 |
+ testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 73 | 73 |
// master dummy interface 'di' notation represent 'docker ipvlan' |
| 74 | 74 |
master := "di-dummy0" |
| 75 | 75 |
// simulate the master link the vlan tagged subinterface parent link will use |
| ... | ... |
@@ -88,7 +89,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanPersistance(c *check.C) {
|
| 88 | 88 |
|
| 89 | 89 |
func (s *DockerNetworkSuite) TestDockerNetworkMacvlanSubIntCreate(c *check.C) {
|
| 90 | 90 |
// verify the driver automatically provisions the 802.1q link (dm-dummy0.50) |
| 91 |
- testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 91 |
+ testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 92 | 92 |
// master dummy interface 'dm' abbreviation represents 'docker macvlan' |
| 93 | 93 |
master := "dm-dummy0" |
| 94 | 94 |
// simulate the master link the vlan tagged subinterface parent link will use |
| ... | ... |
@@ -103,7 +104,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanSubIntCreate(c *check.C) {
|
| 103 | 103 |
|
| 104 | 104 |
func (s *DockerNetworkSuite) TestDockerNetworkIpvlanSubIntCreate(c *check.C) {
|
| 105 | 105 |
// verify the driver automatically provisions the 802.1q link (di-dummy0.50) |
| 106 |
- testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 106 |
+ testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 107 | 107 |
// master dummy interface 'dm' abbreviation represents 'docker ipvlan' |
| 108 | 108 |
master := "di-dummy0" |
| 109 | 109 |
// simulate the master link the vlan tagged subinterface parent link will use |
| ... | ... |
@@ -118,7 +119,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanSubIntCreate(c *check.C) {
|
| 118 | 118 |
|
| 119 | 119 |
func (s *DockerNetworkSuite) TestDockerNetworkMacvlanOverlapParent(c *check.C) {
|
| 120 | 120 |
// verify the same parent interface cannot be used if already in use by an existing network |
| 121 |
- testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 121 |
+ testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 122 | 122 |
// master dummy interface 'dm' abbreviation represents 'docker macvlan' |
| 123 | 123 |
master := "dm-dummy0" |
| 124 | 124 |
out, err := createMasterDummy(c, master) |
| ... | ... |
@@ -138,7 +139,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanOverlapParent(c *check.C) {
|
| 138 | 138 |
|
| 139 | 139 |
func (s *DockerNetworkSuite) TestDockerNetworkIpvlanOverlapParent(c *check.C) {
|
| 140 | 140 |
// verify the same parent interface cannot be used if already in use by an existing network |
| 141 |
- testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 141 |
+ testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 142 | 142 |
// master dummy interface 'dm' abbreviation represents 'docker ipvlan' |
| 143 | 143 |
master := "di-dummy0" |
| 144 | 144 |
out, err := createMasterDummy(c, master) |
| ... | ... |
@@ -158,7 +159,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanOverlapParent(c *check.C) {
|
| 158 | 158 |
|
| 159 | 159 |
func (s *DockerNetworkSuite) TestDockerNetworkMacvlanMultiSubnet(c *check.C) {
|
| 160 | 160 |
// create a dual stack multi-subnet Macvlan bridge mode network and validate connectivity between four containers, two on each subnet |
| 161 |
- testRequires(c, DaemonIsLinux, IPv6, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 161 |
+ testRequires(c, DaemonIsLinux, IPv6, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 162 | 162 |
dockerCmd(c, "network", "create", "--driver=macvlan", "--ipv6", "--subnet=172.28.100.0/24", "--subnet=172.28.102.0/24", "--gateway=172.28.102.254", |
| 163 | 163 |
"--subnet=2001:db8:abc2::/64", "--subnet=2001:db8:abc4::/64", "--gateway=2001:db8:abc4::254", "dualstackbridge") |
| 164 | 164 |
// Ensure the network was created |
| ... | ... |
@@ -213,7 +214,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanMultiSubnet(c *check.C) {
|
| 213 | 213 |
|
| 214 | 214 |
func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL2MultiSubnet(c *check.C) {
|
| 215 | 215 |
// create a dual stack multi-subnet Ipvlan L2 network and validate connectivity within the subnets, two on each subnet |
| 216 |
- testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 216 |
+ testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 217 | 217 |
dockerCmd(c, "network", "create", "--driver=ipvlan", "--ipv6", "--subnet=172.28.200.0/24", "--subnet=172.28.202.0/24", "--gateway=172.28.202.254", |
| 218 | 218 |
"--subnet=2001:db8:abc8::/64", "--subnet=2001:db8:abc6::/64", "--gateway=2001:db8:abc6::254", "dualstackl2") |
| 219 | 219 |
// Ensure the network was created |
| ... | ... |
@@ -267,7 +268,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL2MultiSubnet(c *check.C) {
|
| 267 | 267 |
|
| 268 | 268 |
func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL3MultiSubnet(c *check.C) {
|
| 269 | 269 |
// create a dual stack multi-subnet Ipvlan L3 network and validate connectivity between all four containers per L3 mode |
| 270 |
- testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, IPv6) |
|
| 270 |
+ testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, IPv6, ExperimentalDaemon) |
|
| 271 | 271 |
dockerCmd(c, "network", "create", "--driver=ipvlan", "--ipv6", "--subnet=172.28.10.0/24", "--subnet=172.28.12.0/24", "--gateway=172.28.12.254", |
| 272 | 272 |
"--subnet=2001:db8:abc9::/64", "--subnet=2001:db8:abc7::/64", "--gateway=2001:db8:abc7::254", "-o", "ipvlan_mode=l3", "dualstackl3") |
| 273 | 273 |
// Ensure the network was created |
| ... | ... |
@@ -326,7 +327,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL3MultiSubnet(c *check.C) {
|
| 326 | 326 |
|
| 327 | 327 |
func (s *DockerNetworkSuite) TestDockerNetworkIpvlanAddressing(c *check.C) {
|
| 328 | 328 |
// Ensure the default gateways, next-hops and default dev devices are properly set |
| 329 |
- testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 329 |
+ testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 330 | 330 |
dockerCmd(c, "network", "create", "--driver=macvlan", "--ipv6", "--subnet=172.28.130.0/24", |
| 331 | 331 |
"--subnet=2001:db8:abca::/64", "--gateway=2001:db8:abca::254", "-o", "macvlan_mode=bridge", "dualstackbridge") |
| 332 | 332 |
assertNwIsAvailable(c, "dualstackbridge") |
| ... | ... |
@@ -372,7 +373,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanAddressing(c *check.C) {
|
| 372 | 372 |
|
| 373 | 373 |
func (s *DockerSuite) TestDockerNetworkMacVlanBridgeNilParent(c *check.C) {
|
| 374 | 374 |
// macvlan bridge mode - dummy parent interface is provisioned dynamically |
| 375 |
- testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 375 |
+ testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 376 | 376 |
dockerCmd(c, "network", "create", "--driver=macvlan", "dm-nil-parent") |
| 377 | 377 |
assertNwIsAvailable(c, "dm-nil-parent") |
| 378 | 378 |
|
| ... | ... |
@@ -389,7 +390,7 @@ func (s *DockerSuite) TestDockerNetworkMacVlanBridgeNilParent(c *check.C) {
|
| 389 | 389 |
|
| 390 | 390 |
func (s *DockerSuite) TestDockerNetworkMacVlanBridgeInternalMode(c *check.C) {
|
| 391 | 391 |
// macvlan bridge mode --internal containers can communicate inside the network but not externally |
| 392 |
- testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 392 |
+ testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 393 | 393 |
dockerCmd(c, "network", "create", "--driver=macvlan", "--internal", "dm-internal") |
| 394 | 394 |
assertNwIsAvailable(c, "dm-internal") |
| 395 | 395 |
nr := getNetworkResource(c, "dm-internal") |
| ... | ... |
@@ -412,7 +413,7 @@ func (s *DockerSuite) TestDockerNetworkMacVlanBridgeInternalMode(c *check.C) {
|
| 412 | 412 |
|
| 413 | 413 |
func (s *DockerSuite) TestDockerNetworkIpvlanL2NilParent(c *check.C) {
|
| 414 | 414 |
// ipvlan l2 mode - dummy parent interface is provisioned dynamically |
| 415 |
- testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 415 |
+ testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 416 | 416 |
dockerCmd(c, "network", "create", "--driver=ipvlan", "di-nil-parent") |
| 417 | 417 |
assertNwIsAvailable(c, "di-nil-parent") |
| 418 | 418 |
|
| ... | ... |
@@ -429,7 +430,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL2NilParent(c *check.C) {
|
| 429 | 429 |
|
| 430 | 430 |
func (s *DockerSuite) TestDockerNetworkIpvlanL2InternalMode(c *check.C) {
|
| 431 | 431 |
// ipvlan l2 mode --internal containers can communicate inside the network but not externally |
| 432 |
- testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 432 |
+ testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 433 | 433 |
dockerCmd(c, "network", "create", "--driver=ipvlan", "--internal", "di-internal") |
| 434 | 434 |
assertNwIsAvailable(c, "di-internal") |
| 435 | 435 |
nr := getNetworkResource(c, "di-internal") |
| ... | ... |
@@ -451,7 +452,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL2InternalMode(c *check.C) {
|
| 451 | 451 |
|
| 452 | 452 |
func (s *DockerSuite) TestDockerNetworkIpvlanL3NilParent(c *check.C) {
|
| 453 | 453 |
// ipvlan l3 mode - dummy parent interface is provisioned dynamically |
| 454 |
- testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 454 |
+ testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 455 | 455 |
dockerCmd(c, "network", "create", "--driver=ipvlan", "--subnet=172.28.230.0/24", |
| 456 | 456 |
"--subnet=172.28.220.0/24", "-o", "ipvlan_mode=l3", "di-nil-parent-l3") |
| 457 | 457 |
assertNwIsAvailable(c, "di-nil-parent-l3") |
| ... | ... |
@@ -469,7 +470,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL3NilParent(c *check.C) {
|
| 469 | 469 |
|
| 470 | 470 |
func (s *DockerSuite) TestDockerNetworkIpvlanL3InternalMode(c *check.C) {
|
| 471 | 471 |
// ipvlan l3 mode --internal containers can communicate inside the network but not externally |
| 472 |
- testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 472 |
+ testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 473 | 473 |
dockerCmd(c, "network", "create", "--driver=ipvlan", "--subnet=172.28.230.0/24", |
| 474 | 474 |
"--subnet=172.28.220.0/24", "-o", "ipvlan_mode=l3", "--internal", "di-internal-l3") |
| 475 | 475 |
assertNwIsAvailable(c, "di-internal-l3") |
| ... | ... |
@@ -492,7 +493,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL3InternalMode(c *check.C) {
|
| 492 | 492 |
|
| 493 | 493 |
func (s *DockerSuite) TestDockerNetworkMacVlanExistingParent(c *check.C) {
|
| 494 | 494 |
// macvlan bridge mode - empty parent interface containers can reach each other internally but not externally |
| 495 |
- testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 495 |
+ testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 496 | 496 |
netName := "dm-parent-exists" |
| 497 | 497 |
out, err := createMasterDummy(c, "dm-dummy0") |
| 498 | 498 |
//out, err := createVlanInterface(c, "dm-parent", "dm-slave", "macvlan", "bridge") |
| ... | ... |
@@ -512,7 +513,7 @@ func (s *DockerSuite) TestDockerNetworkMacVlanExistingParent(c *check.C) {
|
| 512 | 512 |
|
| 513 | 513 |
func (s *DockerSuite) TestDockerNetworkMacVlanSubinterface(c *check.C) {
|
| 514 | 514 |
// macvlan bridge mode - empty parent interface containers can reach each other internally but not externally |
| 515 |
- testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) |
|
| 515 |
+ testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) |
|
| 516 | 516 |
netName := "dm-subinterface" |
| 517 | 517 |
out, err := createMasterDummy(c, "dm-dummy0") |
| 518 | 518 |
c.Assert(err, check.IsNil, check.Commentf(out)) |
| ... | ... |
@@ -61,6 +61,10 @@ var ( |
| 61 | 61 |
volumesConfigPath string |
| 62 | 62 |
containerStoragePath string |
| 63 | 63 |
|
| 64 |
+ // experimentalDaemon tell whether the main daemon has |
|
| 65 |
+ // experimental features enabled or not |
|
| 66 |
+ experimentalDaemon bool |
|
| 67 |
+ |
|
| 64 | 68 |
// daemonStorageDriver is held globally so that tests can know the storage |
| 65 | 69 |
// driver of the daemon. This is initialized in docker_utils by sending |
| 66 | 70 |
// a version call to the daemon and examining the response header. |
| ... | ... |
@@ -128,13 +132,15 @@ func init() {
|
| 128 | 128 |
// /info endpoint for the specific root dir |
| 129 | 129 |
dockerBasePath = "/var/lib/docker" |
| 130 | 130 |
type Info struct {
|
| 131 |
- DockerRootDir string |
|
| 131 |
+ DockerRootDir string |
|
| 132 |
+ ExperimentalBuild bool |
|
| 132 | 133 |
} |
| 133 | 134 |
var i Info |
| 134 | 135 |
status, b, err := sockRequest("GET", "/info", nil)
|
| 135 | 136 |
if err == nil && status == 200 {
|
| 136 | 137 |
if err = json.Unmarshal(b, &i); err == nil {
|
| 137 | 138 |
dockerBasePath = i.DockerRootDir |
| 139 |
+ experimentalDaemon = i.ExperimentalBuild |
|
| 138 | 140 |
} |
| 139 | 141 |
} |
| 140 | 142 |
volumesConfigPath = dockerBasePath + "/volumes" |
| ... | ... |
@@ -9,7 +9,6 @@ import ( |
| 9 | 9 |
"strings" |
| 10 | 10 |
"time" |
| 11 | 11 |
|
| 12 |
- "github.com/docker/docker/utils" |
|
| 13 | 12 |
"github.com/go-check/check" |
| 14 | 13 |
) |
| 15 | 14 |
|
| ... | ... |
@@ -31,11 +30,11 @@ var ( |
| 31 | 31 |
"Test requires a Linux daemon", |
| 32 | 32 |
} |
| 33 | 33 |
ExperimentalDaemon = testRequirement{
|
| 34 |
- func() bool { return utils.ExperimentalBuild() },
|
|
| 34 |
+ func() bool { return experimentalDaemon },
|
|
| 35 | 35 |
"Test requires an experimental daemon", |
| 36 | 36 |
} |
| 37 | 37 |
NotExperimentalDaemon = testRequirement{
|
| 38 |
- func() bool { return !utils.ExperimentalBuild() },
|
|
| 38 |
+ func() bool { return !experimentalDaemon },
|
|
| 39 | 39 |
"Test requires a non experimental daemon", |
| 40 | 40 |
} |
| 41 | 41 |
IsAmd64 = testRequirement{
|
| ... | ... |
@@ -27,6 +27,7 @@ dockerd - Enable daemon mode |
| 27 | 27 |
[**--dns-search**[=*[]*]] |
| 28 | 28 |
[**--exec-opt**[=*[]*]] |
| 29 | 29 |
[**--exec-root**[=*/var/run/docker*]] |
| 30 |
+[**--experimental**[=*false*]] |
|
| 30 | 31 |
[**--fixed-cidr**[=*FIXED-CIDR*]] |
| 31 | 32 |
[**--fixed-cidr-v6**[=*FIXED-CIDR-V6*]] |
| 32 | 33 |
[**-G**|**--group**[=*docker*]] |
| ... | ... |
@@ -146,6 +147,9 @@ format. |
| 146 | 146 |
**--exec-root**="" |
| 147 | 147 |
Path to use as the root of the Docker execution state files. Default is `/var/run/docker`. |
| 148 | 148 |
|
| 149 |
+**--experimental**="" |
|
| 150 |
+ Enable the daemon experimental features. |
|
| 151 |
+ |
|
| 149 | 152 |
**--fixed-cidr**="" |
| 150 | 153 |
IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip) |
| 151 | 154 |
|
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-// +build linux,experimental |
|
| 1 |
+// +build linux |
|
| 2 | 2 |
|
| 3 | 3 |
package plugin |
| 4 | 4 |
|
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
"github.com/docker/docker/oci" |
| 13 | 13 |
"github.com/docker/docker/pkg/plugins" |
| 14 | 14 |
"github.com/docker/docker/plugin/v2" |
| 15 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 15 |
+ specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 16 | 16 |
) |
| 17 | 17 |
|
| 18 | 18 |
func (pm *Manager) enable(p *v2.Plugin, force bool) error {
|
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-// +build windows,experimental |
|
| 1 |
+// +build windows |
|
| 2 | 2 |
|
| 3 | 3 |
package plugin |
| 4 | 4 |
|
| ... | ... |
@@ -6,7 +6,7 @@ import ( |
| 6 | 6 |
"fmt" |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/plugin/v2" |
| 9 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 9 |
+ specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
func (pm *Manager) enable(p *v2.Plugin, force bool) error {
|
| ... | ... |
@@ -1,33 +1,240 @@ |
| 1 |
-// +build !experimental |
|
| 2 |
- |
|
| 3 | 1 |
package store |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "strings" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/Sirupsen/logrus" |
|
| 9 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 6 | 10 |
"github.com/docker/docker/pkg/plugingetter" |
| 7 | 11 |
"github.com/docker/docker/pkg/plugins" |
| 12 |
+ "github.com/docker/docker/plugin/v2" |
|
| 13 |
+ "github.com/docker/docker/reference" |
|
| 8 | 14 |
) |
| 9 | 15 |
|
| 10 |
-// GetAllByCap returns a list of plugins matching the given capability. |
|
| 11 |
-func (ps Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
|
|
| 12 |
- pl, err := plugins.GetAll(capability) |
|
| 16 |
+/* allowV1PluginsFallback determines daemon's support for V1 plugins. |
|
| 17 |
+ * When the time comes to remove support for V1 plugins, flipping |
|
| 18 |
+ * this bool is all that will be needed. |
|
| 19 |
+ */ |
|
| 20 |
+const allowV1PluginsFallback bool = true |
|
| 21 |
+ |
|
| 22 |
+/* defaultAPIVersion is the version of the plugin API for volume, network, |
|
| 23 |
+ IPAM and authz. This is a very stable API. When we update this API, then |
|
| 24 |
+ pluginType should include a version. eg "networkdriver/2.0". |
|
| 25 |
+*/ |
|
| 26 |
+const defaultAPIVersion string = "1.0" |
|
| 27 |
+ |
|
| 28 |
+// ErrNotFound indicates that a plugin was not found locally. |
|
| 29 |
+type ErrNotFound string |
|
| 30 |
+ |
|
| 31 |
+func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
|
|
| 32 |
+ |
|
| 33 |
+// GetByName retreives a plugin by name. |
|
| 34 |
+func (ps *Store) GetByName(name string) (*v2.Plugin, error) {
|
|
| 35 |
+ ps.RLock() |
|
| 36 |
+ defer ps.RUnlock() |
|
| 37 |
+ |
|
| 38 |
+ id, nameOk := ps.nameToID[name] |
|
| 39 |
+ if !nameOk {
|
|
| 40 |
+ return nil, ErrNotFound(name) |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ p, idOk := ps.plugins[id] |
|
| 44 |
+ if !idOk {
|
|
| 45 |
+ return nil, ErrNotFound(id) |
|
| 46 |
+ } |
|
| 47 |
+ return p, nil |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+// GetByID retreives a plugin by ID. |
|
| 51 |
+func (ps *Store) GetByID(id string) (*v2.Plugin, error) {
|
|
| 52 |
+ ps.RLock() |
|
| 53 |
+ defer ps.RUnlock() |
|
| 54 |
+ |
|
| 55 |
+ p, idOk := ps.plugins[id] |
|
| 56 |
+ if !idOk {
|
|
| 57 |
+ return nil, ErrNotFound(id) |
|
| 58 |
+ } |
|
| 59 |
+ return p, nil |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+// GetAll retreives all plugins. |
|
| 63 |
+func (ps *Store) GetAll() map[string]*v2.Plugin {
|
|
| 64 |
+ ps.RLock() |
|
| 65 |
+ defer ps.RUnlock() |
|
| 66 |
+ return ps.plugins |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+// SetAll initialized plugins during daemon restore. |
|
| 70 |
+func (ps *Store) SetAll(plugins map[string]*v2.Plugin) {
|
|
| 71 |
+ ps.Lock() |
|
| 72 |
+ defer ps.Unlock() |
|
| 73 |
+ ps.plugins = plugins |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+func (ps *Store) getByCap(name string, capability string) (*v2.Plugin, error) {
|
|
| 77 |
+ ps.RLock() |
|
| 78 |
+ defer ps.RUnlock() |
|
| 79 |
+ |
|
| 80 |
+ p, err := ps.GetByName(name) |
|
| 13 | 81 |
if err != nil {
|
| 14 | 82 |
return nil, err |
| 15 | 83 |
} |
| 16 |
- result := make([]plugingetter.CompatPlugin, len(pl)) |
|
| 17 |
- for i, p := range pl {
|
|
| 18 |
- result[i] = p |
|
| 84 |
+ return p.FilterByCap(capability) |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin {
|
|
| 88 |
+ ps.RLock() |
|
| 89 |
+ defer ps.RUnlock() |
|
| 90 |
+ |
|
| 91 |
+ result := make([]plugingetter.CompatPlugin, 0, 1) |
|
| 92 |
+ for _, p := range ps.plugins {
|
|
| 93 |
+ if _, err := p.FilterByCap(capability); err == nil {
|
|
| 94 |
+ result = append(result, p) |
|
| 95 |
+ } |
|
| 19 | 96 |
} |
| 20 |
- return result, nil |
|
| 97 |
+ return result |
|
| 98 |
+} |
|
| 99 |
+ |
|
| 100 |
+// SetState sets the active state of the plugin and updates plugindb. |
|
| 101 |
+func (ps *Store) SetState(p *v2.Plugin, state bool) {
|
|
| 102 |
+ ps.Lock() |
|
| 103 |
+ defer ps.Unlock() |
|
| 104 |
+ |
|
| 105 |
+ p.PluginObj.Enabled = state |
|
| 106 |
+ ps.updatePluginDB() |
|
| 107 |
+} |
|
| 108 |
+ |
|
| 109 |
+// Add adds a plugin to memory and plugindb. |
|
| 110 |
+func (ps *Store) Add(p *v2.Plugin) {
|
|
| 111 |
+ ps.Lock() |
|
| 112 |
+ ps.plugins[p.GetID()] = p |
|
| 113 |
+ ps.nameToID[p.Name()] = p.GetID() |
|
| 114 |
+ ps.updatePluginDB() |
|
| 115 |
+ ps.Unlock() |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+// Remove removes a plugin from memory and plugindb. |
|
| 119 |
+func (ps *Store) Remove(p *v2.Plugin) {
|
|
| 120 |
+ ps.Lock() |
|
| 121 |
+ delete(ps.plugins, p.GetID()) |
|
| 122 |
+ delete(ps.nameToID, p.Name()) |
|
| 123 |
+ ps.updatePluginDB() |
|
| 124 |
+ ps.Unlock() |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+// Callers are expected to hold the store lock. |
|
| 128 |
+func (ps *Store) updatePluginDB() error {
|
|
| 129 |
+ jsonData, err := json.Marshal(ps.plugins) |
|
| 130 |
+ if err != nil {
|
|
| 131 |
+ logrus.Debugf("Error in json.Marshal: %v", err)
|
|
| 132 |
+ return err |
|
| 133 |
+ } |
|
| 134 |
+ ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600) |
|
| 135 |
+ return nil |
|
| 21 | 136 |
} |
| 22 | 137 |
|
| 23 | 138 |
// Get returns a plugin matching the given name and capability. |
| 24 |
-func (ps Store) Get(name, capability string, _ int) (plugingetter.CompatPlugin, error) {
|
|
| 25 |
- return plugins.Get(name, capability) |
|
| 139 |
+func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) {
|
|
| 140 |
+ var ( |
|
| 141 |
+ p *v2.Plugin |
|
| 142 |
+ err error |
|
| 143 |
+ ) |
|
| 144 |
+ |
|
| 145 |
+ // Lookup using new model. |
|
| 146 |
+ if ps != nil {
|
|
| 147 |
+ fullName := name |
|
| 148 |
+ if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
|
|
| 149 |
+ if reference.IsNameOnly(named) {
|
|
| 150 |
+ named = reference.WithDefaultTag(named) |
|
| 151 |
+ } |
|
| 152 |
+ ref, ok := named.(reference.NamedTagged) |
|
| 153 |
+ if !ok {
|
|
| 154 |
+ return nil, fmt.Errorf("invalid name: %s", named.String())
|
|
| 155 |
+ } |
|
| 156 |
+ fullName = ref.String() |
|
| 157 |
+ } |
|
| 158 |
+ p, err = ps.GetByName(fullName) |
|
| 159 |
+ if err == nil {
|
|
| 160 |
+ p.Lock() |
|
| 161 |
+ p.RefCount += mode |
|
| 162 |
+ p.Unlock() |
|
| 163 |
+ return p.FilterByCap(capability) |
|
| 164 |
+ } |
|
| 165 |
+ if _, ok := err.(ErrNotFound); !ok {
|
|
| 166 |
+ return nil, err |
|
| 167 |
+ } |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ // Lookup using legacy model. |
|
| 171 |
+ if allowV1PluginsFallback {
|
|
| 172 |
+ p, err := plugins.Get(name, capability) |
|
| 173 |
+ if err != nil {
|
|
| 174 |
+ return nil, fmt.Errorf("legacy plugin: %v", err)
|
|
| 175 |
+ } |
|
| 176 |
+ return p, nil |
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 179 |
+ return nil, err |
|
| 180 |
+} |
|
| 181 |
+ |
|
| 182 |
+// GetAllByCap returns a list of plugins matching the given capability. |
|
| 183 |
+func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
|
|
| 184 |
+ result := make([]plugingetter.CompatPlugin, 0, 1) |
|
| 185 |
+ |
|
| 186 |
+ /* Daemon start always calls plugin.Init thereby initializing a store. |
|
| 187 |
+ * So store on experimental builds can never be nil, even while |
|
| 188 |
+ * handling legacy plugins. However, there are legacy plugin unit |
|
| 189 |
+ * tests where the volume subsystem directly talks with the plugin, |
|
| 190 |
+ * bypassing the daemon. For such tests, this check is necessary. |
|
| 191 |
+ */ |
|
| 192 |
+ if ps != nil {
|
|
| 193 |
+ ps.RLock() |
|
| 194 |
+ result = ps.getAllByCap(capability) |
|
| 195 |
+ ps.RUnlock() |
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ // Lookup with legacy model |
|
| 199 |
+ if allowV1PluginsFallback {
|
|
| 200 |
+ pl, err := plugins.GetAll(capability) |
|
| 201 |
+ if err != nil {
|
|
| 202 |
+ return nil, fmt.Errorf("legacy plugin: %v", err)
|
|
| 203 |
+ } |
|
| 204 |
+ for _, p := range pl {
|
|
| 205 |
+ result = append(result, p) |
|
| 206 |
+ } |
|
| 207 |
+ } |
|
| 208 |
+ return result, nil |
|
| 26 | 209 |
} |
| 27 | 210 |
|
| 28 | 211 |
// Handle sets a callback for a given capability. It is only used by network |
| 29 | 212 |
// and ipam drivers during plugin registration. The callback registers the |
| 30 | 213 |
// driver with the subsystem (network, ipam). |
| 31 | 214 |
func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) {
|
| 32 |
- plugins.Handle(capability, callback) |
|
| 215 |
+ pluginType := fmt.Sprintf("docker.%s/%s", strings.ToLower(capability), defaultAPIVersion)
|
|
| 216 |
+ |
|
| 217 |
+ // Register callback with new plugin model. |
|
| 218 |
+ ps.Lock() |
|
| 219 |
+ handlers, ok := ps.handlers[pluginType] |
|
| 220 |
+ if !ok {
|
|
| 221 |
+ handlers = []func(string, *plugins.Client){}
|
|
| 222 |
+ } |
|
| 223 |
+ handlers = append(handlers, callback) |
|
| 224 |
+ ps.handlers[pluginType] = handlers |
|
| 225 |
+ ps.Unlock() |
|
| 226 |
+ |
|
| 227 |
+ // Register callback with legacy plugin model. |
|
| 228 |
+ if allowV1PluginsFallback {
|
|
| 229 |
+ plugins.Handle(capability, callback) |
|
| 230 |
+ } |
|
| 231 |
+} |
|
| 232 |
+ |
|
| 233 |
+// CallHandler calls the registered callback. It is invoked during plugin enable. |
|
| 234 |
+func (ps *Store) CallHandler(p *v2.Plugin) {
|
|
| 235 |
+ for _, typ := range p.GetTypes() {
|
|
| 236 |
+ for _, handler := range ps.handlers[typ.String()] {
|
|
| 237 |
+ handler(p.Name(), p.Client()) |
|
| 238 |
+ } |
|
| 239 |
+ } |
|
| 33 | 240 |
} |
| 34 | 241 |
deleted file mode 100644 |
| ... | ... |
@@ -1,242 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package store |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "encoding/json" |
|
| 7 |
- "fmt" |
|
| 8 |
- "strings" |
|
| 9 |
- |
|
| 10 |
- "github.com/Sirupsen/logrus" |
|
| 11 |
- "github.com/docker/docker/pkg/ioutils" |
|
| 12 |
- "github.com/docker/docker/pkg/plugingetter" |
|
| 13 |
- "github.com/docker/docker/pkg/plugins" |
|
| 14 |
- "github.com/docker/docker/plugin/v2" |
|
| 15 |
- "github.com/docker/docker/reference" |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-/* allowV1PluginsFallback determines daemon's support for V1 plugins. |
|
| 19 |
- * When the time comes to remove support for V1 plugins, flipping |
|
| 20 |
- * this bool is all that will be needed. |
|
| 21 |
- */ |
|
| 22 |
-const allowV1PluginsFallback bool = true |
|
| 23 |
- |
|
| 24 |
-/* defaultAPIVersion is the version of the plugin API for volume, network, |
|
| 25 |
- IPAM and authz. This is a very stable API. When we update this API, then |
|
| 26 |
- pluginType should include a version. eg "networkdriver/2.0". |
|
| 27 |
-*/ |
|
| 28 |
-const defaultAPIVersion string = "1.0" |
|
| 29 |
- |
|
| 30 |
-// ErrNotFound indicates that a plugin was not found locally. |
|
| 31 |
-type ErrNotFound string |
|
| 32 |
- |
|
| 33 |
-func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
|
|
| 34 |
- |
|
| 35 |
-// GetByName retreives a plugin by name. |
|
| 36 |
-func (ps *Store) GetByName(name string) (*v2.Plugin, error) {
|
|
| 37 |
- ps.RLock() |
|
| 38 |
- defer ps.RUnlock() |
|
| 39 |
- |
|
| 40 |
- id, nameOk := ps.nameToID[name] |
|
| 41 |
- if !nameOk {
|
|
| 42 |
- return nil, ErrNotFound(name) |
|
| 43 |
- } |
|
| 44 |
- |
|
| 45 |
- p, idOk := ps.plugins[id] |
|
| 46 |
- if !idOk {
|
|
| 47 |
- return nil, ErrNotFound(id) |
|
| 48 |
- } |
|
| 49 |
- return p, nil |
|
| 50 |
-} |
|
| 51 |
- |
|
| 52 |
-// GetByID retreives a plugin by ID. |
|
| 53 |
-func (ps *Store) GetByID(id string) (*v2.Plugin, error) {
|
|
| 54 |
- ps.RLock() |
|
| 55 |
- defer ps.RUnlock() |
|
| 56 |
- |
|
| 57 |
- p, idOk := ps.plugins[id] |
|
| 58 |
- if !idOk {
|
|
| 59 |
- return nil, ErrNotFound(id) |
|
| 60 |
- } |
|
| 61 |
- return p, nil |
|
| 62 |
-} |
|
| 63 |
- |
|
| 64 |
-// GetAll retreives all plugins. |
|
| 65 |
-func (ps *Store) GetAll() map[string]*v2.Plugin {
|
|
| 66 |
- ps.RLock() |
|
| 67 |
- defer ps.RUnlock() |
|
| 68 |
- return ps.plugins |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-// SetAll initialized plugins during daemon restore. |
|
| 72 |
-func (ps *Store) SetAll(plugins map[string]*v2.Plugin) {
|
|
| 73 |
- ps.Lock() |
|
| 74 |
- defer ps.Unlock() |
|
| 75 |
- ps.plugins = plugins |
|
| 76 |
-} |
|
| 77 |
- |
|
| 78 |
-func (ps *Store) getByCap(name string, capability string) (*v2.Plugin, error) {
|
|
| 79 |
- ps.RLock() |
|
| 80 |
- defer ps.RUnlock() |
|
| 81 |
- |
|
| 82 |
- p, err := ps.GetByName(name) |
|
| 83 |
- if err != nil {
|
|
| 84 |
- return nil, err |
|
| 85 |
- } |
|
| 86 |
- return p.FilterByCap(capability) |
|
| 87 |
-} |
|
| 88 |
- |
|
| 89 |
-func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin {
|
|
| 90 |
- ps.RLock() |
|
| 91 |
- defer ps.RUnlock() |
|
| 92 |
- |
|
| 93 |
- result := make([]plugingetter.CompatPlugin, 0, 1) |
|
| 94 |
- for _, p := range ps.plugins {
|
|
| 95 |
- if _, err := p.FilterByCap(capability); err == nil {
|
|
| 96 |
- result = append(result, p) |
|
| 97 |
- } |
|
| 98 |
- } |
|
| 99 |
- return result |
|
| 100 |
-} |
|
| 101 |
- |
|
| 102 |
-// SetState sets the active state of the plugin and updates plugindb. |
|
| 103 |
-func (ps *Store) SetState(p *v2.Plugin, state bool) {
|
|
| 104 |
- ps.Lock() |
|
| 105 |
- defer ps.Unlock() |
|
| 106 |
- |
|
| 107 |
- p.PluginObj.Enabled = state |
|
| 108 |
- ps.updatePluginDB() |
|
| 109 |
-} |
|
| 110 |
- |
|
| 111 |
-// Add adds a plugin to memory and plugindb. |
|
| 112 |
-func (ps *Store) Add(p *v2.Plugin) {
|
|
| 113 |
- ps.Lock() |
|
| 114 |
- ps.plugins[p.GetID()] = p |
|
| 115 |
- ps.nameToID[p.Name()] = p.GetID() |
|
| 116 |
- ps.updatePluginDB() |
|
| 117 |
- ps.Unlock() |
|
| 118 |
-} |
|
| 119 |
- |
|
| 120 |
-// Remove removes a plugin from memory and plugindb. |
|
| 121 |
-func (ps *Store) Remove(p *v2.Plugin) {
|
|
| 122 |
- ps.Lock() |
|
| 123 |
- delete(ps.plugins, p.GetID()) |
|
| 124 |
- delete(ps.nameToID, p.Name()) |
|
| 125 |
- ps.updatePluginDB() |
|
| 126 |
- ps.Unlock() |
|
| 127 |
-} |
|
| 128 |
- |
|
| 129 |
-// Callers are expected to hold the store lock. |
|
| 130 |
-func (ps *Store) updatePluginDB() error {
|
|
| 131 |
- jsonData, err := json.Marshal(ps.plugins) |
|
| 132 |
- if err != nil {
|
|
| 133 |
- logrus.Debugf("Error in json.Marshal: %v", err)
|
|
| 134 |
- return err |
|
| 135 |
- } |
|
| 136 |
- ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600) |
|
| 137 |
- return nil |
|
| 138 |
-} |
|
| 139 |
- |
|
| 140 |
-// Get returns a plugin matching the given name and capability. |
|
| 141 |
-func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) {
|
|
| 142 |
- var ( |
|
| 143 |
- p *v2.Plugin |
|
| 144 |
- err error |
|
| 145 |
- ) |
|
| 146 |
- |
|
| 147 |
- // Lookup using new model. |
|
| 148 |
- if ps != nil {
|
|
| 149 |
- fullName := name |
|
| 150 |
- if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
|
|
| 151 |
- if reference.IsNameOnly(named) {
|
|
| 152 |
- named = reference.WithDefaultTag(named) |
|
| 153 |
- } |
|
| 154 |
- ref, ok := named.(reference.NamedTagged) |
|
| 155 |
- if !ok {
|
|
| 156 |
- return nil, fmt.Errorf("invalid name: %s", named.String())
|
|
| 157 |
- } |
|
| 158 |
- fullName = ref.String() |
|
| 159 |
- } |
|
| 160 |
- p, err = ps.GetByName(fullName) |
|
| 161 |
- if err == nil {
|
|
| 162 |
- p.Lock() |
|
| 163 |
- p.RefCount += mode |
|
| 164 |
- p.Unlock() |
|
| 165 |
- return p.FilterByCap(capability) |
|
| 166 |
- } |
|
| 167 |
- if _, ok := err.(ErrNotFound); !ok {
|
|
| 168 |
- return nil, err |
|
| 169 |
- } |
|
| 170 |
- } |
|
| 171 |
- |
|
| 172 |
- // Lookup using legacy model. |
|
| 173 |
- if allowV1PluginsFallback {
|
|
| 174 |
- p, err := plugins.Get(name, capability) |
|
| 175 |
- if err != nil {
|
|
| 176 |
- return nil, fmt.Errorf("legacy plugin: %v", err)
|
|
| 177 |
- } |
|
| 178 |
- return p, nil |
|
| 179 |
- } |
|
| 180 |
- |
|
| 181 |
- return nil, err |
|
| 182 |
-} |
|
| 183 |
- |
|
| 184 |
-// GetAllByCap returns a list of plugins matching the given capability. |
|
| 185 |
-func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
|
|
| 186 |
- result := make([]plugingetter.CompatPlugin, 0, 1) |
|
| 187 |
- |
|
| 188 |
- /* Daemon start always calls plugin.Init thereby initializing a store. |
|
| 189 |
- * So store on experimental builds can never be nil, even while |
|
| 190 |
- * handling legacy plugins. However, there are legacy plugin unit |
|
| 191 |
- * tests where the volume subsystem directly talks with the plugin, |
|
| 192 |
- * bypassing the daemon. For such tests, this check is necessary. |
|
| 193 |
- */ |
|
| 194 |
- if ps != nil {
|
|
| 195 |
- ps.RLock() |
|
| 196 |
- result = ps.getAllByCap(capability) |
|
| 197 |
- ps.RUnlock() |
|
| 198 |
- } |
|
| 199 |
- |
|
| 200 |
- // Lookup with legacy model |
|
| 201 |
- if allowV1PluginsFallback {
|
|
| 202 |
- pl, err := plugins.GetAll(capability) |
|
| 203 |
- if err != nil {
|
|
| 204 |
- return nil, fmt.Errorf("legacy plugin: %v", err)
|
|
| 205 |
- } |
|
| 206 |
- for _, p := range pl {
|
|
| 207 |
- result = append(result, p) |
|
| 208 |
- } |
|
| 209 |
- } |
|
| 210 |
- return result, nil |
|
| 211 |
-} |
|
| 212 |
- |
|
| 213 |
-// Handle sets a callback for a given capability. It is only used by network |
|
| 214 |
-// and ipam drivers during plugin registration. The callback registers the |
|
| 215 |
-// driver with the subsystem (network, ipam). |
|
| 216 |
-func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) {
|
|
| 217 |
- pluginType := fmt.Sprintf("docker.%s/%s", strings.ToLower(capability), defaultAPIVersion)
|
|
| 218 |
- |
|
| 219 |
- // Register callback with new plugin model. |
|
| 220 |
- ps.Lock() |
|
| 221 |
- handlers, ok := ps.handlers[pluginType] |
|
| 222 |
- if !ok {
|
|
| 223 |
- handlers = []func(string, *plugins.Client){}
|
|
| 224 |
- } |
|
| 225 |
- handlers = append(handlers, callback) |
|
| 226 |
- ps.handlers[pluginType] = handlers |
|
| 227 |
- ps.Unlock() |
|
| 228 |
- |
|
| 229 |
- // Register callback with legacy plugin model. |
|
| 230 |
- if allowV1PluginsFallback {
|
|
| 231 |
- plugins.Handle(capability, callback) |
|
| 232 |
- } |
|
| 233 |
-} |
|
| 234 |
- |
|
| 235 |
-// CallHandler calls the registered callback. It is invoked during plugin enable. |
|
| 236 |
-func (ps *Store) CallHandler(p *v2.Plugin) {
|
|
| 237 |
- for _, typ := range p.GetTypes() {
|
|
| 238 |
- for _, handler := range ps.handlers[typ.String()] {
|
|
| 239 |
- handler(p.Name(), p.Client()) |
|
| 240 |
- } |
|
| 241 |
- } |
|
| 242 |
-} |
| ... | ... |
@@ -1,10 +1,18 @@ |
| 1 | 1 |
package v2 |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 5 |
+ "errors" |
|
| 6 |
+ "fmt" |
|
| 7 |
+ "os" |
|
| 8 |
+ "path/filepath" |
|
| 9 |
+ "strings" |
|
| 4 | 10 |
"sync" |
| 5 | 11 |
|
| 6 | 12 |
"github.com/docker/docker/api/types" |
| 7 | 13 |
"github.com/docker/docker/pkg/plugins" |
| 14 |
+ "github.com/docker/docker/pkg/system" |
|
| 15 |
+ specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 8 | 16 |
) |
| 9 | 17 |
|
| 10 | 18 |
// Plugin represents an individual plugin. |
| ... | ... |
@@ -17,3 +25,235 @@ type Plugin struct {
|
| 17 | 17 |
Restart bool `json:"-"` |
| 18 | 18 |
ExitChan chan bool `json:"-"` |
| 19 | 19 |
} |
| 20 |
+ |
|
| 21 |
+const defaultPluginRuntimeDestination = "/run/docker/plugins" |
|
| 22 |
+ |
|
| 23 |
+// ErrInadequateCapability indicates that the plugin did not have the requested capability. |
|
| 24 |
+type ErrInadequateCapability string |
|
| 25 |
+ |
|
| 26 |
+func (cap ErrInadequateCapability) Error() string {
|
|
| 27 |
+ return fmt.Sprintf("plugin does not provide %q capability", cap)
|
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+func newPluginObj(name, id, tag string) types.Plugin {
|
|
| 31 |
+ return types.Plugin{Name: name, ID: id, Tag: tag}
|
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+// NewPlugin creates a plugin. |
|
| 35 |
+func NewPlugin(name, id, runRoot, tag string) *Plugin {
|
|
| 36 |
+ return &Plugin{
|
|
| 37 |
+ PluginObj: newPluginObj(name, id, tag), |
|
| 38 |
+ RuntimeSourcePath: filepath.Join(runRoot, id), |
|
| 39 |
+ } |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+// Client returns the plugin client. |
|
| 43 |
+func (p *Plugin) Client() *plugins.Client {
|
|
| 44 |
+ return p.PClient |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+// IsV1 returns true for V1 plugins and false otherwise. |
|
| 48 |
+func (p *Plugin) IsV1() bool {
|
|
| 49 |
+ return false |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+// Name returns the plugin name. |
|
| 53 |
+func (p *Plugin) Name() string {
|
|
| 54 |
+ name := p.PluginObj.Name |
|
| 55 |
+ if len(p.PluginObj.Tag) > 0 {
|
|
| 56 |
+ // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these |
|
| 57 |
+ name += ":" + p.PluginObj.Tag |
|
| 58 |
+ } |
|
| 59 |
+ return name |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+// FilterByCap query the plugin for a given capability. |
|
| 63 |
+func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
|
|
| 64 |
+ capability = strings.ToLower(capability) |
|
| 65 |
+ for _, typ := range p.PluginObj.Manifest.Interface.Types {
|
|
| 66 |
+ if typ.Capability == capability && typ.Prefix == "docker" {
|
|
| 67 |
+ return p, nil |
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ return nil, ErrInadequateCapability(capability) |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+// RemoveFromDisk deletes the plugin's runtime files from disk. |
|
| 74 |
+func (p *Plugin) RemoveFromDisk() error {
|
|
| 75 |
+ return os.RemoveAll(p.RuntimeSourcePath) |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+// InitPlugin populates the plugin object from the plugin manifest file. |
|
| 79 |
+func (p *Plugin) InitPlugin(libRoot string) error {
|
|
| 80 |
+ dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json")) |
|
| 81 |
+ if err != nil {
|
|
| 82 |
+ return err |
|
| 83 |
+ } |
|
| 84 |
+ err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest) |
|
| 85 |
+ dt.Close() |
|
| 86 |
+ if err != nil {
|
|
| 87 |
+ return err |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts)) |
|
| 91 |
+ for i, mount := range p.PluginObj.Manifest.Mounts {
|
|
| 92 |
+ p.PluginObj.Config.Mounts[i] = mount |
|
| 93 |
+ } |
|
| 94 |
+ p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env)) |
|
| 95 |
+ for _, env := range p.PluginObj.Manifest.Env {
|
|
| 96 |
+ if env.Value != nil {
|
|
| 97 |
+ p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
|
|
| 98 |
+ } |
|
| 99 |
+ } |
|
| 100 |
+ copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value) |
|
| 101 |
+ |
|
| 102 |
+ f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json")) |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ return err |
|
| 105 |
+ } |
|
| 106 |
+ err = json.NewEncoder(f).Encode(&p.PluginObj.Config) |
|
| 107 |
+ f.Close() |
|
| 108 |
+ return err |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+// Set is used to pass arguments to the plugin. |
|
| 112 |
+func (p *Plugin) Set(args []string) error {
|
|
| 113 |
+ m := make(map[string]string, len(args)) |
|
| 114 |
+ for _, arg := range args {
|
|
| 115 |
+ i := strings.Index(arg, "=") |
|
| 116 |
+ if i < 0 {
|
|
| 117 |
+ return fmt.Errorf("No equal sign '=' found in %s", arg)
|
|
| 118 |
+ } |
|
| 119 |
+ m[arg[:i]] = arg[i+1:] |
|
| 120 |
+ } |
|
| 121 |
+ return errors.New("not implemented")
|
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+// ComputePrivileges takes the manifest file and computes the list of access necessary |
|
| 125 |
+// for the plugin on the host. |
|
| 126 |
+func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
|
|
| 127 |
+ m := p.PluginObj.Manifest |
|
| 128 |
+ var privileges types.PluginPrivileges |
|
| 129 |
+ if m.Network.Type != "null" && m.Network.Type != "bridge" {
|
|
| 130 |
+ privileges = append(privileges, types.PluginPrivilege{
|
|
| 131 |
+ Name: "network", |
|
| 132 |
+ Description: "", |
|
| 133 |
+ Value: []string{m.Network.Type},
|
|
| 134 |
+ }) |
|
| 135 |
+ } |
|
| 136 |
+ for _, mount := range m.Mounts {
|
|
| 137 |
+ if mount.Source != nil {
|
|
| 138 |
+ privileges = append(privileges, types.PluginPrivilege{
|
|
| 139 |
+ Name: "mount", |
|
| 140 |
+ Description: "", |
|
| 141 |
+ Value: []string{*mount.Source},
|
|
| 142 |
+ }) |
|
| 143 |
+ } |
|
| 144 |
+ } |
|
| 145 |
+ for _, device := range m.Devices {
|
|
| 146 |
+ if device.Path != nil {
|
|
| 147 |
+ privileges = append(privileges, types.PluginPrivilege{
|
|
| 148 |
+ Name: "device", |
|
| 149 |
+ Description: "", |
|
| 150 |
+ Value: []string{*device.Path},
|
|
| 151 |
+ }) |
|
| 152 |
+ } |
|
| 153 |
+ } |
|
| 154 |
+ if len(m.Capabilities) > 0 {
|
|
| 155 |
+ privileges = append(privileges, types.PluginPrivilege{
|
|
| 156 |
+ Name: "capabilities", |
|
| 157 |
+ Description: "", |
|
| 158 |
+ Value: m.Capabilities, |
|
| 159 |
+ }) |
|
| 160 |
+ } |
|
| 161 |
+ return privileges |
|
| 162 |
+} |
|
| 163 |
+ |
|
| 164 |
+// IsEnabled returns the active state of the plugin. |
|
| 165 |
+func (p *Plugin) IsEnabled() bool {
|
|
| 166 |
+ p.RLock() |
|
| 167 |
+ defer p.RUnlock() |
|
| 168 |
+ |
|
| 169 |
+ return p.PluginObj.Enabled |
|
| 170 |
+} |
|
| 171 |
+ |
|
| 172 |
+// GetID returns the plugin's ID. |
|
| 173 |
+func (p *Plugin) GetID() string {
|
|
| 174 |
+ p.RLock() |
|
| 175 |
+ defer p.RUnlock() |
|
| 176 |
+ |
|
| 177 |
+ return p.PluginObj.ID |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 180 |
+// GetSocket returns the plugin socket. |
|
| 181 |
+func (p *Plugin) GetSocket() string {
|
|
| 182 |
+ p.RLock() |
|
| 183 |
+ defer p.RUnlock() |
|
| 184 |
+ |
|
| 185 |
+ return p.PluginObj.Manifest.Interface.Socket |
|
| 186 |
+} |
|
| 187 |
+ |
|
| 188 |
+// GetTypes returns the interface types of a plugin. |
|
| 189 |
+func (p *Plugin) GetTypes() []types.PluginInterfaceType {
|
|
| 190 |
+ p.RLock() |
|
| 191 |
+ defer p.RUnlock() |
|
| 192 |
+ |
|
| 193 |
+ return p.PluginObj.Manifest.Interface.Types |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 196 |
+// InitSpec creates an OCI spec from the plugin's config. |
|
| 197 |
+func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
|
|
| 198 |
+ rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") |
|
| 199 |
+ s.Root = specs.Root{
|
|
| 200 |
+ Path: rootfs, |
|
| 201 |
+ Readonly: false, // TODO: all plugins should be readonly? settable in manifest? |
|
| 202 |
+ } |
|
| 203 |
+ |
|
| 204 |
+ mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
|
|
| 205 |
+ Source: &p.RuntimeSourcePath, |
|
| 206 |
+ Destination: defaultPluginRuntimeDestination, |
|
| 207 |
+ Type: "bind", |
|
| 208 |
+ Options: []string{"rbind", "rshared"},
|
|
| 209 |
+ }) |
|
| 210 |
+ for _, mount := range mounts {
|
|
| 211 |
+ m := specs.Mount{
|
|
| 212 |
+ Destination: mount.Destination, |
|
| 213 |
+ Type: mount.Type, |
|
| 214 |
+ Options: mount.Options, |
|
| 215 |
+ } |
|
| 216 |
+ // TODO: if nil, then it's required and user didn't set it |
|
| 217 |
+ if mount.Source != nil {
|
|
| 218 |
+ m.Source = *mount.Source |
|
| 219 |
+ } |
|
| 220 |
+ if m.Source != "" && m.Type == "bind" {
|
|
| 221 |
+ fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks |
|
| 222 |
+ if err != nil {
|
|
| 223 |
+ return nil, err |
|
| 224 |
+ } |
|
| 225 |
+ if fi.IsDir() {
|
|
| 226 |
+ if err := os.MkdirAll(m.Source, 0700); err != nil {
|
|
| 227 |
+ return nil, err |
|
| 228 |
+ } |
|
| 229 |
+ } |
|
| 230 |
+ } |
|
| 231 |
+ s.Mounts = append(s.Mounts, m) |
|
| 232 |
+ } |
|
| 233 |
+ |
|
| 234 |
+ envs := make([]string, 1, len(p.PluginObj.Config.Env)+1) |
|
| 235 |
+ envs[0] = "PATH=" + system.DefaultPathEnv |
|
| 236 |
+ envs = append(envs, p.PluginObj.Config.Env...) |
|
| 237 |
+ |
|
| 238 |
+ args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...) |
|
| 239 |
+ cwd := p.PluginObj.Manifest.Workdir |
|
| 240 |
+ if len(cwd) == 0 {
|
|
| 241 |
+ cwd = "/" |
|
| 242 |
+ } |
|
| 243 |
+ s.Process = specs.Process{
|
|
| 244 |
+ Terminal: false, |
|
| 245 |
+ Args: args, |
|
| 246 |
+ Cwd: cwd, |
|
| 247 |
+ Env: envs, |
|
| 248 |
+ } |
|
| 249 |
+ |
|
| 250 |
+ return &s, nil |
|
| 251 |
+} |
| 20 | 252 |
deleted file mode 100644 |
| ... | ... |
@@ -1,249 +0,0 @@ |
| 1 |
-// +build experimental |
|
| 2 |
- |
|
| 3 |
-package v2 |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "encoding/json" |
|
| 7 |
- "errors" |
|
| 8 |
- "fmt" |
|
| 9 |
- "os" |
|
| 10 |
- "path/filepath" |
|
| 11 |
- "strings" |
|
| 12 |
- |
|
| 13 |
- "github.com/docker/docker/api/types" |
|
| 14 |
- "github.com/docker/docker/pkg/plugins" |
|
| 15 |
- "github.com/docker/docker/pkg/system" |
|
| 16 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 17 |
-) |
|
| 18 |
- |
|
| 19 |
-const defaultPluginRuntimeDestination = "/run/docker/plugins" |
|
| 20 |
- |
|
| 21 |
-// ErrInadequateCapability indicates that the plugin did not have the requested capability. |
|
| 22 |
-type ErrInadequateCapability string |
|
| 23 |
- |
|
| 24 |
-func (cap ErrInadequateCapability) Error() string {
|
|
| 25 |
- return fmt.Sprintf("plugin does not provide %q capability", cap)
|
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-func newPluginObj(name, id, tag string) types.Plugin {
|
|
| 29 |
- return types.Plugin{Name: name, ID: id, Tag: tag}
|
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-// NewPlugin creates a plugin. |
|
| 33 |
-func NewPlugin(name, id, runRoot, tag string) *Plugin {
|
|
| 34 |
- return &Plugin{
|
|
| 35 |
- PluginObj: newPluginObj(name, id, tag), |
|
| 36 |
- RuntimeSourcePath: filepath.Join(runRoot, id), |
|
| 37 |
- } |
|
| 38 |
-} |
|
| 39 |
- |
|
| 40 |
-// Client returns the plugin client. |
|
| 41 |
-func (p *Plugin) Client() *plugins.Client {
|
|
| 42 |
- return p.PClient |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-// IsV1 returns true for V1 plugins and false otherwise. |
|
| 46 |
-func (p *Plugin) IsV1() bool {
|
|
| 47 |
- return false |
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-// Name returns the plugin name. |
|
| 51 |
-func (p *Plugin) Name() string {
|
|
| 52 |
- name := p.PluginObj.Name |
|
| 53 |
- if len(p.PluginObj.Tag) > 0 {
|
|
| 54 |
- // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these |
|
| 55 |
- name += ":" + p.PluginObj.Tag |
|
| 56 |
- } |
|
| 57 |
- return name |
|
| 58 |
-} |
|
| 59 |
- |
|
| 60 |
-// FilterByCap query the plugin for a given capability. |
|
| 61 |
-func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
|
|
| 62 |
- capability = strings.ToLower(capability) |
|
| 63 |
- for _, typ := range p.PluginObj.Manifest.Interface.Types {
|
|
| 64 |
- if typ.Capability == capability && typ.Prefix == "docker" {
|
|
| 65 |
- return p, nil |
|
| 66 |
- } |
|
| 67 |
- } |
|
| 68 |
- return nil, ErrInadequateCapability(capability) |
|
| 69 |
-} |
|
| 70 |
- |
|
| 71 |
-// RemoveFromDisk deletes the plugin's runtime files from disk. |
|
| 72 |
-func (p *Plugin) RemoveFromDisk() error {
|
|
| 73 |
- return os.RemoveAll(p.RuntimeSourcePath) |
|
| 74 |
-} |
|
| 75 |
- |
|
| 76 |
-// InitPlugin populates the plugin object from the plugin manifest file. |
|
| 77 |
-func (p *Plugin) InitPlugin(libRoot string) error {
|
|
| 78 |
- dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json")) |
|
| 79 |
- if err != nil {
|
|
| 80 |
- return err |
|
| 81 |
- } |
|
| 82 |
- err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest) |
|
| 83 |
- dt.Close() |
|
| 84 |
- if err != nil {
|
|
| 85 |
- return err |
|
| 86 |
- } |
|
| 87 |
- |
|
| 88 |
- p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts)) |
|
| 89 |
- for i, mount := range p.PluginObj.Manifest.Mounts {
|
|
| 90 |
- p.PluginObj.Config.Mounts[i] = mount |
|
| 91 |
- } |
|
| 92 |
- p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env)) |
|
| 93 |
- for _, env := range p.PluginObj.Manifest.Env {
|
|
| 94 |
- if env.Value != nil {
|
|
| 95 |
- p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
|
|
| 96 |
- } |
|
| 97 |
- } |
|
| 98 |
- copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value) |
|
| 99 |
- |
|
| 100 |
- f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json")) |
|
| 101 |
- if err != nil {
|
|
| 102 |
- return err |
|
| 103 |
- } |
|
| 104 |
- err = json.NewEncoder(f).Encode(&p.PluginObj.Config) |
|
| 105 |
- f.Close() |
|
| 106 |
- return err |
|
| 107 |
-} |
|
| 108 |
- |
|
| 109 |
-// Set is used to pass arguments to the plugin. |
|
| 110 |
-func (p *Plugin) Set(args []string) error {
|
|
| 111 |
- m := make(map[string]string, len(args)) |
|
| 112 |
- for _, arg := range args {
|
|
| 113 |
- i := strings.Index(arg, "=") |
|
| 114 |
- if i < 0 {
|
|
| 115 |
- return fmt.Errorf("No equal sign '=' found in %s", arg)
|
|
| 116 |
- } |
|
| 117 |
- m[arg[:i]] = arg[i+1:] |
|
| 118 |
- } |
|
| 119 |
- return errors.New("not implemented")
|
|
| 120 |
-} |
|
| 121 |
- |
|
| 122 |
-// ComputePrivileges takes the manifest file and computes the list of access necessary |
|
| 123 |
-// for the plugin on the host. |
|
| 124 |
-func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
|
|
| 125 |
- m := p.PluginObj.Manifest |
|
| 126 |
- var privileges types.PluginPrivileges |
|
| 127 |
- if m.Network.Type != "null" && m.Network.Type != "bridge" {
|
|
| 128 |
- privileges = append(privileges, types.PluginPrivilege{
|
|
| 129 |
- Name: "network", |
|
| 130 |
- Description: "", |
|
| 131 |
- Value: []string{m.Network.Type},
|
|
| 132 |
- }) |
|
| 133 |
- } |
|
| 134 |
- for _, mount := range m.Mounts {
|
|
| 135 |
- if mount.Source != nil {
|
|
| 136 |
- privileges = append(privileges, types.PluginPrivilege{
|
|
| 137 |
- Name: "mount", |
|
| 138 |
- Description: "", |
|
| 139 |
- Value: []string{*mount.Source},
|
|
| 140 |
- }) |
|
| 141 |
- } |
|
| 142 |
- } |
|
| 143 |
- for _, device := range m.Devices {
|
|
| 144 |
- if device.Path != nil {
|
|
| 145 |
- privileges = append(privileges, types.PluginPrivilege{
|
|
| 146 |
- Name: "device", |
|
| 147 |
- Description: "", |
|
| 148 |
- Value: []string{*device.Path},
|
|
| 149 |
- }) |
|
| 150 |
- } |
|
| 151 |
- } |
|
| 152 |
- if len(m.Capabilities) > 0 {
|
|
| 153 |
- privileges = append(privileges, types.PluginPrivilege{
|
|
| 154 |
- Name: "capabilities", |
|
| 155 |
- Description: "", |
|
| 156 |
- Value: m.Capabilities, |
|
| 157 |
- }) |
|
| 158 |
- } |
|
| 159 |
- return privileges |
|
| 160 |
-} |
|
| 161 |
- |
|
| 162 |
-// IsEnabled returns the active state of the plugin. |
|
| 163 |
-func (p *Plugin) IsEnabled() bool {
|
|
| 164 |
- p.RLock() |
|
| 165 |
- defer p.RUnlock() |
|
| 166 |
- |
|
| 167 |
- return p.PluginObj.Enabled |
|
| 168 |
-} |
|
| 169 |
- |
|
| 170 |
-// GetID returns the plugin's ID. |
|
| 171 |
-func (p *Plugin) GetID() string {
|
|
| 172 |
- p.RLock() |
|
| 173 |
- defer p.RUnlock() |
|
| 174 |
- |
|
| 175 |
- return p.PluginObj.ID |
|
| 176 |
-} |
|
| 177 |
- |
|
| 178 |
-// GetSocket returns the plugin socket. |
|
| 179 |
-func (p *Plugin) GetSocket() string {
|
|
| 180 |
- p.RLock() |
|
| 181 |
- defer p.RUnlock() |
|
| 182 |
- |
|
| 183 |
- return p.PluginObj.Manifest.Interface.Socket |
|
| 184 |
-} |
|
| 185 |
- |
|
| 186 |
-// GetTypes returns the interface types of a plugin. |
|
| 187 |
-func (p *Plugin) GetTypes() []types.PluginInterfaceType {
|
|
| 188 |
- p.RLock() |
|
| 189 |
- defer p.RUnlock() |
|
| 190 |
- |
|
| 191 |
- return p.PluginObj.Manifest.Interface.Types |
|
| 192 |
-} |
|
| 193 |
- |
|
| 194 |
-// InitSpec creates an OCI spec from the plugin's config. |
|
| 195 |
-func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
|
|
| 196 |
- rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") |
|
| 197 |
- s.Root = specs.Root{
|
|
| 198 |
- Path: rootfs, |
|
| 199 |
- Readonly: false, // TODO: all plugins should be readonly? settable in manifest? |
|
| 200 |
- } |
|
| 201 |
- |
|
| 202 |
- mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
|
|
| 203 |
- Source: &p.RuntimeSourcePath, |
|
| 204 |
- Destination: defaultPluginRuntimeDestination, |
|
| 205 |
- Type: "bind", |
|
| 206 |
- Options: []string{"rbind", "rshared"},
|
|
| 207 |
- }) |
|
| 208 |
- for _, mount := range mounts {
|
|
| 209 |
- m := specs.Mount{
|
|
| 210 |
- Destination: mount.Destination, |
|
| 211 |
- Type: mount.Type, |
|
| 212 |
- Options: mount.Options, |
|
| 213 |
- } |
|
| 214 |
- // TODO: if nil, then it's required and user didn't set it |
|
| 215 |
- if mount.Source != nil {
|
|
| 216 |
- m.Source = *mount.Source |
|
| 217 |
- } |
|
| 218 |
- if m.Source != "" && m.Type == "bind" {
|
|
| 219 |
- fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks |
|
| 220 |
- if err != nil {
|
|
| 221 |
- return nil, err |
|
| 222 |
- } |
|
| 223 |
- if fi.IsDir() {
|
|
| 224 |
- if err := os.MkdirAll(m.Source, 0700); err != nil {
|
|
| 225 |
- return nil, err |
|
| 226 |
- } |
|
| 227 |
- } |
|
| 228 |
- } |
|
| 229 |
- s.Mounts = append(s.Mounts, m) |
|
| 230 |
- } |
|
| 231 |
- |
|
| 232 |
- envs := make([]string, 1, len(p.PluginObj.Config.Env)+1) |
|
| 233 |
- envs[0] = "PATH=" + system.DefaultPathEnv |
|
| 234 |
- envs = append(envs, p.PluginObj.Config.Env...) |
|
| 235 |
- |
|
| 236 |
- args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...) |
|
| 237 |
- cwd := p.PluginObj.Manifest.Workdir |
|
| 238 |
- if len(cwd) == 0 {
|
|
| 239 |
- cwd = "/" |
|
| 240 |
- } |
|
| 241 |
- s.Process = specs.Process{
|
|
| 242 |
- Terminal: false, |
|
| 243 |
- Args: args, |
|
| 244 |
- Cwd: cwd, |
|
| 245 |
- Env: envs, |
|
| 246 |
- } |
|
| 247 |
- |
|
| 248 |
- return &s, nil |
|
| 249 |
-} |