Browse code

Make experimental a runtime flag

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

Kenfe-Mickael Laventure authored on 2016/10/06 23:09:54
Showing 112 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package middleware
1
+
2
+import (
3
+	"net/http"
4
+
5
+	"golang.org/x/net/context"
6
+)
7
+
8
+// ExperimentalMiddleware is a the middleware in charge of adding the
9
+// 'Docker-Experimental' header to every outgoing request
10
+type ExperimentalMiddleware struct {
11
+	experimental string
12
+}
13
+
14
+// NewExperimentalMiddleware creates a new ExperimentalMiddleware
15
+func NewExperimentalMiddleware(experimentalEnabled bool) ExperimentalMiddleware {
16
+	if experimentalEnabled {
17
+		return ExperimentalMiddleware{"true"}
18
+	}
19
+	return ExperimentalMiddleware{"false"}
20
+}
21
+
22
+// WrapHandler returns a new handler function wrapping the previous one in the request chain.
23
+func (e ExperimentalMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
24
+	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
25
+		w.Header().Set("Docker-Experimental", e.experimental)
26
+		return handler(ctx, w, r, vars)
27
+	}
28
+}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package checkpoint
4 2
 
5 3
 import "github.com/docker/docker/api/types"
... ...
@@ -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
-}
16 1
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-// +build !experimental
2
-
3
-package checkpoint
4
-
5
-func (r *checkpointRouter) initRoutes() {}
6
-
7
-// Backend is empty so that the package can compile in non-experimental
8
-type Backend interface{}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package checkpoint
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -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
-}
21 1
deleted file mode 100644
... ...
@@ -1,9 +0,0 @@
1
-// +build !experimental
2
-
3
-package plugin
4
-
5
-func (r *pluginRouter) initRoutes() {}
6
-
7
-// Backend is empty so that the package can compile in non-experimental
8
-// (Needed by volume driver)
9
-type Backend interface{}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package bundlefile
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package bundlefile
4 2
 
5 3
 import (
... ...
@@ -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
-}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package checkpoint
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package checkpoint
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package checkpoint
4 2
 
5 3
 import (
... ...
@@ -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
 }
51 53
deleted file mode 100644
... ...
@@ -1,8 +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
-}
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,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -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
-}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package stack
4 2
 
5 3
 import (
... ...
@@ -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
+}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package client
4 2
 
5 3
 import (
... ...
@@ -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
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package daemon
4 2
 
5 3
 import (
6 4
deleted file mode 100644
... ...
@@ -1,10 +0,0 @@
1
-// +build !experimental
2
-
3
-package daemon
4
-
5
-import (
6
-	"github.com/spf13/pflag"
7
-)
8
-
9
-func (config *Config) attachExperimentalFlags(cmd *pflag.FlagSet) {
10
-}
... ...
@@ -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
-}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package graphdriver
4 2
 
5 3
 import (
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
-}
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package graphdriver
4 2
 
5 3
 import (
... ...
@@ -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,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package distribution
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package distribution
4 2
 
5 3
 import (
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package distribution
4 2
 
5 3
 import "errors"
... ...
@@ -1,5 +1,3 @@
1
-// +build experimental
2
-
3 1
 package plugin
4 2
 
5 3
 import (
... ...
@@ -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
-}
250 1
deleted file mode 100644
... ...
@@ -1,9 +0,0 @@
1
-// +build experimental
2
-
3
-package utils
4
-
5
-// ExperimentalBuild is a stub which always returns true for
6
-// builds that include the "experimental" build tag
7
-func ExperimentalBuild() bool {
8
-	return true
9
-}
10 1
deleted file mode 100644
... ...
@@ -1,9 +0,0 @@
1
-// +build !experimental
2
-
3
-package utils
4
-
5
-// ExperimentalBuild is a stub which always returns false for
6
-// builds that do not include the "experimental" build tag
7
-func ExperimentalBuild() bool {
8
-	return false
9
-}