From 7781a1bf0fef748877326632b88e92fbf3c90daa Mon Sep 17 00:00:00 2001 From: Kenfe-Mickael Laventure Date: Thu, 6 Oct 2016 07:09:54 -0700 Subject: [PATCH] Make experimental a runtime flag Signed-off-by: Kenfe-Mickael Laventure --- api/server/middleware/experimental.go | 29 ++ api/server/router/checkpoint/backend.go | 2 - api/server/router/checkpoint/checkpoint.go | 8 + .../checkpoint/checkpoint_experimental.go | 15 -- .../router/checkpoint/checkpoint_regular.go | 8 - .../router/checkpoint/checkpoint_routes.go | 2 - api/server/router/plugin/backend.go | 2 - api/server/router/plugin/plugin.go | 13 + .../router/plugin/plugin_experimental.go | 20 -- api/server/router/plugin/plugin_regular.go | 9 - api/server/router/plugin/plugin_routes.go | 2 - cli/command/bundlefile/bundlefile.go | 2 - cli/command/bundlefile/bundlefile_test.go | 2 - cli/command/checkpoint/cmd.go | 20 +- cli/command/checkpoint/cmd_experimental.go | 30 --- cli/command/checkpoint/create.go | 2 - cli/command/checkpoint/list.go | 2 - cli/command/checkpoint/remove.go | 2 - cli/command/cli.go | 28 +- cli/command/commands/commands.go | 14 +- cli/command/container/start.go | 4 +- cli/command/container/start_utils.go | 8 - .../container/start_utils_experimental.go | 9 - cli/command/plugin/cmd.go | 26 +- cli/command/plugin/cmd_experimental.go | 35 --- cli/command/plugin/disable.go | 2 - cli/command/plugin/enable.go | 2 - cli/command/plugin/inspect.go | 2 - cli/command/plugin/install.go | 2 - cli/command/plugin/list.go | 2 - cli/command/plugin/push.go | 2 - cli/command/plugin/remove.go | 2 - cli/command/plugin/set.go | 2 - cli/command/stack/cmd.go | 32 ++- cli/command/stack/cmd_experimental.go | 40 --- cli/command/stack/common.go | 2 - cli/command/stack/config.go | 2 - cli/command/stack/deploy.go | 2 - cli/command/stack/list.go | 2 - cli/command/stack/opts.go | 2 - cli/command/stack/ps.go | 2 - cli/command/stack/remove.go | 2 - cli/command/stack/services.go | 2 - cli/command/system/info.go | 2 +- cli/command/system/version.go | 23 +- client/interface.go | 1 + client/interface_experimental.go | 9 +- client/interface_stable.go | 3 +- client/ping.go | 19 ++ client/plugin_disable.go | 2 - client/plugin_disable_test.go | 2 - client/plugin_enable.go | 2 - client/plugin_enable_test.go | 2 - client/plugin_inspect.go | 2 - client/plugin_inspect_test.go | 2 - client/plugin_install.go | 2 - client/plugin_list.go | 2 - client/plugin_list_test.go | 2 - client/plugin_push.go | 2 - client/plugin_push_test.go | 2 - client/plugin_remove.go | 2 - client/plugin_remove_test.go | 2 - client/plugin_set.go | 2 - client/plugin_set_test.go | 2 - cmd/docker/docker.go | 6 +- cmd/dockerd/daemon.go | 5 +- cmd/dockerd/docker.go | 7 +- cmd/dockerd/routes.go | 13 - cmd/dockerd/routes_experimental.go | 5 +- daemon/config.go | 3 + daemon/config_experimental.go | 2 - daemon/config_stub.go | 10 - daemon/daemon.go | 13 +- daemon/daemon_experimental.go | 14 +- daemon/daemon_stub.go | 19 -- daemon/graphdriver/plugin.go | 2 - daemon/graphdriver/plugin_unsupported.go | 9 - daemon/graphdriver/proxy.go | 2 - daemon/info.go | 4 +- docs/reference/api/docker_remote_api.md | 2 +- docs/reference/commandline/dockerd.md | 3 + hack/make.sh | 6 - hack/make/.integration-daemon-start | 5 + hack/make/build-deb | 4 - hack/make/build-rpm | 3 +- hack/make/release-deb | 2 +- hack/make/release-rpm | 2 +- integration-cli/check_test.go | 6 +- integration-cli/daemon.go | 3 + .../docker_cli_daemon_experimental_test.go | 20 +- .../docker_cli_experimental_test.go | 29 +- ...cker_cli_external_graphdriver_unix_test.go | 8 +- integration-cli/docker_cli_help_test.go | 3 +- integration-cli/docker_cli_info_test.go | 5 +- integration-cli/docker_cli_stack_test.go | 7 +- .../docker_experimental_network_test.go | 39 +-- integration-cli/docker_test_vars.go | 8 +- integration-cli/requirements.go | 5 +- man/dockerd.8.md | 4 + plugin/backend.go | 2 - plugin/distribution/pull.go | 2 - plugin/distribution/push.go | 2 - plugin/distribution/types.go | 2 - plugin/manager.go | 2 - plugin/manager_linux.go | 4 +- plugin/manager_windows.go | 4 +- plugin/store/store.go | 231 +++++++++++++++- plugin/store/store_experimental.go | 242 ----------------- plugin/v2/plugin.go | 240 +++++++++++++++++ plugin/v2/plugin_experimental.go | 249 ------------------ utils/experimental.go | 9 - utils/stubs.go | 9 - 112 files changed, 782 insertions(+), 979 deletions(-) create mode 100644 api/server/middleware/experimental.go delete mode 100644 api/server/router/checkpoint/checkpoint_experimental.go delete mode 100644 api/server/router/checkpoint/checkpoint_regular.go delete mode 100644 api/server/router/plugin/plugin_experimental.go delete mode 100644 api/server/router/plugin/plugin_regular.go delete mode 100644 cli/command/checkpoint/cmd_experimental.go delete mode 100644 cli/command/container/start_utils.go delete mode 100644 cli/command/container/start_utils_experimental.go delete mode 100644 cli/command/plugin/cmd_experimental.go delete mode 100644 cli/command/stack/cmd_experimental.go create mode 100644 client/ping.go delete mode 100644 cmd/dockerd/routes.go delete mode 100644 daemon/config_stub.go delete mode 100644 daemon/daemon_stub.go delete mode 100644 daemon/graphdriver/plugin_unsupported.go delete mode 100644 plugin/store/store_experimental.go delete mode 100644 plugin/v2/plugin_experimental.go delete mode 100644 utils/experimental.go delete mode 100644 utils/stubs.go diff --git a/api/server/middleware/experimental.go b/api/server/middleware/experimental.go new file mode 100644 index 0000000000..b8f56e88b4 --- /dev/null +++ b/api/server/middleware/experimental.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "net/http" + + "golang.org/x/net/context" +) + +// ExperimentalMiddleware is a the middleware in charge of adding the +// 'Docker-Experimental' header to every outgoing request +type ExperimentalMiddleware struct { + experimental string +} + +// NewExperimentalMiddleware creates a new ExperimentalMiddleware +func NewExperimentalMiddleware(experimentalEnabled bool) ExperimentalMiddleware { + if experimentalEnabled { + return ExperimentalMiddleware{"true"} + } + return ExperimentalMiddleware{"false"} +} + +// WrapHandler returns a new handler function wrapping the previous one in the request chain. +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 { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + w.Header().Set("Docker-Experimental", e.experimental) + return handler(ctx, w, r, vars) + } +} diff --git a/api/server/router/checkpoint/backend.go b/api/server/router/checkpoint/backend.go index a6a4dd0757..e1b68f44cd 100644 --- a/api/server/router/checkpoint/backend.go +++ b/api/server/router/checkpoint/backend.go @@ -1,5 +1,3 @@ -// +build experimental - package checkpoint import "github.com/docker/docker/api/types" diff --git a/api/server/router/checkpoint/checkpoint.go b/api/server/router/checkpoint/checkpoint.go index 65f8961f5c..a668176a78 100644 --- a/api/server/router/checkpoint/checkpoint.go +++ b/api/server/router/checkpoint/checkpoint.go @@ -26,3 +26,11 @@ func NewRouter(b Backend, decoder httputils.ContainerDecoder) router.Router { func (r *checkpointRouter) Routes() []router.Route { return r.routes } + +func (r *checkpointRouter) initRoutes() { + r.routes = []router.Route{ + router.NewGetRoute("/containers/{name:.*}/checkpoints", r.getContainerCheckpoints), + router.NewPostRoute("/containers/{name:.*}/checkpoints", r.postContainerCheckpoint), + router.NewDeleteRoute("/containers/{name}/checkpoints/{checkpoint}", r.deleteContainerCheckpoint), + } +} diff --git a/api/server/router/checkpoint/checkpoint_experimental.go b/api/server/router/checkpoint/checkpoint_experimental.go deleted file mode 100644 index f0ac015ffa..0000000000 --- a/api/server/router/checkpoint/checkpoint_experimental.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build experimental - -package checkpoint - -import ( - "github.com/docker/docker/api/server/router" -) - -func (r *checkpointRouter) initRoutes() { - r.routes = []router.Route{ - router.NewGetRoute("/containers/{name:.*}/checkpoints", r.getContainerCheckpoints), - router.NewPostRoute("/containers/{name:.*}/checkpoints", r.postContainerCheckpoint), - router.NewDeleteRoute("/containers/{name}/checkpoints/{checkpoint}", r.deleteContainerCheckpoint), - } -} diff --git a/api/server/router/checkpoint/checkpoint_regular.go b/api/server/router/checkpoint/checkpoint_regular.go deleted file mode 100644 index df93bba977..0000000000 --- a/api/server/router/checkpoint/checkpoint_regular.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !experimental - -package checkpoint - -func (r *checkpointRouter) initRoutes() {} - -// Backend is empty so that the package can compile in non-experimental -type Backend interface{} diff --git a/api/server/router/checkpoint/checkpoint_routes.go b/api/server/router/checkpoint/checkpoint_routes.go index ed0bfccd63..35e9d26c7a 100644 --- a/api/server/router/checkpoint/checkpoint_routes.go +++ b/api/server/router/checkpoint/checkpoint_routes.go @@ -1,5 +1,3 @@ -// +build experimental - package checkpoint import ( diff --git a/api/server/router/plugin/backend.go b/api/server/router/plugin/backend.go index 2f28f992b9..53be49b12a 100644 --- a/api/server/router/plugin/backend.go +++ b/api/server/router/plugin/backend.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/api/server/router/plugin/plugin.go b/api/server/router/plugin/plugin.go index 999ba6b746..c10510a4db 100644 --- a/api/server/router/plugin/plugin.go +++ b/api/server/router/plugin/plugin.go @@ -21,3 +21,16 @@ func NewRouter(b Backend) router.Router { func (r *pluginRouter) Routes() []router.Route { return r.routes } + +func (r *pluginRouter) initRoutes() { + r.routes = []router.Route{ + router.NewGetRoute("/plugins", r.listPlugins), + router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin), + router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin), + router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH? + router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin), + router.NewPostRoute("/plugins/pull", r.pullPlugin), + router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin), + router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin), + } +} diff --git a/api/server/router/plugin/plugin_experimental.go b/api/server/router/plugin/plugin_experimental.go deleted file mode 100644 index 4e437ee65e..0000000000 --- a/api/server/router/plugin/plugin_experimental.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build experimental - -package plugin - -import ( - "github.com/docker/docker/api/server/router" -) - -func (r *pluginRouter) initRoutes() { - r.routes = []router.Route{ - router.NewGetRoute("/plugins", r.listPlugins), - router.NewGetRoute("/plugins/{name:.*}", r.inspectPlugin), - router.NewDeleteRoute("/plugins/{name:.*}", r.removePlugin), - router.NewPostRoute("/plugins/{name:.*}/enable", r.enablePlugin), // PATCH? - router.NewPostRoute("/plugins/{name:.*}/disable", r.disablePlugin), - router.NewPostRoute("/plugins/pull", r.pullPlugin), - router.NewPostRoute("/plugins/{name:.*}/push", r.pushPlugin), - router.NewPostRoute("/plugins/{name:.*}/set", r.setPlugin), - } -} diff --git a/api/server/router/plugin/plugin_regular.go b/api/server/router/plugin/plugin_regular.go deleted file mode 100644 index f987faecd2..0000000000 --- a/api/server/router/plugin/plugin_regular.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !experimental - -package plugin - -func (r *pluginRouter) initRoutes() {} - -// Backend is empty so that the package can compile in non-experimental -// (Needed by volume driver) -type Backend interface{} diff --git a/api/server/router/plugin/plugin_routes.go b/api/server/router/plugin/plugin_routes.go index 0fd1f915b4..ec1a4ac758 100644 --- a/api/server/router/plugin/plugin_routes.go +++ b/api/server/router/plugin/plugin_routes.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/bundlefile/bundlefile.go b/cli/command/bundlefile/bundlefile.go index 75c2d07433..7fd1e4f6c4 100644 --- a/cli/command/bundlefile/bundlefile.go +++ b/cli/command/bundlefile/bundlefile.go @@ -1,5 +1,3 @@ -// +build experimental - package bundlefile import ( diff --git a/cli/command/bundlefile/bundlefile_test.go b/cli/command/bundlefile/bundlefile_test.go index 1ff8235ff8..c343410df3 100644 --- a/cli/command/bundlefile/bundlefile_test.go +++ b/cli/command/bundlefile/bundlefile_test.go @@ -1,5 +1,3 @@ -// +build experimental - package bundlefile import ( diff --git a/cli/command/checkpoint/cmd.go b/cli/command/checkpoint/cmd.go index 7c3950bba8..84084ab716 100644 --- a/cli/command/checkpoint/cmd.go +++ b/cli/command/checkpoint/cmd.go @@ -1,13 +1,27 @@ -// +build !experimental - package checkpoint import ( + "fmt" + + "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/spf13/cobra" ) // NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental) func NewCheckpointCommand(dockerCli *command.DockerCli) *cobra.Command { - return &cobra.Command{} + cmd := &cobra.Command{ + Use: "checkpoint", + Short: "Manage checkpoints", + Args: cli.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) + }, + } + cmd.AddCommand( + newCreateCommand(dockerCli), + newListCommand(dockerCli), + newRemoveCommand(dockerCli), + ) + return cmd } diff --git a/cli/command/checkpoint/cmd_experimental.go b/cli/command/checkpoint/cmd_experimental.go deleted file mode 100644 index 3c89545778..0000000000 --- a/cli/command/checkpoint/cmd_experimental.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build experimental - -package checkpoint - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/docker/docker/cli" - "github.com/docker/docker/cli/command" -) - -// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental) -func NewCheckpointCommand(dockerCli *command.DockerCli) *cobra.Command { - cmd := &cobra.Command{ - Use: "checkpoint", - Short: "Manage checkpoints", - Args: cli.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) - }, - } - cmd.AddCommand( - newCreateCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), - ) - return cmd -} diff --git a/cli/command/checkpoint/create.go b/cli/command/checkpoint/create.go index f214574556..d369718119 100644 --- a/cli/command/checkpoint/create.go +++ b/cli/command/checkpoint/create.go @@ -1,5 +1,3 @@ -// +build experimental - package checkpoint import ( diff --git a/cli/command/checkpoint/list.go b/cli/command/checkpoint/list.go index 6d22531d45..7ba035890b 100644 --- a/cli/command/checkpoint/list.go +++ b/cli/command/checkpoint/list.go @@ -1,5 +1,3 @@ -// +build experimental - package checkpoint import ( diff --git a/cli/command/checkpoint/remove.go b/cli/command/checkpoint/remove.go index 6605c5e472..82ce62312b 100644 --- a/cli/command/checkpoint/remove.go +++ b/cli/command/checkpoint/remove.go @@ -1,5 +1,3 @@ -// +build experimental - package checkpoint import ( diff --git a/cli/command/cli.go b/cli/command/cli.go index 9ca28765cc..be82ecf6f3 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -19,6 +19,7 @@ import ( dopts "github.com/docker/docker/opts" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" + "golang.org/x/net/context" ) // Streams is an interface which exposes the standard input and output streams @@ -31,12 +32,27 @@ type Streams interface { // DockerCli represents the docker command line client. // Instances of the client can be returned from NewDockerCli. type DockerCli struct { - configFile *configfile.ConfigFile - in *InStream - out *OutStream - err io.Writer - keyFile string - client client.APIClient + configFile *configfile.ConfigFile + in *InStream + out *OutStream + err io.Writer + keyFile string + client client.APIClient + hasExperimental *bool +} + +// HasExperimental returns true if experimental features are accessible +func (cli *DockerCli) HasExperimental() bool { + if cli.hasExperimental == nil { + if cli.client == nil { + cli.Initialize(cliflags.NewClientOptions()) + } + enabled := false + cli.hasExperimental = &enabled + enabled, _ = cli.client.Ping(context.Background()) + } + + return *cli.hasExperimental } // Client returns the APIClient diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index 6d0deb1d90..425f90ba7d 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -24,8 +24,6 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) { cmd.AddCommand( node.NewNodeCommand(dockerCli), service.NewServiceCommand(dockerCli), - stack.NewStackCommand(dockerCli), - stack.NewTopLevelDeployCommand(dockerCli), swarm.NewSwarmCommand(dockerCli), container.NewContainerCommand(dockerCli), image.NewImageCommand(dockerCli), @@ -72,9 +70,17 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) { hide(image.NewSaveCommand(dockerCli)), hide(image.NewTagCommand(dockerCli)), hide(system.NewInspectCommand(dockerCli)), - checkpoint.NewCheckpointCommand(dockerCli), - plugin.NewPluginCommand(dockerCli), ) + + if dockerCli.HasExperimental() { + cmd.AddCommand( + stack.NewStackCommand(dockerCli), + stack.NewTopLevelDeployCommand(dockerCli), + checkpoint.NewCheckpointCommand(dockerCli), + plugin.NewPluginCommand(dockerCli), + ) + } + } func hide(cmd *cobra.Command) *cobra.Command { diff --git a/cli/command/container/start.go b/cli/command/container/start.go index 4c31f9bf97..8693b3a550 100644 --- a/cli/command/container/start.go +++ b/cli/command/container/start.go @@ -44,7 +44,9 @@ func NewStartCommand(dockerCli *command.DockerCli) *cobra.Command { flags.BoolVarP(&opts.openStdin, "interactive", "i", false, "Attach container's STDIN") flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") - addExperimentalStartFlags(flags, &opts) + if dockerCli.HasExperimental() { + flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") + } return cmd } diff --git a/cli/command/container/start_utils.go b/cli/command/container/start_utils.go deleted file mode 100644 index 689d742f06..0000000000 --- a/cli/command/container/start_utils.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !experimental - -package container - -import "github.com/spf13/pflag" - -func addExperimentalStartFlags(flags *pflag.FlagSet, opts *startOptions) { -} diff --git a/cli/command/container/start_utils_experimental.go b/cli/command/container/start_utils_experimental.go deleted file mode 100644 index 43c64f431c..0000000000 --- a/cli/command/container/start_utils_experimental.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build experimental - -package container - -import "github.com/spf13/pflag" - -func addExperimentalStartFlags(flags *pflag.FlagSet, opts *startOptions) { - flags.StringVar(&opts.checkpoint, "checkpoint", "", "Restore from this checkpoint") -} diff --git a/cli/command/plugin/cmd.go b/cli/command/plugin/cmd.go index 10074218dd..80fa61cb1c 100644 --- a/cli/command/plugin/cmd.go +++ b/cli/command/plugin/cmd.go @@ -1,13 +1,33 @@ -// +build !experimental - package plugin import ( + "fmt" + + "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/spf13/cobra" ) // NewPluginCommand returns a cobra command for `plugin` subcommands func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command { - return &cobra.Command{} + cmd := &cobra.Command{ + Use: "plugin", + Short: "Manage plugins", + Args: cli.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) + }, + } + + cmd.AddCommand( + newDisableCommand(dockerCli), + newEnableCommand(dockerCli), + newInspectCommand(dockerCli), + newInstallCommand(dockerCli), + newListCommand(dockerCli), + newRemoveCommand(dockerCli), + newSetCommand(dockerCli), + newPushCommand(dockerCli), + ) + return cmd } diff --git a/cli/command/plugin/cmd_experimental.go b/cli/command/plugin/cmd_experimental.go deleted file mode 100644 index 8bb3416097..0000000000 --- a/cli/command/plugin/cmd_experimental.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build experimental - -package plugin - -import ( - "fmt" - - "github.com/docker/docker/cli" - "github.com/docker/docker/cli/command" - "github.com/spf13/cobra" -) - -// NewPluginCommand returns a cobra command for `plugin` subcommands -func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command { - cmd := &cobra.Command{ - Use: "plugin", - Short: "Manage plugins", - Args: cli.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) - }, - } - - cmd.AddCommand( - newDisableCommand(dockerCli), - newEnableCommand(dockerCli), - newInspectCommand(dockerCli), - newInstallCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), - newSetCommand(dockerCli), - newPushCommand(dockerCli), - ) - return cmd -} diff --git a/cli/command/plugin/disable.go b/cli/command/plugin/disable.go index 3b5c69a018..9089a3cf68 100644 --- a/cli/command/plugin/disable.go +++ b/cli/command/plugin/disable.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/plugin/enable.go b/cli/command/plugin/enable.go index cfc3580f43..0fd8f469dd 100644 --- a/cli/command/plugin/enable.go +++ b/cli/command/plugin/enable.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/plugin/inspect.go b/cli/command/plugin/inspect.go index e5059629e5..13c7fa72d8 100644 --- a/cli/command/plugin/inspect.go +++ b/cli/command/plugin/inspect.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index e90e8d1224..3989a35ce6 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/plugin/list.go b/cli/command/plugin/list.go index b8f5e5e08a..9d4b46d120 100644 --- a/cli/command/plugin/list.go +++ b/cli/command/plugin/list.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 360830902e..4e176bea3e 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/plugin/remove.go b/cli/command/plugin/remove.go index 4222690a4f..7a51dce06d 100644 --- a/cli/command/plugin/remove.go +++ b/cli/command/plugin/remove.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/plugin/set.go b/cli/command/plugin/set.go index f2d3b082c6..e58ea63bc0 100644 --- a/cli/command/plugin/set.go +++ b/cli/command/plugin/set.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/cli/command/stack/cmd.go b/cli/command/stack/cmd.go index 51cb2d1bcf..49fcedf209 100644 --- a/cli/command/stack/cmd.go +++ b/cli/command/stack/cmd.go @@ -1,18 +1,38 @@ -// +build !experimental - package stack import ( + "fmt" + + "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/spf13/cobra" ) -// NewStackCommand returns no command +// NewStackCommand returns a cobra command for `stack` subcommands func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command { - return &cobra.Command{} + cmd := &cobra.Command{ + Use: "stack", + Short: "Manage Docker stacks", + Args: cli.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) + }, + } + cmd.AddCommand( + newConfigCommand(dockerCli), + newDeployCommand(dockerCli), + newListCommand(dockerCli), + newRemoveCommand(dockerCli), + newServicesCommand(dockerCli), + newPsCommand(dockerCli), + ) + return cmd } -// NewTopLevelDeployCommand returns no command +// NewTopLevelDeployCommand returns a command for `docker deploy` func NewTopLevelDeployCommand(dockerCli *command.DockerCli) *cobra.Command { - return &cobra.Command{} + cmd := newDeployCommand(dockerCli) + // Remove the aliases at the top level + cmd.Aliases = []string{} + return cmd } diff --git a/cli/command/stack/cmd_experimental.go b/cli/command/stack/cmd_experimental.go deleted file mode 100644 index b32d925330..0000000000 --- a/cli/command/stack/cmd_experimental.go +++ /dev/null @@ -1,40 +0,0 @@ -// +build experimental - -package stack - -import ( - "fmt" - - "github.com/docker/docker/cli" - "github.com/docker/docker/cli/command" - "github.com/spf13/cobra" -) - -// NewStackCommand returns a cobra command for `stack` subcommands -func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command { - cmd := &cobra.Command{ - Use: "stack", - Short: "Manage Docker stacks", - Args: cli.NoArgs, - Run: func(cmd *cobra.Command, args []string) { - fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString()) - }, - } - cmd.AddCommand( - newConfigCommand(dockerCli), - newDeployCommand(dockerCli), - newListCommand(dockerCli), - newRemoveCommand(dockerCli), - newServicesCommand(dockerCli), - newPsCommand(dockerCli), - ) - return cmd -} - -// NewTopLevelDeployCommand returns a command for `docker deploy` -func NewTopLevelDeployCommand(dockerCli *command.DockerCli) *cobra.Command { - cmd := newDeployCommand(dockerCli) - // Remove the aliases at the top level - cmd.Aliases = []string{} - return cmd -} diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index 2afdb5147d..3e3a35faac 100644 --- a/cli/command/stack/common.go +++ b/cli/command/stack/common.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/stack/config.go b/cli/command/stack/config.go index bdcf7d4835..56e554a86e 100644 --- a/cli/command/stack/config.go +++ b/cli/command/stack/config.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index bf31dd7753..fcf55fb7d2 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index 9fe626d96d..5d87cecb5f 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/stack/opts.go b/cli/command/stack/opts.go index eef4d0e45b..5f2d8b5d0a 100644 --- a/cli/command/stack/opts.go +++ b/cli/command/stack/opts.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index c4683b68a0..2fff3de1fa 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index 6ab005d71d..8137903d47 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index 60f52c30c7..50b50179de 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -1,5 +1,3 @@ -// +build experimental - package stack import ( diff --git a/cli/command/system/info.go b/cli/command/system/info.go index fb5ea17d7b..0f94373d35 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -225,7 +225,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { } } - ioutils.FprintfIfTrue(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild) + fmt.Fprintf(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild) if info.ClusterStore != "" { fmt.Fprintf(dockerCli.Out(), "Cluster Store: %s\n", info.ClusterStore) } diff --git a/cli/command/system/version.go b/cli/command/system/version.go index 7959bf564f..0b484cb3b9 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -10,7 +10,6 @@ import ( "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/dockerversion" - "github.com/docker/docker/utils" "github.com/docker/docker/utils/templates" "github.com/spf13/cobra" ) @@ -21,8 +20,7 @@ var versionTemplate = `Client: Go version: {{.Client.GoVersion}} Git commit: {{.Client.GitCommit}} Built: {{.Client.BuildTime}} - OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}} - Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}} + OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .ServerOK}} Server: Version: {{.Server.Version}} @@ -30,8 +28,8 @@ Server: Go version: {{.Server.GoVersion}} Git commit: {{.Server.GitCommit}} Built: {{.Server.BuildTime}} - OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}} - Experimental: {{.Server.Experimental}}{{end}}{{end}}` + OS/Arch: {{.Server.Os}}/{{.Server.Arch}} + Experimental: {{.Server.Experimental}}{{end}}` type versionOptions struct { format string @@ -73,14 +71,13 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error { vd := types.VersionResponse{ Client: &types.Version{ - Version: dockerversion.Version, - APIVersion: dockerCli.Client().ClientVersion(), - GoVersion: runtime.Version(), - GitCommit: dockerversion.GitCommit, - BuildTime: dockerversion.BuildTime, - Os: runtime.GOOS, - Arch: runtime.GOARCH, - Experimental: utils.ExperimentalBuild(), + Version: dockerversion.Version, + APIVersion: dockerCli.Client().ClientVersion(), + GoVersion: runtime.Version(), + GitCommit: dockerversion.GitCommit, + BuildTime: dockerversion.BuildTime, + Os: runtime.GOOS, + Arch: runtime.GOARCH, }, } diff --git a/client/interface.go b/client/interface.go index 4d450d8316..f919612163 100644 --- a/client/interface.go +++ b/client/interface.go @@ -127,6 +127,7 @@ type SystemAPIClient interface { Info(ctx context.Context) (types.Info, error) RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) DiskUsage(ctx context.Context) (types.DiskUsage, error) + Ping(ctx context.Context) (bool, error) } // VolumeAPIClient defines API client methods for the volumes diff --git a/client/interface_experimental.go b/client/interface_experimental.go index 1ddc517c9a..ddb9f79b5a 100644 --- a/client/interface_experimental.go +++ b/client/interface_experimental.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( @@ -7,9 +5,7 @@ import ( "golang.org/x/net/context" ) -// APIClient is an interface that clients that talk with a docker server must implement. -type APIClient interface { - CommonAPIClient +type apiClientExperimental interface { CheckpointAPIClient PluginAPIClient } @@ -32,6 +28,3 @@ type PluginAPIClient interface { PluginSet(ctx context.Context, name string, args []string) error PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) } - -// Ensure that Client always implements APIClient. -var _ APIClient = &Client{} diff --git a/client/interface_stable.go b/client/interface_stable.go index 496f522d51..cc90a3cbb9 100644 --- a/client/interface_stable.go +++ b/client/interface_stable.go @@ -1,10 +1,9 @@ -// +build !experimental - package client // APIClient is an interface that clients that talk with a docker server must implement. type APIClient interface { CommonAPIClient + apiClientExperimental } // Ensure that Client always implements APIClient. diff --git a/client/ping.go b/client/ping.go new file mode 100644 index 0000000000..5e99e1bba1 --- /dev/null +++ b/client/ping.go @@ -0,0 +1,19 @@ +package client + +import "golang.org/x/net/context" + +// Ping pings the server and return the value of the "Docker-Experimental" header +func (cli *Client) Ping(ctx context.Context) (bool, error) { + serverResp, err := cli.get(ctx, "/_ping", nil, nil) + if err != nil { + return false, err + } + defer ensureReaderClosed(serverResp) + + exp := serverResp.header.Get("Docker-Experimental") + if exp != "true" { + return false, nil + } + + return true, nil +} diff --git a/client/plugin_disable.go b/client/plugin_disable.go index 893fc6e823..51e4565125 100644 --- a/client/plugin_disable.go +++ b/client/plugin_disable.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_disable_test.go b/client/plugin_disable_test.go index 7b50b25730..2818008ab9 100644 --- a/client/plugin_disable_test.go +++ b/client/plugin_disable_test.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_enable.go b/client/plugin_enable.go index 84422abc79..8109814ddb 100644 --- a/client/plugin_enable.go +++ b/client/plugin_enable.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_enable_test.go b/client/plugin_enable_test.go index a2b57be4c2..d919914e75 100644 --- a/client/plugin_enable_test.go +++ b/client/plugin_enable_test.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_inspect.go b/client/plugin_inspect.go index 7ba8db57a8..e9474b5a98 100644 --- a/client/plugin_inspect.go +++ b/client/plugin_inspect.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_inspect_test.go b/client/plugin_inspect_test.go index df4ca9c841..fae407eb9b 100644 --- a/client/plugin_inspect_test.go +++ b/client/plugin_inspect_test.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_install.go b/client/plugin_install.go index 9ee32eea92..636c95364d 100644 --- a/client/plugin_install.go +++ b/client/plugin_install.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_list.go b/client/plugin_list.go index 48b470247b..88c480a3e1 100644 --- a/client/plugin_list.go +++ b/client/plugin_list.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_list_test.go b/client/plugin_list_test.go index 95c51595ca..173e4b87f5 100644 --- a/client/plugin_list_test.go +++ b/client/plugin_list_test.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_push.go b/client/plugin_push.go index 3afea5ed79..d83bbdc358 100644 --- a/client/plugin_push.go +++ b/client/plugin_push.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_push_test.go b/client/plugin_push_test.go index ed685694ec..efdbdc6db1 100644 --- a/client/plugin_push_test.go +++ b/client/plugin_push_test.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_remove.go b/client/plugin_remove.go index 1483f2854d..b017e4d348 100644 --- a/client/plugin_remove.go +++ b/client/plugin_remove.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_remove_test.go b/client/plugin_remove_test.go index fc789fd04d..a15f1661f6 100644 --- a/client/plugin_remove_test.go +++ b/client/plugin_remove_test.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_set.go b/client/plugin_set.go index fb40f38b22..3260d2a90d 100644 --- a/client/plugin_set.go +++ b/client/plugin_set.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/client/plugin_set_test.go b/client/plugin_set_test.go index fa1cde044e..2450254463 100644 --- a/client/plugin_set_test.go +++ b/client/plugin_set_test.go @@ -1,5 +1,3 @@ -// +build experimental - package client import ( diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index d412a38b28..e01e4ba5c7 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -91,11 +91,7 @@ func main() { } func showVersion() { - if utils.ExperimentalBuild() { - fmt.Printf("Docker version %s, build %s, experimental\n", dockerversion.Version, dockerversion.GitCommit) - } else { - fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) - } + fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) } func dockerPreRun(opts *cliflags.ClientOptions) { diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index d85cde45c9..57e9a30d71 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -129,7 +129,7 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) { utils.EnableDebug() } - if utils.ExperimentalBuild() { + if cli.Config.Experimental { logrus.Warn("Running experimental build") } @@ -435,6 +435,9 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) { func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config) { v := cfg.Version + exp := middleware.NewExperimentalMiddleware(cli.d.HasExperimental()) + s.UseMiddleware(exp) + vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion) s.UseMiddleware(vm) diff --git a/cmd/dockerd/docker.go b/cmd/dockerd/docker.go index c9d7235bab..5ce495c2e8 100644 --- a/cmd/dockerd/docker.go +++ b/cmd/dockerd/docker.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/dockerversion" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/utils" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -85,11 +84,7 @@ func runDaemon(opts daemonOptions) error { } func showVersion() { - if utils.ExperimentalBuild() { - fmt.Printf("Docker version %s, build %s, experimental\n", dockerversion.Version, dockerversion.GitCommit) - } else { - fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) - } + fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit) } func main() { diff --git a/cmd/dockerd/routes.go b/cmd/dockerd/routes.go deleted file mode 100644 index 767ff27ce6..0000000000 --- a/cmd/dockerd/routes.go +++ /dev/null @@ -1,13 +0,0 @@ -// +build !experimental - -package main - -import ( - "github.com/docker/docker/api/server/httputils" - "github.com/docker/docker/api/server/router" - "github.com/docker/docker/daemon" -) - -func addExperimentalRouters(routers []router.Router, d *daemon.Daemon, decoder httputils.ContainerDecoder) []router.Router { - return routers -} diff --git a/cmd/dockerd/routes_experimental.go b/cmd/dockerd/routes_experimental.go index adeb145cb2..4a3142ec0f 100644 --- a/cmd/dockerd/routes_experimental.go +++ b/cmd/dockerd/routes_experimental.go @@ -1,5 +1,3 @@ -// +build experimental - package main import ( @@ -12,5 +10,8 @@ import ( ) func addExperimentalRouters(routers []router.Router, d *daemon.Daemon, decoder httputils.ContainerDecoder) []router.Router { + if !d.HasExperimental() { + return []router.Router{} + } return append(routers, checkpointrouter.NewRouter(d, decoder), pluginrouter.NewRouter(plugin.GetManager())) } diff --git a/daemon/config.go b/daemon/config.go index bdd1ea5ab9..97cfbea2d6 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -153,6 +153,8 @@ type CommonConfig struct { reloadLock sync.Mutex valuesSet map[string]interface{} + + Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not } // InstallCommonFlags adds flags to the pflag.FlagSet to configure the daemon @@ -187,6 +189,7 @@ func (config *Config) InstallCommonFlags(flags *pflag.FlagSet) { flags.IntVar(&config.ShutdownTimeout, "shutdown-timeout", defaultShutdownTimeout, "Set the default shutdown timeout") flags.StringVar(&config.SwarmDefaultAdvertiseAddr, "swarm-default-advertise-addr", "", "Set default address or interface for swarm advertised address") + flags.BoolVar(&config.Experimental, "experimental", false, "Enable experimental features") config.MaxConcurrentDownloads = &maxConcurrentDownloads config.MaxConcurrentUploads = &maxConcurrentUploads diff --git a/daemon/config_experimental.go b/daemon/config_experimental.go index 55e87986b1..963a51e5a3 100644 --- a/daemon/config_experimental.go +++ b/daemon/config_experimental.go @@ -1,5 +1,3 @@ -// +build experimental - package daemon import ( diff --git a/daemon/config_stub.go b/daemon/config_stub.go deleted file mode 100644 index fb64813e23..0000000000 --- a/daemon/config_stub.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build !experimental - -package daemon - -import ( - "github.com/spf13/pflag" -) - -func (config *Config) attachExperimentalFlags(cmd *pflag.FlagSet) { -} diff --git a/daemon/daemon.go b/daemon/daemon.go index 8c64facce2..8043aea1a5 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -104,6 +104,14 @@ type Daemon struct { clusterProvider cluster.Provider } +// HasExperimental returns whether the experimental features of the daemon are enabled or not +func (daemon *Daemon) HasExperimental() bool { + if daemon.configStore != nil && daemon.configStore.Experimental { + return true + } + return false +} + func (daemon *Daemon) restore() error { var ( currentDriver = daemon.GraphDriverName() @@ -667,7 +675,7 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot } // Plugin system initialization should happen before restore. Do not change order. - if err := pluginInit(d, config, containerdRemote); err != nil { + if err := d.pluginInit(config, containerdRemote); err != nil { return nil, err } @@ -775,7 +783,7 @@ func (daemon *Daemon) Shutdown() error { } // Shutdown plugins after containers. Dont change the order. - pluginShutdown() + daemon.pluginShutdown() // trigger libnetwork Stop only if it's initialized if daemon.netController != nil { @@ -1022,7 +1030,6 @@ func (daemon *Daemon) Reload(config *Config) (err error) { if err := daemon.containerdRemote.UpdateOptions(libcontainerd.WithLiveRestore(config.LiveRestoreEnabled)); err != nil { return err } - } // If no value is set for max-concurrent-downloads we assume it is the default value diff --git a/daemon/daemon_experimental.go b/daemon/daemon_experimental.go index 6b2bb6c405..cf38704553 100644 --- a/daemon/daemon_experimental.go +++ b/daemon/daemon_experimental.go @@ -1,5 +1,3 @@ -// +build experimental - package daemon import ( @@ -12,11 +10,17 @@ func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container. return nil, nil } -func pluginInit(d *Daemon, cfg *Config, remote libcontainerd.Remote) error { - return plugin.Init(cfg.Root, d.PluginStore, remote, d.RegistryService, cfg.LiveRestoreEnabled, d.LogPluginEvent) +func (daemon *Daemon) pluginInit(cfg *Config, remote libcontainerd.Remote) error { + if !daemon.HasExperimental() { + return nil + } + return plugin.Init(cfg.Root, daemon.PluginStore, remote, daemon.RegistryService, cfg.LiveRestoreEnabled, daemon.LogPluginEvent) } -func pluginShutdown() { +func (daemon *Daemon) pluginShutdown() { + if !daemon.HasExperimental() { + return + } manager := plugin.GetManager() // Check for a valid manager object. In error conditions, daemon init can fail // and shutdown called, before plugin manager is initialized. diff --git a/daemon/daemon_stub.go b/daemon/daemon_stub.go deleted file mode 100644 index 5c72f82b3c..0000000000 --- a/daemon/daemon_stub.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build !experimental - -package daemon - -import ( - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/libcontainerd" -) - -func (daemon *Daemon) verifyExperimentalContainerSettings(hostConfig *container.HostConfig, config *container.Config) ([]string, error) { - return nil, nil -} - -func pluginInit(d *Daemon, config *Config, remote libcontainerd.Remote) error { - return nil -} - -func pluginShutdown() { -} diff --git a/daemon/graphdriver/plugin.go b/daemon/graphdriver/plugin.go index be1b244e72..6d2b85d8a1 100644 --- a/daemon/graphdriver/plugin.go +++ b/daemon/graphdriver/plugin.go @@ -1,5 +1,3 @@ -// +build experimental - package graphdriver import ( diff --git a/daemon/graphdriver/plugin_unsupported.go b/daemon/graphdriver/plugin_unsupported.go deleted file mode 100644 index c2fad0fcd2..0000000000 --- a/daemon/graphdriver/plugin_unsupported.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !experimental - -package graphdriver - -import "github.com/docker/docker/pkg/plugingetter" - -func lookupPlugin(name, home string, opts []string, pg plugingetter.PluginGetter) (Driver, error) { - return nil, ErrNotSupported -} diff --git a/daemon/graphdriver/proxy.go b/daemon/graphdriver/proxy.go index 7fc8fe0de4..d73c4f0d96 100644 --- a/daemon/graphdriver/proxy.go +++ b/daemon/graphdriver/proxy.go @@ -1,5 +1,3 @@ -// +build experimental - package graphdriver import ( diff --git a/daemon/info.go b/daemon/info.go index 2a6c92abf8..d57616a3d5 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -109,7 +109,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { MemTotal: meminfo.MemTotal, DockerRootDir: daemon.configStore.Root, Labels: daemon.configStore.Labels, - ExperimentalBuild: utils.ExperimentalBuild(), + ExperimentalBuild: daemon.configStore.Experimental, ServerVersion: dockerversion.Version, ClusterStore: daemon.configStore.ClusterStore, ClusterAdvertise: daemon.configStore.ClusterAdvertise, @@ -158,7 +158,7 @@ func (daemon *Daemon) SystemVersion() types.Version { Os: runtime.GOOS, Arch: runtime.GOARCH, BuildTime: dockerversion.BuildTime, - Experimental: utils.ExperimentalBuild(), + Experimental: daemon.configStore.Experimental, } kernelVersion := "" diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 1d21bc7abb..740935e248 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -157,7 +157,7 @@ This section lists each version from latest to oldest. Each listing includes a * `POST /containers/prune` prunes stopped containers. * `POST /images/prune` prunes unused images. * `POST /volumes/prune` prunes unused volumes. - +* Every API response now includes a `Docker-Experimental` header specifying if experimental features are enabled (value can be `true` or `false`). ### v1.24 API changes diff --git a/docs/reference/commandline/dockerd.md b/docs/reference/commandline/dockerd.md index fa4c52ab72..b894af4d20 100644 --- a/docs/reference/commandline/dockerd.md +++ b/docs/reference/commandline/dockerd.md @@ -45,6 +45,7 @@ Options: --dns-search value DNS search domains to use (default []) --exec-opt value Runtime execution options (default []) --exec-root string Root directory for execution state files (default "/var/run/docker") + --experimental Enable experimental features --fixed-cidr string IPv4 subnet for fixed IPs --fixed-cidr-v6 string IPv6 subnet for fixed IPs -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: "dns-search": [], "exec-opts": [], "exec-root": "", + "experimental": false, "storage-driver": "", "storage-opts": [], "labels": [], @@ -1195,6 +1197,7 @@ This is a full example of the allowed configuration options on Windows: "dns-opts": [], "dns-search": [], "exec-opts": [], + "experimental": false, "storage-driver": "", "storage-opts": [], "labels": [], diff --git a/hack/make.sh b/hack/make.sh index c9b3683c1f..6ca553fd3c 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -126,12 +126,6 @@ if [ ! "$GOPATH" ]; then exit 1 fi -if [ "$DOCKER_EXPERIMENTAL" ]; then - echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features' - echo >&2 - DOCKER_BUILDTAGS+=" experimental" -fi - DOCKER_BUILDTAGS+=" daemon" if ${PKG_CONFIG} 'libsystemd >= 209' 2> /dev/null ; then DOCKER_BUILDTAGS+=" journald" diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 61ba6fccac..b96979bdb2 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -50,6 +50,11 @@ if [ "$DOCKER_REMAP_ROOT" ]; then extra_params="--userns-remap $DOCKER_REMAP_ROOT" fi +if [ "$DOCKER_EXPERIMENTAL" ]; then + echo >&2 '# DOCKER_EXPERIMENTAL is set: starting daemon with experimental features enabled! ' + extra_params="$extra_params --experimental" +fi + if [ -z "$DOCKER_TEST_HOST" ]; then # Start apparmor if it is enabled if [ -e "/sys/module/apparmor/parameters/enabled" ] && [ "$(cat /sys/module/apparmor/parameters/enabled)" == "Y" ]; then diff --git a/hack/make/build-deb b/hack/make/build-deb index b3ae3df24b..cd23622629 100644 --- a/hack/make/build-deb +++ b/hack/make/build-deb @@ -76,10 +76,6 @@ set -e # Install runc and containerd RUN ./hack/dockerfile/install-binaries.sh runc-dynamic containerd-dynamic EOF - - if [ "$DOCKER_EXPERIMENTAL" ]; then - echo 'ENV DOCKER_EXPERIMENTAL 1' >> "$DEST/$version/Dockerfile.build" - fi cat >> "$DEST/$version/Dockerfile.build" <<-EOF RUN cp -aL hack/make/.build-deb debian RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog diff --git a/hack/make/build-rpm b/hack/make/build-rpm index 725fe8e7ad..ae71fe8aba 100644 --- a/hack/make/build-rpm +++ b/hack/make/build-rpm @@ -96,8 +96,7 @@ set -e # Install runc and containerd RUN ./hack/dockerfile/install-binaries.sh runc-dynamic containerd-dynamic EOF - - if [ "$DOCKER_EXPERIMENTAL" ]; then + if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then echo 'ENV DOCKER_EXPERIMENTAL 1' >> "$DEST/$version/Dockerfile.build" fi cat >> "$DEST/$version/Dockerfile.build" <<-EOF diff --git a/hack/make/release-deb b/hack/make/release-deb index 80a25c443b..19faae157f 100755 --- a/hack/make/release-deb +++ b/hack/make/release-deb @@ -39,7 +39,7 @@ if [[ "$VERSION" == *-rc* ]]; then component="testing" fi -if [ "$DOCKER_EXPERIMENTAL" ] || [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then +if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then component="experimental" fi diff --git a/hack/make/release-rpm b/hack/make/release-rpm index 5c109d0745..f82a243578 100755 --- a/hack/make/release-rpm +++ b/hack/make/release-rpm @@ -25,7 +25,7 @@ if [[ "$VERSION" == *-rc* ]]; then release="testing" fi -if [ $DOCKER_EXPERIMENTAL ] || [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then +if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then release="experimental" fi diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index b1917a8826..a2e74173e0 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -266,7 +266,11 @@ func (s *DockerSwarmSuite) AddDaemon(c *check.C, joinSwarm, manager bool) *Swarm port: defaultSwarmPort + s.portIndex, } d.listenAddr = fmt.Sprintf("0.0.0.0:%d", d.port) - err := d.StartWithBusybox("--iptables=false", "--swarm-default-advertise-addr=lo") // avoid networking conflicts + args := []string{"--iptables=false", "--swarm-default-advertise-addr=lo"} // avoid networking conflicts + if experimentalDaemon { + args = append(args, "--experimental") + } + err := d.StartWithBusybox(args...) c.Assert(err, check.IsNil) if joinSwarm == true { diff --git a/integration-cli/daemon.go b/integration-cli/daemon.go index dbcb1f0df3..e5fcd815a6 100644 --- a/integration-cli/daemon.go +++ b/integration-cli/daemon.go @@ -153,6 +153,9 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), ) + if experimentalDaemon { + args = append(args, "--experimental") + } if !(d.useDefaultHost || d.useDefaultTLSHost) { args = append(args, []string{"--host", d.sock()}...) } diff --git a/integration-cli/docker_cli_daemon_experimental_test.go b/integration-cli/docker_cli_daemon_experimental_test.go index 0a33f1d849..7b1df797c5 100644 --- a/integration-cli/docker_cli_daemon_experimental_test.go +++ b/integration-cli/docker_cli_daemon_experimental_test.go @@ -1,4 +1,4 @@ -// +build linux, experimental +// +build linux package main @@ -17,7 +17,8 @@ var pluginName = "tiborvass/no-remove" // TestDaemonRestartWithPluginEnabled tests state restore for an enabled plugin func (s *DockerDaemonSuite) TestDaemonRestartWithPluginEnabled(c *check.C) { - testRequires(c, Network) + testRequires(c, Network, ExperimentalDaemon) + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } @@ -49,7 +50,8 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginEnabled(c *check.C) { // TestDaemonRestartWithPluginDisabled tests state restore for a disabled plugin func (s *DockerDaemonSuite) TestDaemonRestartWithPluginDisabled(c *check.C) { - testRequires(c, Network) + testRequires(c, Network, ExperimentalDaemon) + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } @@ -79,7 +81,8 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginDisabled(c *check.C) { // TestDaemonKillLiveRestoreWithPlugins SIGKILLs daemon started with --live-restore. // Plugins should continue to run. func (s *DockerDaemonSuite) TestDaemonKillLiveRestoreWithPlugins(c *check.C) { - testRequires(c, Network) + testRequires(c, Network, ExperimentalDaemon) + if err := s.d.Start("--live-restore"); err != nil { c.Fatalf("Could not start daemon: %v", err) } @@ -111,7 +114,8 @@ func (s *DockerDaemonSuite) TestDaemonKillLiveRestoreWithPlugins(c *check.C) { // TestDaemonShutdownLiveRestoreWithPlugins SIGTERMs daemon started with --live-restore. // Plugins should continue to run. func (s *DockerDaemonSuite) TestDaemonShutdownLiveRestoreWithPlugins(c *check.C) { - testRequires(c, Network) + testRequires(c, Network, ExperimentalDaemon) + if err := s.d.Start("--live-restore"); err != nil { c.Fatalf("Could not start daemon: %v", err) } @@ -142,7 +146,8 @@ func (s *DockerDaemonSuite) TestDaemonShutdownLiveRestoreWithPlugins(c *check.C) // TestDaemonShutdownWithPlugins shuts down running plugins. func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) { - testRequires(c, Network) + testRequires(c, Network, ExperimentalDaemon) + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } @@ -180,7 +185,8 @@ func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) { // TestVolumePlugin tests volume creation using a plugin. func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) { - testRequires(c, Network) + testRequires(c, Network, ExperimentalDaemon) + volName := "plugin-volume" volRoot := "/data" destDir := "/tmp/data/" diff --git a/integration-cli/docker_cli_experimental_test.go b/integration-cli/docker_cli_experimental_test.go index 8795078f64..6a49cc8cb1 100644 --- a/integration-cli/docker_cli_experimental_test.go +++ b/integration-cli/docker_cli_experimental_test.go @@ -1,21 +1,36 @@ -// +build experimental - package main import ( + "strings" + "github.com/docker/docker/pkg/integration/checker" "github.com/go-check/check" - "strings" ) -func (s *DockerSuite) TestExperimentalVersion(c *check.C) { +func (s *DockerSuite) TestExperimentalVersionTrue(c *check.C) { + testRequires(c, ExperimentalDaemon) + out, _ := dockerCmd(c, "version") for _, line := range strings.Split(out, "\n") { - if strings.HasPrefix(line, "Experimental (client):") || strings.HasPrefix(line, "Experimental (server):") { + if strings.HasPrefix(strings.TrimSpace(line), "Experimental:") { c.Assert(line, checker.Matches, "*true") + return } } - out, _ = dockerCmd(c, "-v") - c.Assert(out, checker.Contains, ", experimental", check.Commentf("docker version did not contain experimental")) + c.Fatal(`"Experimental" not found in version output`) +} + +func (s *DockerSuite) TestExperimentalVersionFalse(c *check.C) { + testRequires(c, NotExperimentalDaemon) + + out, _ := dockerCmd(c, "version") + for _, line := range strings.Split(out, "\n") { + if strings.HasPrefix(strings.TrimSpace(line), "Experimental:") { + c.Assert(line, checker.Matches, "*false") + return + } + } + + c.Fatal(`"Experimental" not found in version output`) } diff --git a/integration-cli/docker_cli_external_graphdriver_unix_test.go b/integration-cli/docker_cli_external_graphdriver_unix_test.go index 1b0ec9147c..41f1400d8f 100644 --- a/integration-cli/docker_cli_external_graphdriver_unix_test.go +++ b/integration-cli/docker_cli_external_graphdriver_unix_test.go @@ -1,4 +1,3 @@ -// +build experimental // +build !windows package main @@ -287,7 +286,7 @@ func (s *DockerExternalGraphdriverSuite) setUpPlugin(c *check.C, name string, ex mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) { s.ec[ext].applydiff++ - var diff io.Reader = r.Body + diff := r.Body defer r.Body.Close() id := r.URL.Query().Get("id") @@ -338,6 +337,8 @@ func (s *DockerExternalGraphdriverSuite) TearDownSuite(c *check.C) { } func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) { + testRequires(c, ExperimentalDaemon) + s.testExternalGraphDriver("test-external-graph-driver", "spec", c) s.testExternalGraphDriver("json-external-graph-driver", "json", c) } @@ -388,7 +389,8 @@ func (s *DockerExternalGraphdriverSuite) testExternalGraphDriver(name string, ex } func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriverPull(c *check.C) { - testRequires(c, Network) + testRequires(c, Network, ExperimentalDaemon) + c.Assert(s.d.Start(), check.IsNil) out, err := s.d.Cmd("pull", "busybox:latest") diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go index 9603791239..415974355e 100644 --- a/integration-cli/docker_cli_help_test.go +++ b/integration-cli/docker_cli_help_test.go @@ -10,7 +10,6 @@ import ( "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/integration/checker" icmd "github.com/docker/docker/pkg/integration/cmd" - "github.com/docker/docker/utils" "github.com/go-check/check" ) @@ -117,7 +116,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) { cmdsToTest = append(cmdsToTest, "network ls") cmdsToTest = append(cmdsToTest, "network rm") - if utils.ExperimentalBuild() { + if experimentalDaemon { cmdsToTest = append(cmdsToTest, "checkpoint create") cmdsToTest = append(cmdsToTest, "checkpoint ls") cmdsToTest = append(cmdsToTest, "checkpoint rm") diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index c0caa23556..f59ae5b7af 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/docker/docker/pkg/integration/checker" - "github.com/docker/docker/utils" "github.com/go-check/check" ) @@ -44,8 +43,10 @@ func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) { stringsToCheck = append(stringsToCheck, "Runtimes:", "Default Runtime: runc") } - if utils.ExperimentalBuild() { + if experimentalDaemon { stringsToCheck = append(stringsToCheck, "Experimental: true") + } else { + stringsToCheck = append(stringsToCheck, "Experimental: false") } for _, linePrefix := range stringsToCheck { diff --git a/integration-cli/docker_cli_stack_test.go b/integration-cli/docker_cli_stack_test.go index 04a52761a4..db4fdfc1b4 100644 --- a/integration-cli/docker_cli_stack_test.go +++ b/integration-cli/docker_cli_stack_test.go @@ -1,5 +1,3 @@ -// +build experimental - package main import ( @@ -11,6 +9,7 @@ import ( ) func (s *DockerSwarmSuite) TestStackRemove(c *check.C) { + testRequires(c, ExperimentalDaemon) d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "remove", "UNKNOWN_STACK"}) @@ -21,6 +20,7 @@ func (s *DockerSwarmSuite) TestStackRemove(c *check.C) { } func (s *DockerSwarmSuite) TestStackTasks(c *check.C) { + testRequires(c, ExperimentalDaemon) d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "ps", "UNKNOWN_STACK"}) @@ -31,6 +31,7 @@ func (s *DockerSwarmSuite) TestStackTasks(c *check.C) { } func (s *DockerSwarmSuite) TestStackServices(c *check.C) { + testRequires(c, ExperimentalDaemon) d := s.AddDaemon(c, true, true) stackArgs := append([]string{"stack", "services", "UNKNOWN_STACK"}) @@ -59,6 +60,7 @@ const testDAB = `{ }` func (s *DockerSwarmSuite) TestStackWithDAB(c *check.C) { + testRequires(c, ExperimentalDaemon) // setup testStackName := "test" testDABFileName := testStackName + ".dab" @@ -92,6 +94,7 @@ func (s *DockerSwarmSuite) TestStackWithDAB(c *check.C) { } func (s *DockerSwarmSuite) TestStackWithDABExtension(c *check.C) { + testRequires(c, ExperimentalDaemon) // setup testStackName := "test.dab" testDABFileName := testStackName diff --git a/integration-cli/docker_experimental_network_test.go b/integration-cli/docker_experimental_network_test.go index 10a082ce03..85dec31948 100644 --- a/integration-cli/docker_experimental_network_test.go +++ b/integration-cli/docker_experimental_network_test.go @@ -1,4 +1,4 @@ -// +build experimental +// +build !windows package main @@ -50,7 +50,8 @@ var ( func (s *DockerNetworkSuite) TestDockerNetworkMacvlanPersistance(c *check.C) { // verify the driver automatically provisions the 802.1q link (dm-dummy0.60) - testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) + // master dummy interface 'dm' abbreviation represents 'docker macvlan' master := "dm-dummy0" // simulate the master link the vlan tagged subinterface parent link will use @@ -69,7 +70,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanPersistance(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkIpvlanPersistance(c *check.C) { // verify the driver automatically provisions the 802.1q link (di-dummy0.70) - testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) // master dummy interface 'di' notation represent 'docker ipvlan' master := "di-dummy0" // simulate the master link the vlan tagged subinterface parent link will use @@ -88,7 +89,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanPersistance(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkMacvlanSubIntCreate(c *check.C) { // verify the driver automatically provisions the 802.1q link (dm-dummy0.50) - testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) // master dummy interface 'dm' abbreviation represents 'docker macvlan' master := "dm-dummy0" // simulate the master link the vlan tagged subinterface parent link will use @@ -103,7 +104,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanSubIntCreate(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkIpvlanSubIntCreate(c *check.C) { // verify the driver automatically provisions the 802.1q link (di-dummy0.50) - testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) // master dummy interface 'dm' abbreviation represents 'docker ipvlan' master := "di-dummy0" // simulate the master link the vlan tagged subinterface parent link will use @@ -118,7 +119,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanSubIntCreate(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkMacvlanOverlapParent(c *check.C) { // verify the same parent interface cannot be used if already in use by an existing network - testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) // master dummy interface 'dm' abbreviation represents 'docker macvlan' master := "dm-dummy0" out, err := createMasterDummy(c, master) @@ -138,7 +139,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanOverlapParent(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkIpvlanOverlapParent(c *check.C) { // verify the same parent interface cannot be used if already in use by an existing network - testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) // master dummy interface 'dm' abbreviation represents 'docker ipvlan' master := "di-dummy0" out, err := createMasterDummy(c, master) @@ -158,7 +159,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanOverlapParent(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkMacvlanMultiSubnet(c *check.C) { // create a dual stack multi-subnet Macvlan bridge mode network and validate connectivity between four containers, two on each subnet - testRequires(c, DaemonIsLinux, IPv6, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IPv6, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=macvlan", "--ipv6", "--subnet=172.28.100.0/24", "--subnet=172.28.102.0/24", "--gateway=172.28.102.254", "--subnet=2001:db8:abc2::/64", "--subnet=2001:db8:abc4::/64", "--gateway=2001:db8:abc4::254", "dualstackbridge") // Ensure the network was created @@ -213,7 +214,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkMacvlanMultiSubnet(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL2MultiSubnet(c *check.C) { // create a dual stack multi-subnet Ipvlan L2 network and validate connectivity within the subnets, two on each subnet - testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=ipvlan", "--ipv6", "--subnet=172.28.200.0/24", "--subnet=172.28.202.0/24", "--gateway=172.28.202.254", "--subnet=2001:db8:abc8::/64", "--subnet=2001:db8:abc6::/64", "--gateway=2001:db8:abc6::254", "dualstackl2") // Ensure the network was created @@ -267,7 +268,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL2MultiSubnet(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL3MultiSubnet(c *check.C) { // create a dual stack multi-subnet Ipvlan L3 network and validate connectivity between all four containers per L3 mode - testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, IPv6) + testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, IPv6, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=ipvlan", "--ipv6", "--subnet=172.28.10.0/24", "--subnet=172.28.12.0/24", "--gateway=172.28.12.254", "--subnet=2001:db8:abc9::/64", "--subnet=2001:db8:abc7::/64", "--gateway=2001:db8:abc7::254", "-o", "ipvlan_mode=l3", "dualstackl3") // Ensure the network was created @@ -326,7 +327,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanL3MultiSubnet(c *check.C) { func (s *DockerNetworkSuite) TestDockerNetworkIpvlanAddressing(c *check.C) { // Ensure the default gateways, next-hops and default dev devices are properly set - testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IPv6, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=macvlan", "--ipv6", "--subnet=172.28.130.0/24", "--subnet=2001:db8:abca::/64", "--gateway=2001:db8:abca::254", "-o", "macvlan_mode=bridge", "dualstackbridge") assertNwIsAvailable(c, "dualstackbridge") @@ -372,7 +373,7 @@ func (s *DockerNetworkSuite) TestDockerNetworkIpvlanAddressing(c *check.C) { func (s *DockerSuite) TestDockerNetworkMacVlanBridgeNilParent(c *check.C) { // macvlan bridge mode - dummy parent interface is provisioned dynamically - testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=macvlan", "dm-nil-parent") assertNwIsAvailable(c, "dm-nil-parent") @@ -389,7 +390,7 @@ func (s *DockerSuite) TestDockerNetworkMacVlanBridgeNilParent(c *check.C) { func (s *DockerSuite) TestDockerNetworkMacVlanBridgeInternalMode(c *check.C) { // macvlan bridge mode --internal containers can communicate inside the network but not externally - testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=macvlan", "--internal", "dm-internal") assertNwIsAvailable(c, "dm-internal") nr := getNetworkResource(c, "dm-internal") @@ -412,7 +413,7 @@ func (s *DockerSuite) TestDockerNetworkMacVlanBridgeInternalMode(c *check.C) { func (s *DockerSuite) TestDockerNetworkIpvlanL2NilParent(c *check.C) { // ipvlan l2 mode - dummy parent interface is provisioned dynamically - testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=ipvlan", "di-nil-parent") assertNwIsAvailable(c, "di-nil-parent") @@ -429,7 +430,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL2NilParent(c *check.C) { func (s *DockerSuite) TestDockerNetworkIpvlanL2InternalMode(c *check.C) { // ipvlan l2 mode --internal containers can communicate inside the network but not externally - testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=ipvlan", "--internal", "di-internal") assertNwIsAvailable(c, "di-internal") nr := getNetworkResource(c, "di-internal") @@ -451,7 +452,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL2InternalMode(c *check.C) { func (s *DockerSuite) TestDockerNetworkIpvlanL3NilParent(c *check.C) { // ipvlan l3 mode - dummy parent interface is provisioned dynamically - testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=ipvlan", "--subnet=172.28.230.0/24", "--subnet=172.28.220.0/24", "-o", "ipvlan_mode=l3", "di-nil-parent-l3") assertNwIsAvailable(c, "di-nil-parent-l3") @@ -469,7 +470,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL3NilParent(c *check.C) { func (s *DockerSuite) TestDockerNetworkIpvlanL3InternalMode(c *check.C) { // ipvlan l3 mode --internal containers can communicate inside the network but not externally - testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, IpvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) dockerCmd(c, "network", "create", "--driver=ipvlan", "--subnet=172.28.230.0/24", "--subnet=172.28.220.0/24", "-o", "ipvlan_mode=l3", "--internal", "di-internal-l3") assertNwIsAvailable(c, "di-internal-l3") @@ -492,7 +493,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL3InternalMode(c *check.C) { func (s *DockerSuite) TestDockerNetworkMacVlanExistingParent(c *check.C) { // macvlan bridge mode - empty parent interface containers can reach each other internally but not externally - testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) netName := "dm-parent-exists" out, err := createMasterDummy(c, "dm-dummy0") //out, err := createVlanInterface(c, "dm-parent", "dm-slave", "macvlan", "bridge") @@ -512,7 +513,7 @@ func (s *DockerSuite) TestDockerNetworkMacVlanExistingParent(c *check.C) { func (s *DockerSuite) TestDockerNetworkMacVlanSubinterface(c *check.C) { // macvlan bridge mode - empty parent interface containers can reach each other internally but not externally - testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm) + testRequires(c, DaemonIsLinux, MacvlanKernelSupport, NotUserNamespace, NotArm, ExperimentalDaemon) netName := "dm-subinterface" out, err := createMasterDummy(c, "dm-dummy0") c.Assert(err, check.IsNil, check.Commentf(out)) diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go index 4e387987b4..bfc1a733b3 100644 --- a/integration-cli/docker_test_vars.go +++ b/integration-cli/docker_test_vars.go @@ -61,6 +61,10 @@ var ( volumesConfigPath string containerStoragePath string + // experimentalDaemon tell whether the main daemon has + // experimental features enabled or not + experimentalDaemon bool + // daemonStorageDriver is held globally so that tests can know the storage // driver of the daemon. This is initialized in docker_utils by sending // a version call to the daemon and examining the response header. @@ -128,13 +132,15 @@ func init() { // /info endpoint for the specific root dir dockerBasePath = "/var/lib/docker" type Info struct { - DockerRootDir string + DockerRootDir string + ExperimentalBuild bool } var i Info status, b, err := sockRequest("GET", "/info", nil) if err == nil && status == 200 { if err = json.Unmarshal(b, &i); err == nil { dockerBasePath = i.DockerRootDir + experimentalDaemon = i.ExperimentalBuild } } volumesConfigPath = dockerBasePath + "/volumes" diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 53ec417f13..56312c1682 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -9,7 +9,6 @@ import ( "strings" "time" - "github.com/docker/docker/utils" "github.com/go-check/check" ) @@ -31,11 +30,11 @@ var ( "Test requires a Linux daemon", } ExperimentalDaemon = testRequirement{ - func() bool { return utils.ExperimentalBuild() }, + func() bool { return experimentalDaemon }, "Test requires an experimental daemon", } NotExperimentalDaemon = testRequirement{ - func() bool { return !utils.ExperimentalBuild() }, + func() bool { return !experimentalDaemon }, "Test requires a non experimental daemon", } IsAmd64 = testRequirement{ diff --git a/man/dockerd.8.md b/man/dockerd.8.md index 24b71811fb..c20f0bb140 100644 --- a/man/dockerd.8.md +++ b/man/dockerd.8.md @@ -27,6 +27,7 @@ dockerd - Enable daemon mode [**--dns-search**[=*[]*]] [**--exec-opt**[=*[]*]] [**--exec-root**[=*/var/run/docker*]] +[**--experimental**[=*false*]] [**--fixed-cidr**[=*FIXED-CIDR*]] [**--fixed-cidr-v6**[=*FIXED-CIDR-V6*]] [**-G**|**--group**[=*docker*]] @@ -146,6 +147,9 @@ format. **--exec-root**="" Path to use as the root of the Docker execution state files. Default is `/var/run/docker`. +**--experimental**="" + Enable the daemon experimental features. + **--fixed-cidr**="" 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) diff --git a/plugin/backend.go b/plugin/backend.go index 826075f3d3..26f8938b35 100644 --- a/plugin/backend.go +++ b/plugin/backend.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/plugin/distribution/pull.go b/plugin/distribution/pull.go index a4824539d5..71566a3d2b 100644 --- a/plugin/distribution/pull.go +++ b/plugin/distribution/pull.go @@ -1,5 +1,3 @@ -// +build experimental - package distribution import ( diff --git a/plugin/distribution/push.go b/plugin/distribution/push.go index 131738976f..104b684d54 100644 --- a/plugin/distribution/push.go +++ b/plugin/distribution/push.go @@ -1,5 +1,3 @@ -// +build experimental - package distribution import ( diff --git a/plugin/distribution/types.go b/plugin/distribution/types.go index 8a327d1042..a673b50321 100644 --- a/plugin/distribution/types.go +++ b/plugin/distribution/types.go @@ -1,5 +1,3 @@ -// +build experimental - package distribution import "errors" diff --git a/plugin/manager.go b/plugin/manager.go index cd17e11c8b..f214f03d92 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -1,5 +1,3 @@ -// +build experimental - package plugin import ( diff --git a/plugin/manager_linux.go b/plugin/manager_linux.go index 14a577ddc5..6206952dbd 100644 --- a/plugin/manager_linux.go +++ b/plugin/manager_linux.go @@ -1,4 +1,4 @@ -// +build linux,experimental +// +build linux package plugin @@ -12,7 +12,7 @@ import ( "github.com/docker/docker/oci" "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/plugin/v2" - "github.com/opencontainers/runtime-spec/specs-go" + specs "github.com/opencontainers/runtime-spec/specs-go" ) func (pm *Manager) enable(p *v2.Plugin, force bool) error { diff --git a/plugin/manager_windows.go b/plugin/manager_windows.go index 6b8149a0af..6828018750 100644 --- a/plugin/manager_windows.go +++ b/plugin/manager_windows.go @@ -1,4 +1,4 @@ -// +build windows,experimental +// +build windows package plugin @@ -6,7 +6,7 @@ import ( "fmt" "github.com/docker/docker/plugin/v2" - "github.com/opencontainers/runtime-spec/specs-go" + specs "github.com/opencontainers/runtime-spec/specs-go" ) func (pm *Manager) enable(p *v2.Plugin, force bool) error { diff --git a/plugin/store/store.go b/plugin/store/store.go index 375488f250..d4d6499c9e 100644 --- a/plugin/store/store.go +++ b/plugin/store/store.go @@ -1,33 +1,240 @@ -// +build !experimental - package store import ( + "encoding/json" + "fmt" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/plugingetter" "github.com/docker/docker/pkg/plugins" + "github.com/docker/docker/plugin/v2" + "github.com/docker/docker/reference" ) -// GetAllByCap returns a list of plugins matching the given capability. -func (ps Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) { - pl, err := plugins.GetAll(capability) +/* allowV1PluginsFallback determines daemon's support for V1 plugins. + * When the time comes to remove support for V1 plugins, flipping + * this bool is all that will be needed. + */ +const allowV1PluginsFallback bool = true + +/* defaultAPIVersion is the version of the plugin API for volume, network, + IPAM and authz. This is a very stable API. When we update this API, then + pluginType should include a version. eg "networkdriver/2.0". +*/ +const defaultAPIVersion string = "1.0" + +// ErrNotFound indicates that a plugin was not found locally. +type ErrNotFound string + +func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) } + +// GetByName retreives a plugin by name. +func (ps *Store) GetByName(name string) (*v2.Plugin, error) { + ps.RLock() + defer ps.RUnlock() + + id, nameOk := ps.nameToID[name] + if !nameOk { + return nil, ErrNotFound(name) + } + + p, idOk := ps.plugins[id] + if !idOk { + return nil, ErrNotFound(id) + } + return p, nil +} + +// GetByID retreives a plugin by ID. +func (ps *Store) GetByID(id string) (*v2.Plugin, error) { + ps.RLock() + defer ps.RUnlock() + + p, idOk := ps.plugins[id] + if !idOk { + return nil, ErrNotFound(id) + } + return p, nil +} + +// GetAll retreives all plugins. +func (ps *Store) GetAll() map[string]*v2.Plugin { + ps.RLock() + defer ps.RUnlock() + return ps.plugins +} + +// SetAll initialized plugins during daemon restore. +func (ps *Store) SetAll(plugins map[string]*v2.Plugin) { + ps.Lock() + defer ps.Unlock() + ps.plugins = plugins +} + +func (ps *Store) getByCap(name string, capability string) (*v2.Plugin, error) { + ps.RLock() + defer ps.RUnlock() + + p, err := ps.GetByName(name) if err != nil { return nil, err } - result := make([]plugingetter.CompatPlugin, len(pl)) - for i, p := range pl { - result[i] = p + return p.FilterByCap(capability) +} + +func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin { + ps.RLock() + defer ps.RUnlock() + + result := make([]plugingetter.CompatPlugin, 0, 1) + for _, p := range ps.plugins { + if _, err := p.FilterByCap(capability); err == nil { + result = append(result, p) + } } - return result, nil + return result +} + +// SetState sets the active state of the plugin and updates plugindb. +func (ps *Store) SetState(p *v2.Plugin, state bool) { + ps.Lock() + defer ps.Unlock() + + p.PluginObj.Enabled = state + ps.updatePluginDB() +} + +// Add adds a plugin to memory and plugindb. +func (ps *Store) Add(p *v2.Plugin) { + ps.Lock() + ps.plugins[p.GetID()] = p + ps.nameToID[p.Name()] = p.GetID() + ps.updatePluginDB() + ps.Unlock() +} + +// Remove removes a plugin from memory and plugindb. +func (ps *Store) Remove(p *v2.Plugin) { + ps.Lock() + delete(ps.plugins, p.GetID()) + delete(ps.nameToID, p.Name()) + ps.updatePluginDB() + ps.Unlock() +} + +// Callers are expected to hold the store lock. +func (ps *Store) updatePluginDB() error { + jsonData, err := json.Marshal(ps.plugins) + if err != nil { + logrus.Debugf("Error in json.Marshal: %v", err) + return err + } + ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600) + return nil } // Get returns a plugin matching the given name and capability. -func (ps Store) Get(name, capability string, _ int) (plugingetter.CompatPlugin, error) { - return plugins.Get(name, capability) +func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) { + var ( + p *v2.Plugin + err error + ) + + // Lookup using new model. + if ps != nil { + fullName := name + if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate + if reference.IsNameOnly(named) { + named = reference.WithDefaultTag(named) + } + ref, ok := named.(reference.NamedTagged) + if !ok { + return nil, fmt.Errorf("invalid name: %s", named.String()) + } + fullName = ref.String() + } + p, err = ps.GetByName(fullName) + if err == nil { + p.Lock() + p.RefCount += mode + p.Unlock() + return p.FilterByCap(capability) + } + if _, ok := err.(ErrNotFound); !ok { + return nil, err + } + } + + // Lookup using legacy model. + if allowV1PluginsFallback { + p, err := plugins.Get(name, capability) + if err != nil { + return nil, fmt.Errorf("legacy plugin: %v", err) + } + return p, nil + } + + return nil, err +} + +// GetAllByCap returns a list of plugins matching the given capability. +func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) { + result := make([]plugingetter.CompatPlugin, 0, 1) + + /* Daemon start always calls plugin.Init thereby initializing a store. + * So store on experimental builds can never be nil, even while + * handling legacy plugins. However, there are legacy plugin unit + * tests where the volume subsystem directly talks with the plugin, + * bypassing the daemon. For such tests, this check is necessary. + */ + if ps != nil { + ps.RLock() + result = ps.getAllByCap(capability) + ps.RUnlock() + } + + // Lookup with legacy model + if allowV1PluginsFallback { + pl, err := plugins.GetAll(capability) + if err != nil { + return nil, fmt.Errorf("legacy plugin: %v", err) + } + for _, p := range pl { + result = append(result, p) + } + } + return result, nil } // Handle sets a callback for a given capability. It is only used by network // and ipam drivers during plugin registration. The callback registers the // driver with the subsystem (network, ipam). func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) { - plugins.Handle(capability, callback) + pluginType := fmt.Sprintf("docker.%s/%s", strings.ToLower(capability), defaultAPIVersion) + + // Register callback with new plugin model. + ps.Lock() + handlers, ok := ps.handlers[pluginType] + if !ok { + handlers = []func(string, *plugins.Client){} + } + handlers = append(handlers, callback) + ps.handlers[pluginType] = handlers + ps.Unlock() + + // Register callback with legacy plugin model. + if allowV1PluginsFallback { + plugins.Handle(capability, callback) + } +} + +// CallHandler calls the registered callback. It is invoked during plugin enable. +func (ps *Store) CallHandler(p *v2.Plugin) { + for _, typ := range p.GetTypes() { + for _, handler := range ps.handlers[typ.String()] { + handler(p.Name(), p.Client()) + } + } } diff --git a/plugin/store/store_experimental.go b/plugin/store/store_experimental.go deleted file mode 100644 index ee2bf9b6c8..0000000000 --- a/plugin/store/store_experimental.go +++ /dev/null @@ -1,242 +0,0 @@ -// +build experimental - -package store - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/plugingetter" - "github.com/docker/docker/pkg/plugins" - "github.com/docker/docker/plugin/v2" - "github.com/docker/docker/reference" -) - -/* allowV1PluginsFallback determines daemon's support for V1 plugins. - * When the time comes to remove support for V1 plugins, flipping - * this bool is all that will be needed. - */ -const allowV1PluginsFallback bool = true - -/* defaultAPIVersion is the version of the plugin API for volume, network, - IPAM and authz. This is a very stable API. When we update this API, then - pluginType should include a version. eg "networkdriver/2.0". -*/ -const defaultAPIVersion string = "1.0" - -// ErrNotFound indicates that a plugin was not found locally. -type ErrNotFound string - -func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) } - -// GetByName retreives a plugin by name. -func (ps *Store) GetByName(name string) (*v2.Plugin, error) { - ps.RLock() - defer ps.RUnlock() - - id, nameOk := ps.nameToID[name] - if !nameOk { - return nil, ErrNotFound(name) - } - - p, idOk := ps.plugins[id] - if !idOk { - return nil, ErrNotFound(id) - } - return p, nil -} - -// GetByID retreives a plugin by ID. -func (ps *Store) GetByID(id string) (*v2.Plugin, error) { - ps.RLock() - defer ps.RUnlock() - - p, idOk := ps.plugins[id] - if !idOk { - return nil, ErrNotFound(id) - } - return p, nil -} - -// GetAll retreives all plugins. -func (ps *Store) GetAll() map[string]*v2.Plugin { - ps.RLock() - defer ps.RUnlock() - return ps.plugins -} - -// SetAll initialized plugins during daemon restore. -func (ps *Store) SetAll(plugins map[string]*v2.Plugin) { - ps.Lock() - defer ps.Unlock() - ps.plugins = plugins -} - -func (ps *Store) getByCap(name string, capability string) (*v2.Plugin, error) { - ps.RLock() - defer ps.RUnlock() - - p, err := ps.GetByName(name) - if err != nil { - return nil, err - } - return p.FilterByCap(capability) -} - -func (ps *Store) getAllByCap(capability string) []plugingetter.CompatPlugin { - ps.RLock() - defer ps.RUnlock() - - result := make([]plugingetter.CompatPlugin, 0, 1) - for _, p := range ps.plugins { - if _, err := p.FilterByCap(capability); err == nil { - result = append(result, p) - } - } - return result -} - -// SetState sets the active state of the plugin and updates plugindb. -func (ps *Store) SetState(p *v2.Plugin, state bool) { - ps.Lock() - defer ps.Unlock() - - p.PluginObj.Enabled = state - ps.updatePluginDB() -} - -// Add adds a plugin to memory and plugindb. -func (ps *Store) Add(p *v2.Plugin) { - ps.Lock() - ps.plugins[p.GetID()] = p - ps.nameToID[p.Name()] = p.GetID() - ps.updatePluginDB() - ps.Unlock() -} - -// Remove removes a plugin from memory and plugindb. -func (ps *Store) Remove(p *v2.Plugin) { - ps.Lock() - delete(ps.plugins, p.GetID()) - delete(ps.nameToID, p.Name()) - ps.updatePluginDB() - ps.Unlock() -} - -// Callers are expected to hold the store lock. -func (ps *Store) updatePluginDB() error { - jsonData, err := json.Marshal(ps.plugins) - if err != nil { - logrus.Debugf("Error in json.Marshal: %v", err) - return err - } - ioutils.AtomicWriteFile(ps.plugindb, jsonData, 0600) - return nil -} - -// Get returns a plugin matching the given name and capability. -func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) { - var ( - p *v2.Plugin - err error - ) - - // Lookup using new model. - if ps != nil { - fullName := name - if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate - if reference.IsNameOnly(named) { - named = reference.WithDefaultTag(named) - } - ref, ok := named.(reference.NamedTagged) - if !ok { - return nil, fmt.Errorf("invalid name: %s", named.String()) - } - fullName = ref.String() - } - p, err = ps.GetByName(fullName) - if err == nil { - p.Lock() - p.RefCount += mode - p.Unlock() - return p.FilterByCap(capability) - } - if _, ok := err.(ErrNotFound); !ok { - return nil, err - } - } - - // Lookup using legacy model. - if allowV1PluginsFallback { - p, err := plugins.Get(name, capability) - if err != nil { - return nil, fmt.Errorf("legacy plugin: %v", err) - } - return p, nil - } - - return nil, err -} - -// GetAllByCap returns a list of plugins matching the given capability. -func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) { - result := make([]plugingetter.CompatPlugin, 0, 1) - - /* Daemon start always calls plugin.Init thereby initializing a store. - * So store on experimental builds can never be nil, even while - * handling legacy plugins. However, there are legacy plugin unit - * tests where the volume subsystem directly talks with the plugin, - * bypassing the daemon. For such tests, this check is necessary. - */ - if ps != nil { - ps.RLock() - result = ps.getAllByCap(capability) - ps.RUnlock() - } - - // Lookup with legacy model - if allowV1PluginsFallback { - pl, err := plugins.GetAll(capability) - if err != nil { - return nil, fmt.Errorf("legacy plugin: %v", err) - } - for _, p := range pl { - result = append(result, p) - } - } - return result, nil -} - -// Handle sets a callback for a given capability. It is only used by network -// and ipam drivers during plugin registration. The callback registers the -// driver with the subsystem (network, ipam). -func (ps *Store) Handle(capability string, callback func(string, *plugins.Client)) { - pluginType := fmt.Sprintf("docker.%s/%s", strings.ToLower(capability), defaultAPIVersion) - - // Register callback with new plugin model. - ps.Lock() - handlers, ok := ps.handlers[pluginType] - if !ok { - handlers = []func(string, *plugins.Client){} - } - handlers = append(handlers, callback) - ps.handlers[pluginType] = handlers - ps.Unlock() - - // Register callback with legacy plugin model. - if allowV1PluginsFallback { - plugins.Handle(capability, callback) - } -} - -// CallHandler calls the registered callback. It is invoked during plugin enable. -func (ps *Store) CallHandler(p *v2.Plugin) { - for _, typ := range p.GetTypes() { - for _, handler := range ps.handlers[typ.String()] { - handler(p.Name(), p.Client()) - } - } -} diff --git a/plugin/v2/plugin.go b/plugin/v2/plugin.go index 8cc06bd824..fc79fd8437 100644 --- a/plugin/v2/plugin.go +++ b/plugin/v2/plugin.go @@ -1,10 +1,18 @@ package v2 import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" "sync" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/plugins" + "github.com/docker/docker/pkg/system" + specs "github.com/opencontainers/runtime-spec/specs-go" ) // Plugin represents an individual plugin. @@ -17,3 +25,235 @@ type Plugin struct { Restart bool `json:"-"` ExitChan chan bool `json:"-"` } + +const defaultPluginRuntimeDestination = "/run/docker/plugins" + +// ErrInadequateCapability indicates that the plugin did not have the requested capability. +type ErrInadequateCapability string + +func (cap ErrInadequateCapability) Error() string { + return fmt.Sprintf("plugin does not provide %q capability", cap) +} + +func newPluginObj(name, id, tag string) types.Plugin { + return types.Plugin{Name: name, ID: id, Tag: tag} +} + +// NewPlugin creates a plugin. +func NewPlugin(name, id, runRoot, tag string) *Plugin { + return &Plugin{ + PluginObj: newPluginObj(name, id, tag), + RuntimeSourcePath: filepath.Join(runRoot, id), + } +} + +// Client returns the plugin client. +func (p *Plugin) Client() *plugins.Client { + return p.PClient +} + +// IsV1 returns true for V1 plugins and false otherwise. +func (p *Plugin) IsV1() bool { + return false +} + +// Name returns the plugin name. +func (p *Plugin) Name() string { + name := p.PluginObj.Name + if len(p.PluginObj.Tag) > 0 { + // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these + name += ":" + p.PluginObj.Tag + } + return name +} + +// FilterByCap query the plugin for a given capability. +func (p *Plugin) FilterByCap(capability string) (*Plugin, error) { + capability = strings.ToLower(capability) + for _, typ := range p.PluginObj.Manifest.Interface.Types { + if typ.Capability == capability && typ.Prefix == "docker" { + return p, nil + } + } + return nil, ErrInadequateCapability(capability) +} + +// RemoveFromDisk deletes the plugin's runtime files from disk. +func (p *Plugin) RemoveFromDisk() error { + return os.RemoveAll(p.RuntimeSourcePath) +} + +// InitPlugin populates the plugin object from the plugin manifest file. +func (p *Plugin) InitPlugin(libRoot string) error { + dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json")) + if err != nil { + return err + } + err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest) + dt.Close() + if err != nil { + return err + } + + p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts)) + for i, mount := range p.PluginObj.Manifest.Mounts { + p.PluginObj.Config.Mounts[i] = mount + } + p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env)) + for _, env := range p.PluginObj.Manifest.Env { + if env.Value != nil { + p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) + } + } + copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value) + + f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json")) + if err != nil { + return err + } + err = json.NewEncoder(f).Encode(&p.PluginObj.Config) + f.Close() + return err +} + +// Set is used to pass arguments to the plugin. +func (p *Plugin) Set(args []string) error { + m := make(map[string]string, len(args)) + for _, arg := range args { + i := strings.Index(arg, "=") + if i < 0 { + return fmt.Errorf("No equal sign '=' found in %s", arg) + } + m[arg[:i]] = arg[i+1:] + } + return errors.New("not implemented") +} + +// ComputePrivileges takes the manifest file and computes the list of access necessary +// for the plugin on the host. +func (p *Plugin) ComputePrivileges() types.PluginPrivileges { + m := p.PluginObj.Manifest + var privileges types.PluginPrivileges + if m.Network.Type != "null" && m.Network.Type != "bridge" { + privileges = append(privileges, types.PluginPrivilege{ + Name: "network", + Description: "", + Value: []string{m.Network.Type}, + }) + } + for _, mount := range m.Mounts { + if mount.Source != nil { + privileges = append(privileges, types.PluginPrivilege{ + Name: "mount", + Description: "", + Value: []string{*mount.Source}, + }) + } + } + for _, device := range m.Devices { + if device.Path != nil { + privileges = append(privileges, types.PluginPrivilege{ + Name: "device", + Description: "", + Value: []string{*device.Path}, + }) + } + } + if len(m.Capabilities) > 0 { + privileges = append(privileges, types.PluginPrivilege{ + Name: "capabilities", + Description: "", + Value: m.Capabilities, + }) + } + return privileges +} + +// IsEnabled returns the active state of the plugin. +func (p *Plugin) IsEnabled() bool { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.Enabled +} + +// GetID returns the plugin's ID. +func (p *Plugin) GetID() string { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.ID +} + +// GetSocket returns the plugin socket. +func (p *Plugin) GetSocket() string { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.Manifest.Interface.Socket +} + +// GetTypes returns the interface types of a plugin. +func (p *Plugin) GetTypes() []types.PluginInterfaceType { + p.RLock() + defer p.RUnlock() + + return p.PluginObj.Manifest.Interface.Types +} + +// InitSpec creates an OCI spec from the plugin's config. +func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { + rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") + s.Root = specs.Root{ + Path: rootfs, + Readonly: false, // TODO: all plugins should be readonly? settable in manifest? + } + + mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ + Source: &p.RuntimeSourcePath, + Destination: defaultPluginRuntimeDestination, + Type: "bind", + Options: []string{"rbind", "rshared"}, + }) + for _, mount := range mounts { + m := specs.Mount{ + Destination: mount.Destination, + Type: mount.Type, + Options: mount.Options, + } + // TODO: if nil, then it's required and user didn't set it + if mount.Source != nil { + m.Source = *mount.Source + } + if m.Source != "" && m.Type == "bind" { + fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks + if err != nil { + return nil, err + } + if fi.IsDir() { + if err := os.MkdirAll(m.Source, 0700); err != nil { + return nil, err + } + } + } + s.Mounts = append(s.Mounts, m) + } + + envs := make([]string, 1, len(p.PluginObj.Config.Env)+1) + envs[0] = "PATH=" + system.DefaultPathEnv + envs = append(envs, p.PluginObj.Config.Env...) + + args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...) + cwd := p.PluginObj.Manifest.Workdir + if len(cwd) == 0 { + cwd = "/" + } + s.Process = specs.Process{ + Terminal: false, + Args: args, + Cwd: cwd, + Env: envs, + } + + return &s, nil +} diff --git a/plugin/v2/plugin_experimental.go b/plugin/v2/plugin_experimental.go deleted file mode 100644 index 948437c186..0000000000 --- a/plugin/v2/plugin_experimental.go +++ /dev/null @@ -1,249 +0,0 @@ -// +build experimental - -package v2 - -import ( - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/plugins" - "github.com/docker/docker/pkg/system" - "github.com/opencontainers/runtime-spec/specs-go" -) - -const defaultPluginRuntimeDestination = "/run/docker/plugins" - -// ErrInadequateCapability indicates that the plugin did not have the requested capability. -type ErrInadequateCapability string - -func (cap ErrInadequateCapability) Error() string { - return fmt.Sprintf("plugin does not provide %q capability", cap) -} - -func newPluginObj(name, id, tag string) types.Plugin { - return types.Plugin{Name: name, ID: id, Tag: tag} -} - -// NewPlugin creates a plugin. -func NewPlugin(name, id, runRoot, tag string) *Plugin { - return &Plugin{ - PluginObj: newPluginObj(name, id, tag), - RuntimeSourcePath: filepath.Join(runRoot, id), - } -} - -// Client returns the plugin client. -func (p *Plugin) Client() *plugins.Client { - return p.PClient -} - -// IsV1 returns true for V1 plugins and false otherwise. -func (p *Plugin) IsV1() bool { - return false -} - -// Name returns the plugin name. -func (p *Plugin) Name() string { - name := p.PluginObj.Name - if len(p.PluginObj.Tag) > 0 { - // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these - name += ":" + p.PluginObj.Tag - } - return name -} - -// FilterByCap query the plugin for a given capability. -func (p *Plugin) FilterByCap(capability string) (*Plugin, error) { - capability = strings.ToLower(capability) - for _, typ := range p.PluginObj.Manifest.Interface.Types { - if typ.Capability == capability && typ.Prefix == "docker" { - return p, nil - } - } - return nil, ErrInadequateCapability(capability) -} - -// RemoveFromDisk deletes the plugin's runtime files from disk. -func (p *Plugin) RemoveFromDisk() error { - return os.RemoveAll(p.RuntimeSourcePath) -} - -// InitPlugin populates the plugin object from the plugin manifest file. -func (p *Plugin) InitPlugin(libRoot string) error { - dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json")) - if err != nil { - return err - } - err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest) - dt.Close() - if err != nil { - return err - } - - p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts)) - for i, mount := range p.PluginObj.Manifest.Mounts { - p.PluginObj.Config.Mounts[i] = mount - } - p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env)) - for _, env := range p.PluginObj.Manifest.Env { - if env.Value != nil { - p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) - } - } - copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value) - - f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json")) - if err != nil { - return err - } - err = json.NewEncoder(f).Encode(&p.PluginObj.Config) - f.Close() - return err -} - -// Set is used to pass arguments to the plugin. -func (p *Plugin) Set(args []string) error { - m := make(map[string]string, len(args)) - for _, arg := range args { - i := strings.Index(arg, "=") - if i < 0 { - return fmt.Errorf("No equal sign '=' found in %s", arg) - } - m[arg[:i]] = arg[i+1:] - } - return errors.New("not implemented") -} - -// ComputePrivileges takes the manifest file and computes the list of access necessary -// for the plugin on the host. -func (p *Plugin) ComputePrivileges() types.PluginPrivileges { - m := p.PluginObj.Manifest - var privileges types.PluginPrivileges - if m.Network.Type != "null" && m.Network.Type != "bridge" { - privileges = append(privileges, types.PluginPrivilege{ - Name: "network", - Description: "", - Value: []string{m.Network.Type}, - }) - } - for _, mount := range m.Mounts { - if mount.Source != nil { - privileges = append(privileges, types.PluginPrivilege{ - Name: "mount", - Description: "", - Value: []string{*mount.Source}, - }) - } - } - for _, device := range m.Devices { - if device.Path != nil { - privileges = append(privileges, types.PluginPrivilege{ - Name: "device", - Description: "", - Value: []string{*device.Path}, - }) - } - } - if len(m.Capabilities) > 0 { - privileges = append(privileges, types.PluginPrivilege{ - Name: "capabilities", - Description: "", - Value: m.Capabilities, - }) - } - return privileges -} - -// IsEnabled returns the active state of the plugin. -func (p *Plugin) IsEnabled() bool { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.Enabled -} - -// GetID returns the plugin's ID. -func (p *Plugin) GetID() string { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.ID -} - -// GetSocket returns the plugin socket. -func (p *Plugin) GetSocket() string { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.Manifest.Interface.Socket -} - -// GetTypes returns the interface types of a plugin. -func (p *Plugin) GetTypes() []types.PluginInterfaceType { - p.RLock() - defer p.RUnlock() - - return p.PluginObj.Manifest.Interface.Types -} - -// InitSpec creates an OCI spec from the plugin's config. -func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { - rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") - s.Root = specs.Root{ - Path: rootfs, - Readonly: false, // TODO: all plugins should be readonly? settable in manifest? - } - - mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ - Source: &p.RuntimeSourcePath, - Destination: defaultPluginRuntimeDestination, - Type: "bind", - Options: []string{"rbind", "rshared"}, - }) - for _, mount := range mounts { - m := specs.Mount{ - Destination: mount.Destination, - Type: mount.Type, - Options: mount.Options, - } - // TODO: if nil, then it's required and user didn't set it - if mount.Source != nil { - m.Source = *mount.Source - } - if m.Source != "" && m.Type == "bind" { - fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks - if err != nil { - return nil, err - } - if fi.IsDir() { - if err := os.MkdirAll(m.Source, 0700); err != nil { - return nil, err - } - } - } - s.Mounts = append(s.Mounts, m) - } - - envs := make([]string, 1, len(p.PluginObj.Config.Env)+1) - envs[0] = "PATH=" + system.DefaultPathEnv - envs = append(envs, p.PluginObj.Config.Env...) - - args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...) - cwd := p.PluginObj.Manifest.Workdir - if len(cwd) == 0 { - cwd = "/" - } - s.Process = specs.Process{ - Terminal: false, - Args: args, - Cwd: cwd, - Env: envs, - } - - return &s, nil -} diff --git a/utils/experimental.go b/utils/experimental.go deleted file mode 100644 index ceed0cb3ff..0000000000 --- a/utils/experimental.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build experimental - -package utils - -// ExperimentalBuild is a stub which always returns true for -// builds that include the "experimental" build tag -func ExperimentalBuild() bool { - return true -} diff --git a/utils/stubs.go b/utils/stubs.go deleted file mode 100644 index 8a496d392f..0000000000 --- a/utils/stubs.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !experimental - -package utils - -// ExperimentalBuild is a stub which always returns false for -// builds that do not include the "experimental" build tag -func ExperimentalBuild() bool { - return false -}