|
@@ -1,6 +1,7 @@
|
|
|
package docker
|
|
|
|
|
|
import (
|
|
|
+ "code.google.com/p/go.net/websocket"
|
|
|
"encoding/json"
|
|
|
"fmt"
|
|
|
"github.com/dotcloud/docker/auth"
|
|
@@ -9,6 +10,7 @@ import (
|
|
|
"io"
|
|
|
"io/ioutil"
|
|
|
"log"
|
|
|
+ "mime"
|
|
|
"net"
|
|
|
"net/http"
|
|
|
"os"
|
|
@@ -23,6 +25,8 @@ const DEFAULTHTTPHOST = "127.0.0.1"
|
|
|
const DEFAULTHTTPPORT = 4243
|
|
|
const DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
|
|
|
|
|
+type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
|
|
+
|
|
|
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
|
conn, _, err := w.(http.Hijacker).Hijack()
|
|
|
if err != nil {
|
|
@@ -83,13 +87,21 @@ func getBoolParam(value string) (bool, error) {
|
|
|
return ret, nil
|
|
|
}
|
|
|
|
|
|
+func matchesContentType(contentType, expectedType string) bool {
|
|
|
+ mimetype, _, err := mime.ParseMediaType(contentType)
|
|
|
+ if err != nil {
|
|
|
+ utils.Debugf("Error parsing media type: %s error: %s", contentType, err.Error())
|
|
|
+ }
|
|
|
+ return err == nil && mimetype == expectedType
|
|
|
+}
|
|
|
+
|
|
|
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
authConfig := &auth.AuthConfig{}
|
|
|
err := json.NewDecoder(r.Body).Decode(authConfig)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- status, err := auth.Login(authConfig)
|
|
|
+ status, err := auth.Login(authConfig, srv.HTTPRequestFactory())
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -225,8 +237,7 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- for {
|
|
|
- event := <-listener
|
|
|
+ for event := range listener {
|
|
|
err := sendEvent(wf, &event)
|
|
|
if err != nil && err.Error() == "JSON error" {
|
|
|
continue
|
|
@@ -388,7 +399,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
|
|
|
}
|
|
|
sf := utils.NewStreamFormatter(version > 1.0)
|
|
|
if image != "" { //pull
|
|
|
- if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil {
|
|
|
+ if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}, version > 1.3); err != nil {
|
|
|
if sf.Used() {
|
|
|
w.Write(sf.FormatError(err))
|
|
|
return nil
|
|
@@ -490,7 +501,12 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
|
|
+ resolvConf, err := utils.GetResolvConf()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
|
|
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
|
|
|
config.Dns = defaultDns
|
|
|
}
|
|
@@ -510,6 +526,11 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
|
|
|
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
|
|
|
}
|
|
|
|
|
|
+ if srv.runtime.capabilities.IPv4ForwardingDisabled {
|
|
|
+ log.Println("Warning: IPv4 forwarding is disabled.")
|
|
|
+ out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
|
|
|
+ }
|
|
|
+
|
|
|
b, err := json.Marshal(out)
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -591,7 +612,7 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
|
|
|
|
|
|
// allow a nil body for backwards compatibility
|
|
|
if r.Body != nil {
|
|
|
- if r.Header.Get("Content-Type") == "application/json" {
|
|
|
+ if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
|
|
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -729,6 +750,53 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
+
|
|
|
+ if err := parseForm(r); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ logs, err := getBoolParam(r.Form.Get("logs"))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ stream, err := getBoolParam(r.Form.Get("stream"))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ stdin, err := getBoolParam(r.Form.Get("stdin"))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ stdout, err := getBoolParam(r.Form.Get("stdout"))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ stderr, err := getBoolParam(r.Form.Get("stderr"))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if vars == nil {
|
|
|
+ return fmt.Errorf("Missing parameter")
|
|
|
+ }
|
|
|
+ name := vars["name"]
|
|
|
+
|
|
|
+ if _, err := srv.ContainerInspect(name); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ h := websocket.Handler(func(ws *websocket.Conn) {
|
|
|
+ defer ws.Close()
|
|
|
+
|
|
|
+ if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil {
|
|
|
+ utils.Debugf("Error: %s", err)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ h.ServeHTTP(w, r)
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
if vars == nil {
|
|
|
return fmt.Errorf("Missing parameter")
|
|
@@ -795,12 +863,8 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|
|
remoteURL := r.FormValue("remote")
|
|
|
repoName := r.FormValue("t")
|
|
|
rawSuppressOutput := r.FormValue("q")
|
|
|
- tag := ""
|
|
|
- if strings.Contains(repoName, ":") {
|
|
|
- remoteParts := strings.Split(repoName, ":")
|
|
|
- tag = remoteParts[1]
|
|
|
- repoName = remoteParts[0]
|
|
|
- }
|
|
|
+ rawNoCache := r.FormValue("nocache")
|
|
|
+ repoName, tag := utils.ParseRepositoryTag(repoName)
|
|
|
|
|
|
var context io.Reader
|
|
|
|
|
@@ -846,8 +910,12 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+ noCache, err := getBoolParam(rawNoCache)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
|
|
|
- b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput)
|
|
|
+ b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput, !noCache)
|
|
|
id, err := b.Build(context)
|
|
|
if err != nil {
|
|
|
fmt.Fprintf(w, "Error build: %s\n", err)
|
|
@@ -859,6 +927,36 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
+ if vars == nil {
|
|
|
+ return fmt.Errorf("Missing parameter")
|
|
|
+ }
|
|
|
+ name := vars["name"]
|
|
|
+
|
|
|
+ copyData := &APICopy{}
|
|
|
+ contentType := r.Header.Get("Content-Type")
|
|
|
+ if contentType == "application/json" {
|
|
|
+ if err := json.NewDecoder(r.Body).Decode(copyData); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return fmt.Errorf("Content-Type not supported: %s", contentType)
|
|
|
+ }
|
|
|
+
|
|
|
+ if copyData.Resource == "" {
|
|
|
+ return fmt.Errorf("Resource cannot be empty")
|
|
|
+ }
|
|
|
+ if copyData.Resource[0] == '/' {
|
|
|
+ copyData.Resource = copyData.Resource[1:]
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := srv.ContainerCopy(name, copyData.Resource, w); err != nil {
|
|
|
+ utils.Debugf("%s", err.Error())
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
return nil
|
|
@@ -869,25 +967,61 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
|
|
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
|
|
}
|
|
|
|
|
|
+func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc) http.HandlerFunc {
|
|
|
+ return func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ // log the request
|
|
|
+ utils.Debugf("Calling %s %s", localMethod, localRoute)
|
|
|
+
|
|
|
+ if logging {
|
|
|
+ log.Println(r.Method, r.RequestURI)
|
|
|
+ }
|
|
|
+
|
|
|
+ if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
|
|
+ userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
|
|
+ if len(userAgent) == 2 && userAgent[1] != VERSION {
|
|
|
+ utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
|
|
|
+ if err != nil {
|
|
|
+ version = APIVERSION
|
|
|
+ }
|
|
|
+ if srv.enableCors {
|
|
|
+ writeCorsHeaders(w, r)
|
|
|
+ }
|
|
|
+
|
|
|
+ if version == 0 || version > APIVERSION {
|
|
|
+ w.WriteHeader(http.StatusNotFound)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := handlerFunc(srv, version, w, r, mux.Vars(r)); err != nil {
|
|
|
+ utils.Debugf("Error: %s", err)
|
|
|
+ httpError(w, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|
|
r := mux.NewRouter()
|
|
|
|
|
|
- m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
|
|
|
+ m := map[string]map[string]HttpApiFunc{
|
|
|
"GET": {
|
|
|
- "/events": getEvents,
|
|
|
- "/info": getInfo,
|
|
|
- "/version": getVersion,
|
|
|
- "/images/json": getImagesJSON,
|
|
|
- "/images/viz": getImagesViz,
|
|
|
- "/images/search": getImagesSearch,
|
|
|
- "/images/{name:.*}/history": getImagesHistory,
|
|
|
- "/images/{name:.*}/json": getImagesByName,
|
|
|
- "/containers/ps": getContainersJSON,
|
|
|
- "/containers/json": getContainersJSON,
|
|
|
- "/containers/{name:.*}/export": getContainersExport,
|
|
|
- "/containers/{name:.*}/changes": getContainersChanges,
|
|
|
- "/containers/{name:.*}/json": getContainersByName,
|
|
|
- "/containers/{name:.*}/top": getContainersTop,
|
|
|
+ "/events": getEvents,
|
|
|
+ "/info": getInfo,
|
|
|
+ "/version": getVersion,
|
|
|
+ "/images/json": getImagesJSON,
|
|
|
+ "/images/viz": getImagesViz,
|
|
|
+ "/images/search": getImagesSearch,
|
|
|
+ "/images/{name:.*}/history": getImagesHistory,
|
|
|
+ "/images/{name:.*}/json": getImagesByName,
|
|
|
+ "/containers/ps": getContainersJSON,
|
|
|
+ "/containers/json": getContainersJSON,
|
|
|
+ "/containers/{name:.*}/export": getContainersExport,
|
|
|
+ "/containers/{name:.*}/changes": getContainersChanges,
|
|
|
+ "/containers/{name:.*}/json": getContainersByName,
|
|
|
+ "/containers/{name:.*}/top": getContainersTop,
|
|
|
+ "/containers/{name:.*}/attach/ws": wsContainersAttach,
|
|
|
},
|
|
|
"POST": {
|
|
|
"/auth": postAuth,
|
|
@@ -906,6 +1040,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|
|
"/containers/{name:.*}/wait": postContainersWait,
|
|
|
"/containers/{name:.*}/resize": postContainersResize,
|
|
|
"/containers/{name:.*}/attach": postContainersAttach,
|
|
|
+ "/containers/{name:.*}/copy": postContainersCopy,
|
|
|
},
|
|
|
"DELETE": {
|
|
|
"/containers/{name:.*}": deleteContainers,
|
|
@@ -921,37 +1056,13 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|
|
utils.Debugf("Registering %s, %s", method, route)
|
|
|
// NOTE: scope issue, make sure the variables are local and won't be changed
|
|
|
localRoute := route
|
|
|
- localMethod := method
|
|
|
localFct := fct
|
|
|
- f := func(w http.ResponseWriter, r *http.Request) {
|
|
|
- utils.Debugf("Calling %s %s from %s", localMethod, localRoute, r.RemoteAddr)
|
|
|
-
|
|
|
- if logging {
|
|
|
- log.Println(r.Method, r.RequestURI)
|
|
|
- }
|
|
|
- if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
|
|
- userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
|
|
- if len(userAgent) == 2 && userAgent[1] != VERSION {
|
|
|
- utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
|
|
|
- }
|
|
|
- }
|
|
|
- version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
|
|
|
- if err != nil {
|
|
|
- version = APIVERSION
|
|
|
- }
|
|
|
- if srv.enableCors {
|
|
|
- writeCorsHeaders(w, r)
|
|
|
- }
|
|
|
- if version == 0 || version > APIVERSION {
|
|
|
- w.WriteHeader(http.StatusNotFound)
|
|
|
- return
|
|
|
- }
|
|
|
+ localMethod := method
|
|
|
|
|
|
- if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
|
|
|
- httpError(w, err)
|
|
|
- }
|
|
|
- }
|
|
|
+ // build the handler function
|
|
|
+ f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct)
|
|
|
|
|
|
+ // add the new route
|
|
|
if localRoute == "" {
|
|
|
r.Methods(localMethod).HandlerFunc(f)
|
|
|
} else {
|
|
@@ -960,6 +1071,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
return r, nil
|
|
|
}
|
|
|
|
|
@@ -975,7 +1087,10 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
|
|
return e
|
|
|
}
|
|
|
if proto == "unix" {
|
|
|
- os.Chmod(addr, 0660)
|
|
|
+ if err := os.Chmod(addr, 0660); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
groups, err := ioutil.ReadFile("/etc/group")
|
|
|
if err != nil {
|
|
|
return err
|
|
@@ -987,7 +1102,9 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
|
|
return err
|
|
|
}
|
|
|
utils.Debugf("docker group found. gid: %d", gid)
|
|
|
- os.Chown(addr, 0, gid)
|
|
|
+ if err := os.Chown(addr, 0, gid); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
httpSrv := http.Server{Addr: addr, Handler: r}
|