Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
Kenfe-Mickael Laventure authored on 2016/10/06 23:09:541 | 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 |
-} |