2014-03-28 22:59:29 +00:00
|
|
|
package server
|
2013-04-11 02:48:21 +00:00
|
|
|
|
|
|
|
import (
|
2015-05-07 16:49:07 +00:00
|
|
|
"crypto/tls"
|
2013-04-18 01:13:43 +00:00
|
|
|
"encoding/json"
|
2013-04-19 13:24:37 +00:00
|
|
|
"fmt"
|
2013-05-08 01:06:49 +00:00
|
|
|
"io"
|
2013-06-18 18:59:56 +00:00
|
|
|
"net"
|
2013-04-11 02:48:21 +00:00
|
|
|
"net/http"
|
2013-06-18 18:59:56 +00:00
|
|
|
"os"
|
2013-04-19 13:24:37 +00:00
|
|
|
"strings"
|
2014-03-28 22:59:29 +00:00
|
|
|
|
2014-07-24 20:37:44 +00:00
|
|
|
"github.com/gorilla/mux"
|
2014-05-02 21:43:51 +00:00
|
|
|
|
2015-03-26 22:22:04 +00:00
|
|
|
"github.com/Sirupsen/logrus"
|
2015-07-21 20:30:32 +00:00
|
|
|
"github.com/docker/distribution/registry/api/errcode"
|
2014-07-24 22:19:50 +00:00
|
|
|
"github.com/docker/docker/api"
|
2015-09-09 21:14:09 +00:00
|
|
|
"github.com/docker/docker/context"
|
2015-03-31 23:21:37 +00:00
|
|
|
"github.com/docker/docker/daemon"
|
2015-05-20 23:48:39 +00:00
|
|
|
"github.com/docker/docker/pkg/sockets"
|
2015-09-18 17:48:16 +00:00
|
|
|
"github.com/docker/docker/utils"
|
2013-04-11 02:48:21 +00:00
|
|
|
)
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// Config provides the configuration for the API server
|
|
|
|
type Config struct {
|
2015-04-16 19:48:04 +00:00
|
|
|
Logging bool
|
|
|
|
EnableCors bool
|
|
|
|
CorsHeaders string
|
|
|
|
Version string
|
|
|
|
SocketGroup string
|
2015-05-07 16:49:07 +00:00
|
|
|
TLSConfig *tls.Config
|
2015-04-16 19:48:04 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// Server contains instance details for the server
|
2015-04-17 21:32:18 +00:00
|
|
|
type Server struct {
|
2015-04-27 21:11:29 +00:00
|
|
|
daemon *daemon.Daemon
|
2015-07-21 05:15:44 +00:00
|
|
|
cfg *Config
|
2015-04-27 21:11:29 +00:00
|
|
|
router *mux.Router
|
|
|
|
start chan struct{}
|
|
|
|
servers []serverCloser
|
2015-04-17 21:32:18 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// New returns a new instance of the server based on the specified configuration.
|
2015-09-29 17:51:40 +00:00
|
|
|
func New(cfg *Config) *Server {
|
2015-04-17 22:18:28 +00:00
|
|
|
srv := &Server{
|
|
|
|
cfg: cfg,
|
|
|
|
start: make(chan struct{}),
|
2015-04-17 21:32:18 +00:00
|
|
|
}
|
2015-09-29 17:51:40 +00:00
|
|
|
srv.router = createRouter(srv)
|
2015-04-17 22:18:28 +00:00
|
|
|
return srv
|
2015-04-17 21:32:18 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// Close closes servers and thus stop receiving requests
|
2015-04-27 21:11:29 +00:00
|
|
|
func (s *Server) Close() {
|
|
|
|
for _, srv := range s.servers {
|
|
|
|
if err := srv.Close(); err != nil {
|
|
|
|
logrus.Error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-17 21:32:18 +00:00
|
|
|
type serverCloser interface {
|
|
|
|
Serve() error
|
|
|
|
Close() error
|
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// ServeAPI loops through all of the protocols sent in to docker and spawns
|
2015-04-17 21:32:18 +00:00
|
|
|
// off a go routine to setup a serving http.Server for each.
|
2015-07-21 05:15:44 +00:00
|
|
|
func (s *Server) ServeAPI(protoAddrs []string) error {
|
2015-04-17 21:32:18 +00:00
|
|
|
var chErrors = make(chan error, len(protoAddrs))
|
|
|
|
|
|
|
|
for _, protoAddr := range protoAddrs {
|
|
|
|
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
|
|
|
if len(protoAddrParts) != 2 {
|
|
|
|
return fmt.Errorf("bad format, expected PROTO://ADDR")
|
|
|
|
}
|
2015-04-27 21:11:29 +00:00
|
|
|
srv, err := s.newServer(protoAddrParts[0], protoAddrParts[1])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-05-28 19:15:03 +00:00
|
|
|
s.servers = append(s.servers, srv...)
|
2015-04-27 21:11:29 +00:00
|
|
|
|
2015-05-28 19:15:03 +00:00
|
|
|
for _, s := range srv {
|
|
|
|
logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
|
|
|
|
go func(s serverCloser) {
|
|
|
|
if err := s.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
chErrors <- err
|
|
|
|
}(s)
|
|
|
|
}
|
2015-04-17 21:32:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(protoAddrs); i++ {
|
|
|
|
err := <-chErrors
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// HTTPServer contains an instance of http server and the listener.
|
|
|
|
// srv *http.Server, contains configuration to create a http server and a mux router with all api end points.
|
|
|
|
// l net.Listener, is a TCP or Socket listener that dispatches incoming request to the router.
|
|
|
|
type HTTPServer struct {
|
2014-11-07 20:21:19 +00:00
|
|
|
srv *http.Server
|
|
|
|
l net.Listener
|
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// Serve starts listening for inbound requests.
|
|
|
|
func (s *HTTPServer) Serve() error {
|
2014-11-07 20:21:19 +00:00
|
|
|
return s.srv.Serve(s.l)
|
|
|
|
}
|
2015-07-21 05:15:44 +00:00
|
|
|
|
|
|
|
// Close closes the HTTPServer from listening for the inbound requests.
|
|
|
|
func (s *HTTPServer) Close() error {
|
2014-11-07 20:21:19 +00:00
|
|
|
return s.l.Close()
|
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// HTTPAPIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
|
|
|
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
|
2015-08-31 14:55:51 +00:00
|
|
|
type HTTPAPIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
2013-07-13 14:59:07 +00:00
|
|
|
|
2013-05-08 01:06:49 +00:00
|
|
|
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
|
|
conn, _, err := w.(http.Hijacker).Hijack()
|
2013-05-06 09:31:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
// Flush the options to make sure the client sets the raw mode
|
2013-05-08 01:06:49 +00:00
|
|
|
conn.Write([]byte{})
|
|
|
|
return conn, conn, nil
|
2013-05-06 09:31:22 +00:00
|
|
|
}
|
|
|
|
|
2014-12-09 19:21:21 +00:00
|
|
|
func closeStreams(streams ...interface{}) {
|
|
|
|
for _, stream := range streams {
|
|
|
|
if tcpc, ok := stream.(interface {
|
|
|
|
CloseWrite() error
|
|
|
|
}); ok {
|
|
|
|
tcpc.CloseWrite()
|
|
|
|
} else if closer, ok := stream.(io.Closer); ok {
|
|
|
|
closer.Close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
// checkForJSON makes sure that the request's Content-Type is application/json.
|
|
|
|
func checkForJSON(r *http.Request) error {
|
2014-09-25 15:24:41 +00:00
|
|
|
ct := r.Header.Get("Content-Type")
|
|
|
|
|
|
|
|
// No Content-Type header is ok as long as there's no Body
|
|
|
|
if ct == "" {
|
|
|
|
if r.Body == nil || r.ContentLength == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise it better be json
|
|
|
|
if api.MatchesContentType(ct, "application/json") {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
|
|
|
}
|
|
|
|
|
2013-05-08 16:52:01 +00:00
|
|
|
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
|
|
|
func parseForm(r *http.Request) error {
|
2013-10-08 19:15:29 +00:00
|
|
|
if r == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2013-05-08 16:52:01 +00:00
|
|
|
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-23 03:07:26 +00:00
|
|
|
func parseMultipartForm(r *http.Request) error {
|
|
|
|
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-05-06 09:31:22 +00:00
|
|
|
func httpError(w http.ResponseWriter, err error) {
|
2015-04-20 09:49:51 +00:00
|
|
|
if err == nil || w == nil {
|
|
|
|
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
|
|
|
return
|
|
|
|
}
|
2015-07-21 20:30:32 +00:00
|
|
|
|
2013-07-11 12:21:43 +00:00
|
|
|
statusCode := http.StatusInternalServerError
|
2015-07-21 20:30:32 +00:00
|
|
|
errMsg := err.Error()
|
|
|
|
|
|
|
|
// Based on the type of error we get we need to process things
|
|
|
|
// slightly differently to extract the error message.
|
|
|
|
// In the 'errcode.*' cases there are two different type of
|
|
|
|
// error that could be returned. errocode.ErrorCode is the base
|
|
|
|
// type of error object - it is just an 'int' that can then be
|
|
|
|
// used as the look-up key to find the message. errorcode.Error
|
|
|
|
// extends errorcode.Error by adding error-instance specific
|
|
|
|
// data, like 'details' or variable strings to be inserted into
|
|
|
|
// the message.
|
|
|
|
//
|
|
|
|
// Ideally, we should just be able to call err.Error() for all
|
|
|
|
// cases but the errcode package doesn't support that yet.
|
|
|
|
//
|
|
|
|
// Additionally, in both errcode cases, there might be an http
|
|
|
|
// status code associated with it, and if so use it.
|
|
|
|
switch err.(type) {
|
|
|
|
case errcode.ErrorCode:
|
|
|
|
daError, _ := err.(errcode.ErrorCode)
|
|
|
|
statusCode = daError.Descriptor().HTTPStatusCode
|
|
|
|
errMsg = daError.Message()
|
|
|
|
|
|
|
|
case errcode.Error:
|
|
|
|
// For reference, if you're looking for a particular error
|
|
|
|
// then you can do something like :
|
2015-09-17 18:54:14 +00:00
|
|
|
// import ( derr "github.com/docker/docker/errors" )
|
2015-07-21 20:30:32 +00:00
|
|
|
// if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
|
|
|
|
|
|
|
|
daError, _ := err.(errcode.Error)
|
|
|
|
statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
|
|
|
|
errMsg = daError.Message
|
|
|
|
|
|
|
|
default:
|
|
|
|
// This part of will be removed once we've
|
|
|
|
// converted everything over to use the errcode package
|
|
|
|
|
|
|
|
// FIXME: this is brittle and should not be necessary.
|
|
|
|
// If we need to differentiate between different possible error types,
|
|
|
|
// we should create appropriate error types with clearly defined meaning
|
|
|
|
errStr := strings.ToLower(err.Error())
|
|
|
|
for keyword, status := range map[string]int{
|
|
|
|
"not found": http.StatusNotFound,
|
|
|
|
"no such": http.StatusNotFound,
|
|
|
|
"bad parameter": http.StatusBadRequest,
|
|
|
|
"conflict": http.StatusConflict,
|
|
|
|
"impossible": http.StatusNotAcceptable,
|
|
|
|
"wrong login/password": http.StatusUnauthorized,
|
|
|
|
"hasn't been activated": http.StatusForbidden,
|
|
|
|
} {
|
|
|
|
if strings.Contains(errStr, keyword) {
|
|
|
|
statusCode = status
|
|
|
|
break
|
|
|
|
}
|
2015-04-23 11:37:47 +00:00
|
|
|
}
|
2013-10-17 00:08:14 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 20:30:32 +00:00
|
|
|
if statusCode == 0 {
|
|
|
|
statusCode = http.StatusInternalServerError
|
|
|
|
}
|
|
|
|
|
2015-09-18 17:48:16 +00:00
|
|
|
logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
|
2015-07-21 20:30:32 +00:00
|
|
|
http.Error(w, errMsg, statusCode)
|
2013-05-06 09:31:22 +00:00
|
|
|
}
|
|
|
|
|
2015-02-24 19:12:47 +00:00
|
|
|
// writeJSON writes the value v to the http response stream as json with standard
|
|
|
|
// json encoding.
|
|
|
|
func writeJSON(w http.ResponseWriter, code int, v interface{}) error {
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(code)
|
|
|
|
return json.NewEncoder(w).Encode(v)
|
|
|
|
}
|
|
|
|
|
2015-08-31 14:55:51 +00:00
|
|
|
func (s *Server) optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2013-06-11 01:10:40 +00:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
return nil
|
|
|
|
}
|
2015-02-09 07:15:07 +00:00
|
|
|
func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
|
2015-03-26 22:22:04 +00:00
|
|
|
logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
|
2015-02-09 07:15:07 +00:00
|
|
|
w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
|
2014-11-01 16:22:28 +00:00
|
|
|
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
|
2015-08-24 09:57:22 +00:00
|
|
|
w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
|
2013-06-04 01:39:00 +00:00
|
|
|
}
|
|
|
|
|
2015-08-31 14:55:51 +00:00
|
|
|
func (s *Server) ping(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
2014-05-02 22:03:59 +00:00
|
|
|
_, err := w.Write([]byte{'O', 'K'})
|
|
|
|
return err
|
2014-05-02 21:43:51 +00:00
|
|
|
}
|
|
|
|
|
2015-07-21 05:15:44 +00:00
|
|
|
func (s *Server) initTCPSocket(addr string) (l net.Listener, err error) {
|
2015-05-07 16:49:07 +00:00
|
|
|
if s.cfg.TLSConfig == nil || s.cfg.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert {
|
2015-05-20 23:48:39 +00:00
|
|
|
logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
|
|
|
}
|
2015-07-25 08:35:07 +00:00
|
|
|
if l, err = sockets.NewTCPSocket(addr, s.cfg.TLSConfig, s.start); err != nil {
|
2015-05-20 23:48:39 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := allocateDaemonPort(addr); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-09-29 17:51:40 +00:00
|
|
|
func (s *Server) makeHTTPHandler(localMethod string, localRoute string, localHandler HTTPAPIFunc) http.HandlerFunc {
|
2013-07-13 14:59:07 +00:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2015-09-15 23:01:49 +00:00
|
|
|
// log the handler generation
|
|
|
|
logrus.Debugf("Calling %s %s", localMethod, localRoute)
|
|
|
|
|
2015-08-31 14:55:51 +00:00
|
|
|
// Define the context that we'll pass around to share info
|
|
|
|
// like the docker-request-id.
|
|
|
|
//
|
|
|
|
// The 'context' will be used for global data that should
|
|
|
|
// apply to all requests. Data that is specific to the
|
|
|
|
// immediate function being called should still be passed
|
|
|
|
// as 'args' on the function call.
|
2015-09-29 17:51:40 +00:00
|
|
|
ctx := context.Background()
|
2015-09-15 23:01:49 +00:00
|
|
|
handlerFunc := s.handleWithGlobalMiddlewares(localHandler)
|
2015-06-04 13:30:14 +00:00
|
|
|
|
2015-08-31 14:55:51 +00:00
|
|
|
if err := handlerFunc(ctx, w, r, mux.Vars(r)); err != nil {
|
2015-09-18 17:48:16 +00:00
|
|
|
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, utils.GetErrorMessage(err))
|
2013-07-13 14:59:07 +00:00
|
|
|
httpError(w, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-15 23:01:49 +00:00
|
|
|
// createRouter initializes the main router the server uses.
|
2015-02-09 07:15:07 +00:00
|
|
|
// we keep enableCors just for legacy usage, need to be removed in the future
|
2015-09-29 17:51:40 +00:00
|
|
|
func createRouter(s *Server) *mux.Router {
|
2013-05-07 23:33:12 +00:00
|
|
|
r := mux.NewRouter()
|
2013-11-19 19:25:17 +00:00
|
|
|
if os.Getenv("DEBUG") != "" {
|
2015-07-21 05:15:44 +00:00
|
|
|
profilerSetup(r, "/debug/")
|
2013-11-19 19:25:17 +00:00
|
|
|
}
|
2015-07-21 05:15:44 +00:00
|
|
|
m := map[string]map[string]HTTPAPIFunc{
|
2015-05-13 22:01:51 +00:00
|
|
|
"HEAD": {
|
|
|
|
"/containers/{name:.*}/archive": s.headContainersArchive,
|
|
|
|
},
|
2013-05-07 23:33:12 +00:00
|
|
|
"GET": {
|
2015-04-17 22:18:28 +00:00
|
|
|
"/_ping": s.ping,
|
|
|
|
"/events": s.getEvents,
|
|
|
|
"/info": s.getInfo,
|
|
|
|
"/version": s.getVersion,
|
|
|
|
"/images/json": s.getImagesJSON,
|
|
|
|
"/images/search": s.getImagesSearch,
|
|
|
|
"/images/get": s.getImagesGet,
|
|
|
|
"/images/{name:.*}/get": s.getImagesGet,
|
|
|
|
"/images/{name:.*}/history": s.getImagesHistory,
|
|
|
|
"/images/{name:.*}/json": s.getImagesByName,
|
|
|
|
"/containers/json": s.getContainersJSON,
|
|
|
|
"/containers/{name:.*}/export": s.getContainersExport,
|
|
|
|
"/containers/{name:.*}/changes": s.getContainersChanges,
|
|
|
|
"/containers/{name:.*}/json": s.getContainersByName,
|
|
|
|
"/containers/{name:.*}/top": s.getContainersTop,
|
|
|
|
"/containers/{name:.*}/logs": s.getContainersLogs,
|
|
|
|
"/containers/{name:.*}/stats": s.getContainersStats,
|
|
|
|
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
|
|
|
|
"/exec/{id:.*}/json": s.getExecByID,
|
2015-05-13 22:01:51 +00:00
|
|
|
"/containers/{name:.*}/archive": s.getContainersArchive,
|
2015-06-12 13:25:32 +00:00
|
|
|
"/volumes": s.getVolumesList,
|
|
|
|
"/volumes/{name:.*}": s.getVolumeByName,
|
2013-05-07 23:33:12 +00:00
|
|
|
},
|
|
|
|
"POST": {
|
2015-04-17 22:18:28 +00:00
|
|
|
"/auth": s.postAuth,
|
|
|
|
"/commit": s.postCommit,
|
|
|
|
"/build": s.postBuild,
|
|
|
|
"/images/create": s.postImagesCreate,
|
|
|
|
"/images/load": s.postImagesLoad,
|
|
|
|
"/images/{name:.*}/push": s.postImagesPush,
|
|
|
|
"/images/{name:.*}/tag": s.postImagesTag,
|
|
|
|
"/containers/create": s.postContainersCreate,
|
|
|
|
"/containers/{name:.*}/kill": s.postContainersKill,
|
|
|
|
"/containers/{name:.*}/pause": s.postContainersPause,
|
|
|
|
"/containers/{name:.*}/unpause": s.postContainersUnpause,
|
|
|
|
"/containers/{name:.*}/restart": s.postContainersRestart,
|
|
|
|
"/containers/{name:.*}/start": s.postContainersStart,
|
|
|
|
"/containers/{name:.*}/stop": s.postContainersStop,
|
|
|
|
"/containers/{name:.*}/wait": s.postContainersWait,
|
|
|
|
"/containers/{name:.*}/resize": s.postContainersResize,
|
|
|
|
"/containers/{name:.*}/attach": s.postContainersAttach,
|
|
|
|
"/containers/{name:.*}/copy": s.postContainersCopy,
|
|
|
|
"/containers/{name:.*}/exec": s.postContainerExecCreate,
|
|
|
|
"/exec/{name:.*}/start": s.postContainerExecStart,
|
|
|
|
"/exec/{name:.*}/resize": s.postContainerExecResize,
|
|
|
|
"/containers/{name:.*}/rename": s.postContainerRename,
|
2015-06-12 13:25:32 +00:00
|
|
|
"/volumes": s.postVolumesCreate,
|
2013-05-07 23:33:12 +00:00
|
|
|
},
|
2015-05-13 22:01:51 +00:00
|
|
|
"PUT": {
|
|
|
|
"/containers/{name:.*}/archive": s.putContainersArchive,
|
|
|
|
},
|
2013-05-07 23:33:12 +00:00
|
|
|
"DELETE": {
|
2015-04-17 22:18:28 +00:00
|
|
|
"/containers/{name:.*}": s.deleteContainers,
|
|
|
|
"/images/{name:.*}": s.deleteImages,
|
2015-06-12 13:25:32 +00:00
|
|
|
"/volumes/{name:.*}": s.deleteVolumes,
|
2013-05-07 23:33:12 +00:00
|
|
|
},
|
2013-06-11 01:10:40 +00:00
|
|
|
"OPTIONS": {
|
2015-04-17 22:18:28 +00:00
|
|
|
"": s.optionsHandler,
|
2013-06-11 01:10:40 +00:00
|
|
|
},
|
2013-05-07 23:33:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for method, routes := range m {
|
|
|
|
for route, fct := range routes {
|
2015-03-26 22:22:04 +00:00
|
|
|
logrus.Debugf("Registering %s, %s", method, route)
|
2013-05-07 23:33:12 +00:00
|
|
|
// NOTE: scope issue, make sure the variables are local and won't be changed
|
|
|
|
localRoute := route
|
|
|
|
localFct := fct
|
2013-07-13 14:59:07 +00:00
|
|
|
localMethod := method
|
2013-06-21 01:18:36 +00:00
|
|
|
|
2013-07-13 14:59:07 +00:00
|
|
|
// build the handler function
|
2015-09-29 17:51:40 +00:00
|
|
|
f := s.makeHTTPHandler(localMethod, localRoute, localFct)
|
2013-06-11 01:10:40 +00:00
|
|
|
|
2013-07-13 14:59:07 +00:00
|
|
|
// add the new route
|
2013-06-11 01:10:40 +00:00
|
|
|
if localRoute == "" {
|
|
|
|
r.Methods(localMethod).HandlerFunc(f)
|
|
|
|
} else {
|
|
|
|
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
|
|
|
|
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
|
|
|
|
}
|
2013-04-29 15:46:41 +00:00
|
|
|
}
|
2013-05-07 23:33:12 +00:00
|
|
|
}
|
2013-07-05 17:55:15 +00:00
|
|
|
|
2015-01-13 21:41:34 +00:00
|
|
|
return r
|
2013-06-10 22:02:40 +00:00
|
|
|
}
|