From 3976a33c1a578827da3726bec3927cc058fe326a Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Wed, 16 Nov 2016 17:58:06 -0800 Subject: [PATCH] router: Return explicit error rather than 404 for experimental. Instead of not adding experimental routes at all, fail with an explicit message if the daemon is not running in experimental mode. Added the `router.Experimental` which does this automatically. Signed-off-by: Andrea Luzzardi --- api/server/router/checkpoint/checkpoint.go | 6 +- api/server/router/experimental.go | 67 ++++++++++++++++++++++ api/server/router/swarm/cluster.go | 17 +----- cmd/dockerd/daemon.go | 24 +++++--- cmd/dockerd/routes_experimental.go | 15 ----- 5 files changed, 89 insertions(+), 40 deletions(-) create mode 100644 api/server/router/experimental.go delete mode 100644 cmd/dockerd/routes_experimental.go diff --git a/api/server/router/checkpoint/checkpoint.go b/api/server/router/checkpoint/checkpoint.go index a668176a78..c1e93926f5 100644 --- a/api/server/router/checkpoint/checkpoint.go +++ b/api/server/router/checkpoint/checkpoint.go @@ -29,8 +29,8 @@ func (r *checkpointRouter) Routes() []router.Route { 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), + router.Experimental(router.NewGetRoute("/containers/{name:.*}/checkpoints", r.getContainerCheckpoints)), + router.Experimental(router.NewPostRoute("/containers/{name:.*}/checkpoints", r.postContainerCheckpoint)), + router.Experimental(router.NewDeleteRoute("/containers/{name}/checkpoints/{checkpoint}", r.deleteContainerCheckpoint)), } } diff --git a/api/server/router/experimental.go b/api/server/router/experimental.go new file mode 100644 index 0000000000..51385c2552 --- /dev/null +++ b/api/server/router/experimental.go @@ -0,0 +1,67 @@ +package router + +import ( + "errors" + "net/http" + + "golang.org/x/net/context" + + apierrors "github.com/docker/docker/api/errors" + "github.com/docker/docker/api/server/httputils" +) + +var ( + errExperimentalFeature = errors.New("This experimental feature is disabled by default. Start the Docker daemon with --experimental in order to enable it.") +) + +// ExperimentalRoute defines an experimental API route that can be enabled or disabled. +type ExperimentalRoute interface { + Route + + Enable() + Disable() +} + +// experimentalRoute defines an experimental API route that can be enabled or disabled. +// It implements ExperimentalRoute +type experimentalRoute struct { + local Route + handler httputils.APIFunc +} + +// Enable enables this experimental route +func (r *experimentalRoute) Enable() { + r.handler = r.local.Handler() +} + +// Disable disables the experimental route +func (r *experimentalRoute) Disable() { + r.handler = experimentalHandler +} + +func experimentalHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + return apierrors.NewErrorWithStatusCode(errExperimentalFeature, http.StatusNotImplemented) +} + +// Handler returns returns the APIFunc to let the server wrap it in middlewares. +func (r *experimentalRoute) Handler() httputils.APIFunc { + return r.handler +} + +// Method returns the http method that the route responds to. +func (r *experimentalRoute) Method() string { + return r.local.Method() +} + +// Path returns the subpath where the route responds to. +func (r *experimentalRoute) Path() string { + return r.local.Path() +} + +// Experimental will mark a route as experimental. +func Experimental(r Route) Route { + return &experimentalRoute{ + local: r, + handler: experimentalHandler, + } +} diff --git a/api/server/router/swarm/cluster.go b/api/server/router/swarm/cluster.go index e7181a4d1b..352a80db14 100644 --- a/api/server/router/swarm/cluster.go +++ b/api/server/router/swarm/cluster.go @@ -1,9 +1,6 @@ package swarm -import ( - "github.com/docker/docker/api/server/router" - "github.com/docker/docker/daemon" -) +import "github.com/docker/docker/api/server/router" // swarmRouter is a router to talk with the build controller type swarmRouter struct { @@ -12,14 +9,11 @@ type swarmRouter struct { } // NewRouter initializes a new build router -func NewRouter(d *daemon.Daemon, b Backend) router.Router { +func NewRouter(b Backend) router.Router { r := &swarmRouter{ backend: b, } r.initRoutes() - if d.HasExperimental() { - r.addExperimentalRoutes() - } return r } @@ -28,12 +22,6 @@ func (sr *swarmRouter) Routes() []router.Route { return sr.routes } -func (sr *swarmRouter) addExperimentalRoutes() { - sr.routes = append(sr.routes, - router.Cancellable(router.NewGetRoute("/services/{id}/logs", sr.getServiceLogs)), - ) -} - func (sr *swarmRouter) initRoutes() { sr.routes = []router.Route{ router.NewPostRoute("/swarm/init", sr.initCluster), @@ -48,6 +36,7 @@ func (sr *swarmRouter) initRoutes() { router.NewPostRoute("/services/create", sr.createService), router.NewPostRoute("/services/{id}/update", sr.updateService), router.NewDeleteRoute("/services/{id}", sr.removeService), + router.Experimental(router.Cancellable(router.NewGetRoute("/services/{id}/logs", sr.getServiceLogs))), router.NewGetRoute("/nodes", sr.getNodes), router.NewGetRoute("/nodes/{id}", sr.getNode), router.NewDeleteRoute("/nodes/{id}", sr.removeNode), diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index 866c2ec3e2..be8025fd98 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/api/server/middleware" "github.com/docker/docker/api/server/router" "github.com/docker/docker/api/server/router/build" + checkpointrouter "github.com/docker/docker/api/server/router/checkpoint" "github.com/docker/docker/api/server/router/container" "github.com/docker/docker/api/server/router/image" "github.com/docker/docker/api/server/router/network" @@ -461,25 +462,32 @@ func loadDaemonCliConfig(opts daemonOptions) (*daemon.Config, error) { func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) { decoder := runconfig.ContainerDecoder{} - routers := []router.Router{} - - // we need to add the checkpoint router before the container router or the DELETE gets masked - routers = addExperimentalRouters(routers, d, decoder) - - routers = append(routers, []router.Router{ + routers := []router.Router{ + // we need to add the checkpoint router before the container router or the DELETE gets masked + checkpointrouter.NewRouter(d, decoder), container.NewRouter(d, decoder), image.NewRouter(d, decoder), systemrouter.NewRouter(d, c), volume.NewRouter(d), build.NewRouter(dockerfile.NewBuildManager(d)), - swarmrouter.NewRouter(d, c), + swarmrouter.NewRouter(c), pluginrouter.NewRouter(plugin.GetManager()), - }...) + } if d.NetworkControllerEnabled() { routers = append(routers, network.NewRouter(d, c)) } + if d.HasExperimental() { + for _, r := range routers { + for _, route := range r.Routes() { + if experimental, ok := route.(router.ExperimentalRoute); ok { + experimental.Enable() + } + } + } + } + s.InitRouter(utils.IsDebugEnabled(), routers...) } diff --git a/cmd/dockerd/routes_experimental.go b/cmd/dockerd/routes_experimental.go deleted file mode 100644 index 8c8abf2a3e..0000000000 --- a/cmd/dockerd/routes_experimental.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/docker/docker/api/server/httputils" - "github.com/docker/docker/api/server/router" - checkpointrouter "github.com/docker/docker/api/server/router/checkpoint" - "github.com/docker/docker/daemon" -) - -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)) -}