From 64450ae3f89b8f9b5288224c5a7d109a166cf22a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 3 Jul 2013 17:11:00 +0000 Subject: [PATCH 001/242] add last version --- commands.go | 9 +++++++++ utils/utils.go | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/commands.go b/commands.go index 6e1e5e88c2..e275bdd671 100644 --- a/commands.go +++ b/commands.go @@ -407,6 +407,15 @@ func (cli *DockerCli) CmdVersion(args ...string) error { if out.GoVersion != "" { fmt.Fprintf(cli.out, "Go version: %s\n", out.GoVersion) } + + release := utils.GetReleaseVersion() + if release != "" { + fmt.Fprintf(cli.out, "Last stable version: %s", release) + if VERSION != release || out.Version != release { + fmt.Fprintf(cli.out, ", please update docker") + } + fmt.Fprintf(cli.out, "\n") + } return nil } diff --git a/utils/utils.go b/utils/utils.go index 2f2a52867e..b4a41ea420 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -687,3 +687,25 @@ func ParseHost(host string, port int, addr string) string { } return fmt.Sprintf("tcp://%s:%d", host, port) } + +func GetReleaseVersion() string { + type githubTag struct { + Name string `json:"name"` + } + + resp, err := http.Get("https://api.github.com/repos/dotcloud/docker/tags") + if err != nil { + return "" + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "" + } + var tags []githubTag + err = json.Unmarshal(body, &tags) + if err != nil || len(tags) == 0 { + return "" + } + return strings.TrimPrefix(tags[0].Name, "v") +} From 8eeff01939e3b9f09e5233f6cde60e1c10af0a63 Mon Sep 17 00:00:00 2001 From: benoitc Date: Fri, 5 Jul 2013 19:55:15 +0200 Subject: [PATCH 002/242] add websocket support to /container//attach/ws This function add the possibility to attach containers streams to a websocket. When a websocket is asked the request is upgraded to this protocol.. --- api.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/api.go b/api.go index 1970feaec6..45261973a7 100644 --- a/api.go +++ b/api.go @@ -1,6 +1,7 @@ package docker import ( + "code.google.com/p/go.net/websocket" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -693,6 +694,52 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return nil } +func wsContainersAttach(srv *Server, ws *websocket.Conn, vars map[string]string) error { + + r := ws.Request() + + 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 + } + + defer ws.Close() + + if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil { + return err + } + + 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") @@ -898,12 +945,14 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { if srv.enableCors { writeCorsHeaders(w, r) } + if version == 0 || version > APIVERSION { w.WriteHeader(http.StatusNotFound) return } if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil { + utils.Debugf("Error: %s", err) httpError(w, err) } } @@ -914,8 +963,26 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) r.Path(localRoute).Methods(localMethod).HandlerFunc(f) } + } } + attachHandler := websocket.Handler(func(ws *websocket.Conn) { + r := ws.Request() + utils.Debugf("Calling %s %s", r.Method, r.RequestURI) + + if logging { + log.Println(r.Method, r.RequestURI) + } + if err := wsContainersAttach(srv, ws, mux.Vars(r)); err != nil { + utils.Debugf("Error: %s", err) + ws.Close() + } + }) + + attachRoute := "/containers/{name:.*}/attach/ws" + r.Path("/v{version:[0-9.]+}" + attachRoute).Methods("GET").Handler(attachHandler) + r.Path(attachRoute).Methods("GET").Handler(attachHandler) + return r, nil } From fc3a8e409d182ade45c697c244562e20beee9f9a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 10 Jul 2013 22:44:31 +0000 Subject: [PATCH 003/242] change tag -> repo name in build usage --- commands.go | 2 +- docs/sources/api/docker_remote_api_v1.0.rst | 2 +- docs/sources/api/docker_remote_api_v1.2.rst | 2 +- docs/sources/api/docker_remote_api_v1.3.rst | 2 +- docs/sources/commandline/command/build.rst | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index feab558259..d12a5755e4 100644 --- a/commands.go +++ b/commands.go @@ -156,7 +156,7 @@ func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) { func (cli *DockerCli) CmdBuild(args ...string) error { cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH") - tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success") + tag := cmd.String("t", "", "Repository name to be applied to the resulting image in case of success") if err := cmd.Parse(args); err != nil { return nil } diff --git a/docs/sources/api/docker_remote_api_v1.0.rst b/docs/sources/api/docker_remote_api_v1.0.rst index a789337093..f2e9a99939 100644 --- a/docs/sources/api/docker_remote_api_v1.0.rst +++ b/docs/sources/api/docker_remote_api_v1.0.rst @@ -827,7 +827,7 @@ Build an image from Dockerfile via stdin {{ STREAM }} - :query t: tag to be applied to the resulting image in case of success + :query t: repository name to be applied to the resulting image in case of success :statuscode 200: no error :statuscode 500: server error diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index a6c2c31920..bec5677425 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -865,7 +865,7 @@ Build an image from Dockerfile via stdin {{ STREAM }} - :query t: tag to be applied to the resulting image in case of success + :query t: repository name to be applied to the resulting image in case of success :query remote: resource to fetch, as URI :statuscode 200: no error :statuscode 500: server error diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index b0955ce496..efd695b82c 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -880,7 +880,7 @@ Build an image from Dockerfile via stdin The Content-type header should be set to "application/tar". - :query t: tag to be applied to the resulting image in case of success + :query t: repository name to be applied to the resulting image in case of success :statuscode 200: no error :statuscode 500: server error diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index 1645002ba2..aeba8cc899 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -10,7 +10,7 @@ Usage: docker build [OPTIONS] PATH | URL | - Build a new container image from the source code at PATH - -t="": Tag to be applied to the resulting image in case of success. + -t="": Repository name to be applied to the resulting image in case of success. When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context From 166eba3e28cc440e06e97ed1bda5b1c3881d46da Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 16:59:07 +0200 Subject: [PATCH 004/242] put the websocket route in the map containing all routes Instead of handling the websocket differently just handle it as a normal route and upgrade it to a websocket. --- api.go | 139 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 72 insertions(+), 67 deletions(-) diff --git a/api.go b/api.go index 45261973a7..ca59bb3cdf 100644 --- a/api.go +++ b/api.go @@ -22,6 +22,9 @@ const APIVERSION = 1.3 const DEFAULTHTTPHOST string = "127.0.0.1" const DEFAULTHTTPPORT int = 4243 +type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error +type WsApiFunc func(srv *Server, ws *websocket.Conn, vars map[string]string) error + func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() if err != nil { @@ -694,9 +697,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return nil } -func wsContainersAttach(srv *Server, ws *websocket.Conn, vars map[string]string) error { - - r := ws.Request() +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 @@ -731,11 +732,14 @@ func wsContainersAttach(srv *Server, ws *websocket.Conn, vars map[string]string) return err } - defer ws.Close() + h := websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() - if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil { - return err - } + 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 } @@ -873,24 +877,68 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } +func logRequest(logging bool, localMethod string, localRoute string, r *http.Request) { + utils.Debugf("Calling %s %s", localMethod, localRoute) + + if logging { + log.Println(r.Method, r.RequestURI) + } +} + +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{ "GET": { - "/auth": getAuth, - "/version": getVersion, - "/info": getInfo, - "/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, + "/auth": getAuth, + "/version": getVersion, + "/info": getInfo, + "/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:.*}/attach/ws": wsContainersAttach, }, "POST": { "/auth": postAuth, @@ -924,64 +972,21 @@ 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", localMethod, localRoute) + localMethod := method - 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 := localFct(srv, version, w, r, mux.Vars(r)); err != nil { - utils.Debugf("Error: %s", err) - 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 { r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) r.Path(localRoute).Methods(localMethod).HandlerFunc(f) } - } } - attachHandler := websocket.Handler(func(ws *websocket.Conn) { - r := ws.Request() - utils.Debugf("Calling %s %s", r.Method, r.RequestURI) - - if logging { - log.Println(r.Method, r.RequestURI) - } - if err := wsContainersAttach(srv, ws, mux.Vars(r)); err != nil { - utils.Debugf("Error: %s", err) - ws.Close() - } - }) - - attachRoute := "/containers/{name:.*}/attach/ws" - r.Path("/v{version:[0-9.]+}" + attachRoute).Methods("GET").Handler(attachHandler) - r.Path(attachRoute).Methods("GET").Handler(attachHandler) return r, nil } From 507cef8bcea5b56d25d34ca7073c46f608962f19 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 17:03:04 +0200 Subject: [PATCH 005/242] useless type --- api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api.go b/api.go index ca59bb3cdf..e27158e7a8 100644 --- a/api.go +++ b/api.go @@ -23,7 +23,6 @@ const DEFAULTHTTPHOST string = "127.0.0.1" const DEFAULTHTTPPORT int = 4243 type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error -type WsApiFunc func(srv *Server, ws *websocket.Conn, vars map[string]string) error func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() From a3b1a9f01a1c08c63e6c37945414481176817ba6 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 17:04:04 +0200 Subject: [PATCH 006/242] useless function. forgot to remove it. --- api.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/api.go b/api.go index e27158e7a8..f86672c6df 100644 --- a/api.go +++ b/api.go @@ -876,14 +876,6 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func logRequest(logging bool, localMethod string, localRoute string, r *http.Request) { - utils.Debugf("Calling %s %s", localMethod, localRoute) - - if logging { - log.Println(r.Method, r.RequestURI) - } -} - 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 From d639f61ec1c80f8d8d65386ec9331dbb6e218cb0 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sat, 13 Jul 2013 19:19:38 +0200 Subject: [PATCH 007/242] reuse the type --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index f86672c6df..6246d06d67 100644 --- a/api.go +++ b/api.go @@ -914,7 +914,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s 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": { "/auth": getAuth, "/version": getVersion, From fb005a3da813ba855b6597c146b66842846df3d8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 16 Jul 2013 14:38:18 +0000 Subject: [PATCH 008/242] add server.ContainerTop, server.poolAdd and ser.poolRemove tests --- server.go | 2 +- server_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index c43cae0c38..48a64aa8c8 100644 --- a/server.go +++ b/server.go @@ -475,7 +475,7 @@ func (srv *Server) poolAdd(kind, key string) error { defer srv.Unlock() if _, exists := srv.pullingPool[key]; exists { - return fmt.Errorf("%s %s is already in progress", key, kind) + return fmt.Errorf("pull %s is already in progress", key) } switch kind { diff --git a/server_test.go b/server_test.go index 05a286aaa8..5181a501c9 100644 --- a/server_test.go +++ b/server_test.go @@ -2,6 +2,7 @@ package docker import ( "testing" + "time" ) func TestContainerTagImageDelete(t *testing.T) { @@ -163,3 +164,90 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) { } } + +func TestContainerTop(t *testing.T) { + runtime := mkRuntime(t) + srv := &Server{runtime: runtime} + defer nuke(runtime) + + c, hostConfig := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t) + defer runtime.Destroy(c) + if err := c.Start(hostConfig); err != nil { + t.Fatal(err) + } + + // Give some time to the process to start + c.WaitTimeout(500 * time.Millisecond) + + if !c.State.Running { + t.Errorf("Container should be running") + } + procs, err := srv.ContainerTop(c.ID) + if err != nil { + t.Fatal(err) + } + + if len(procs) != 2 { + t.Fatalf("Expected 2 processes, found %d.", len(procs)) + } + + if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" { + t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd) + } + + if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" { + t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd) + } +} + +func TestPools(t *testing.T) { + runtime := mkRuntime(t) + srv := &Server{ + runtime: runtime, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + defer nuke(runtime) + + err := srv.poolAdd("pull", "test1") + if err != nil { + t.Fatal(err) + } + err = srv.poolAdd("pull", "test2") + if err != nil { + t.Fatal(err) + } + err = srv.poolAdd("push", "test1") + if err == nil || err.Error() != "pull test1 is already in progress" { + t.Fatalf("Expected `pull test1 is already in progress`") + } + err = srv.poolAdd("pull", "test1") + if err == nil || err.Error() != "pull test1 is already in progress" { + t.Fatalf("Expected `pull test1 is already in progress`") + } + err = srv.poolAdd("wait", "test3") + if err == nil || err.Error() != "Unkown pool type" { + t.Fatalf("Expected `Unkown pool type`") + } + + err = srv.poolRemove("pull", "test2") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("pull", "test2") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("pull", "test1") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("push", "test1") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("wait", "test3") + if err == nil || err.Error() != "Unkown pool type" { + t.Fatalf("Expected `Unkown pool type`") + } +} From 48a892bee5270feea3d0e7b64963acb38dbd634b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 16 Jul 2013 15:58:23 +0000 Subject: [PATCH 009/242] Add CompareConfig test --- utils_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/utils_test.go b/utils_test.go index af2dcc88c5..31489446f8 100644 --- a/utils_test.go +++ b/utils_test.go @@ -129,6 +129,30 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e return } +func TestCompareConfig(t *testing.T) { + config1 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + } + config2 := Config{ + Dns: []string{"0.0.0.0", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + } + config3 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + } + if CompareConfig(&config1, &config2) { + t.Fatalf("CompareConfig should return false, Dns are different") + } + if CompareConfig(&config1, &config3) { + t.Fatalf("CompareConfig should return false, PortSpecs are different") + } +} + func TestMergeConfig(t *testing.T) { volumesImage := make(map[string]struct{}) volumesImage["/test1"] = struct{}{} From 2db99441c85fdf1e7d468bc02b085d9e1b95704c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 20:39:36 +0000 Subject: [PATCH 010/242] prevent any kind of operation simultaneously --- server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server.go b/server.go index 48a64aa8c8..de20ff65c7 100644 --- a/server.go +++ b/server.go @@ -477,6 +477,9 @@ func (srv *Server) poolAdd(kind, key string) error { if _, exists := srv.pullingPool[key]; exists { return fmt.Errorf("pull %s is already in progress", key) } + if _, exists := srv.pushingPool[key]; exists { + return fmt.Errorf("push %s is already in progress", key) + } switch kind { case "pull": From 7c00201222e29989214b7e8a221b057daba0a27d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 20:51:25 +0000 Subject: [PATCH 011/242] add Volumes and VolumesFrom to CompareConfig --- utils.go | 11 +++++++++-- utils_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/utils.go b/utils.go index 33cfbe506f..4f3a846d75 100644 --- a/utils.go +++ b/utils.go @@ -18,14 +18,16 @@ func CompareConfig(a, b *Config) bool { a.MemorySwap != b.MemorySwap || a.CpuShares != b.CpuShares || a.OpenStdin != b.OpenStdin || - a.Tty != b.Tty { + a.Tty != b.Tty || + a.VolumesFrom != b.VolumesFrom { return false } if len(a.Cmd) != len(b.Cmd) || len(a.Dns) != len(b.Dns) || len(a.Env) != len(b.Env) || len(a.PortSpecs) != len(b.PortSpecs) || - len(a.Entrypoint) != len(b.Entrypoint) { + len(a.Entrypoint) != len(b.Entrypoint) || + len(a.Volumes) != len(b.Volumes) { return false } @@ -54,6 +56,11 @@ func CompareConfig(a, b *Config) bool { return false } } + for key := range a.Volumes { + if _, exists := b.Volumes[key]; !exists { + return false + } + } return true } diff --git a/utils_test.go b/utils_test.go index 31489446f8..95f3d4bfdd 100644 --- a/utils_test.go +++ b/utils_test.go @@ -130,20 +130,44 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e } func TestCompareConfig(t *testing.T) { + volumes1 := make(map[string]struct{}) + volumes1["/test1"] = struct{}{} config1 := Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - PortSpecs: []string{"1111:1111", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes1, } config2 := Config{ - Dns: []string{"0.0.0.0", "2.2.2.2"}, - PortSpecs: []string{"1111:1111", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, + Dns: []string{"0.0.0.0", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes1, } config3 := Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - PortSpecs: []string{"0000:0000", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes1, + } + config4 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "22222222", + Volumes: volumes1, + } + volumes2 := make(map[string]struct{}) + volumes2["/test2"] = struct{}{} + config5 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes2, } if CompareConfig(&config1, &config2) { t.Fatalf("CompareConfig should return false, Dns are different") @@ -151,6 +175,15 @@ func TestCompareConfig(t *testing.T) { if CompareConfig(&config1, &config3) { t.Fatalf("CompareConfig should return false, PortSpecs are different") } + if CompareConfig(&config1, &config4) { + t.Fatalf("CompareConfig should return false, VolumesFrom are different") + } + if CompareConfig(&config1, &config5) { + t.Fatalf("CompareConfig should return false, Volumes are different") + } + if !CompareConfig(&config1, &config1) { + t.Fatalf("CompareConfig should return true") + } } func TestMergeConfig(t *testing.T) { From 1a226f0e28e7da4eb3701b31dbe959142c42b752 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 21:06:46 +0000 Subject: [PATCH 012/242] add VolumesFrom to MergeConfig, and test --- utils.go | 3 +++ utils_test.go | 13 +++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/utils.go b/utils.go index 4f3a846d75..85bf61e8e2 100644 --- a/utils.go +++ b/utils.go @@ -132,6 +132,9 @@ func MergeConfig(userConf, imageConf *Config) { if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { userConf.Entrypoint = imageConf.Entrypoint } + if userConf.VolumesFrom == "" { + userConf.VolumesFrom = imageConf.VolumesFrom + } if userConf.Volumes == nil || len(userConf.Volumes) == 0 { userConf.Volumes = imageConf.Volumes } else { diff --git a/utils_test.go b/utils_test.go index 95f3d4bfdd..90292ef9d0 100644 --- a/utils_test.go +++ b/utils_test.go @@ -191,10 +191,11 @@ func TestMergeConfig(t *testing.T) { volumesImage["/test1"] = struct{}{} volumesImage["/test2"] = struct{}{} configImage := &Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - PortSpecs: []string{"1111:1111", "2222:2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - Volumes: volumesImage, + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "1111", + Volumes: volumesImage, } volumesUser := make(map[string]struct{}) @@ -242,4 +243,8 @@ func TestMergeConfig(t *testing.T) { t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v) } } + + if configUser.VolumesFrom != "1111" { + t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom) + } } From 5d8efc107d2c7b7da61a6d22657190c6f13713d2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 14 Jun 2013 16:56:08 -0700 Subject: [PATCH 013/242] + Runtime: inject dockerinit at /.dockerinit instead of overwriting /sbin/init. This makes it possible to run /sbin/init inside a container. --- container.go | 2 +- docker/docker.go | 2 +- graph.go | 33 ++++++++++++++++++++++++++++++++- image.go | 14 ++++++++++++++ lxc_template.go | 2 +- runtime_test.go | 2 +- 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/container.go b/container.go index 3772cf29d2..e8159aae48 100644 --- a/container.go +++ b/container.go @@ -615,7 +615,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { "-n", container.ID, "-f", container.lxcConfigPath(), "--", - "/sbin/init", + "/.dockerinit", } // Networking diff --git a/docker/docker.go b/docker/docker.go index fb7c465369..d609a64a51 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -19,7 +19,7 @@ var ( ) func main() { - if utils.SelfPath() == "/sbin/init" { + if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { // Running in init mode docker.SysInit() return diff --git a/graph.go b/graph.go index 42d1bdbd4c..1e60d6dbe8 100644 --- a/graph.go +++ b/graph.go @@ -193,8 +193,39 @@ func (graph *Graph) Mktemp(id string) (string, error) { return tmp.imageRoot(id), nil } +// getDockerInitLayer returns the path of a layer containing a mountpoint suitable +// for bind-mounting dockerinit into the container. The mountpoint is simply an +// empty file at /.dockerinit +// +// This extra layer is used by all containers as the top-most ro layer. It protects +// the container from unwanted side-effects on the rw layer. +func (graph *Graph) getDockerInitLayer() (string, error) { + tmp, err := graph.tmp() + if err != nil { + return "", err + } + initLayer := tmp.imageRoot("_dockerinit") + if err := os.Mkdir(initLayer, 0700); err != nil && !os.IsExist(err) { + // If directory already existed, keep going. + // For all other errors, abort. + return "", err + } + // FIXME: how the hell do I break down this line in a way + // that is idiomatic and not ugly as hell? + if f, err := os.OpenFile(path.Join(initLayer, ".dockerinit"), os.O_CREATE|os.O_TRUNC, 0700); err != nil && !os.IsExist(err) { + // If file already existed, keep going. + // For all other errors, abort. + return "", err + } else { + f.Close() + } + // Layer is ready to use, if it wasn't before. + return initLayer, nil +} + func (graph *Graph) tmp() (*Graph, error) { - return NewGraph(path.Join(graph.Root, ":tmp:")) + // Changed to _tmp from :tmp:, because it messed with ":" separators in aufs branch syntax... + return NewGraph(path.Join(graph.Root, "_tmp")) } // Check if given error is "not empty". diff --git a/image.go b/image.go index e1b1ac0418..af7b5030be 100644 --- a/image.go +++ b/image.go @@ -263,6 +263,13 @@ func (img *Image) layers() ([]string, error) { if len(list) == 0 { return nil, fmt.Errorf("No layer found for image %s\n", img.ID) } + + // Inject the dockerinit layer (empty place-holder for mount-binding dockerinit) + if dockerinitLayer, err := img.getDockerInitLayer(); err != nil { + return nil, err + } else { + list = append([]string{dockerinitLayer}, list...) + } return list, nil } @@ -292,6 +299,13 @@ func (img *Image) GetParent() (*Image, error) { return img.graph.Get(img.Parent) } +func (img *Image) getDockerInitLayer() (string, error) { + if img.graph == nil { + return "", fmt.Errorf("Can't lookup dockerinit layer of unregistered image") + } + return img.graph.getDockerInitLayer() +} + func (img *Image) root() (string, error) { if img.graph == nil { return "", fmt.Errorf("Can't lookup root of unregistered image") diff --git a/lxc_template.go b/lxc_template.go index 93b795e901..e00b2f88c5 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -79,7 +79,7 @@ lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,no #lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 # Inject docker-init -lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0 +lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/.dockerinit none bind,ro 0 0 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0 diff --git a/runtime_test.go b/runtime_test.go index 9d43bd46e5..7c30322fc5 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -69,7 +69,7 @@ func layerArchive(tarfile string) (io.Reader, error) { func init() { // Hack to run sys init during unit testing - if utils.SelfPath() == "/sbin/init" { + if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { SysInit() return } From 0afed3eded0950b5899729714fb11d6a7322301e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 20:56:41 +0000 Subject: [PATCH 014/242] handle -dev --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 5f3913f9c2..7b70faa198 100644 --- a/commands.go +++ b/commands.go @@ -436,7 +436,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { release := utils.GetReleaseVersion() if release != "" { fmt.Fprintf(cli.out, "Last stable version: %s", release) - if VERSION != release || out.Version != release { + if strings.Trim(VERSION, "-dev") != release || strings.Trim(out.Version, "-dev") != release { fmt.Fprintf(cli.out, ", please update docker") } fmt.Fprintf(cli.out, "\n") From df86cb9a5c949530336b43100b303876f07c69ba Mon Sep 17 00:00:00 2001 From: unclejack Date: Sat, 20 Jul 2013 13:47:13 +0300 Subject: [PATCH 015/242] make docker run handle SIGINT/SIGTERM --- commands.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/commands.go b/commands.go index f0e1695b3f..db8a126696 100644 --- a/commands.go +++ b/commands.go @@ -1393,6 +1393,21 @@ func (cli *DockerCli) CmdRun(args ...string) error { v.Set("stderr", "1") } + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + go func() { + for { + sig := <-signals + if sig == syscall.SIGINT || sig == syscall.SIGTERM { + fmt.Printf("\nReceived signal: %s; cleaning up\n", sig) + if err := cli.CmdStop("-t", "4", runResult.ID); err != nil { + fmt.Printf("failed to stop container:", err) + } + return + } + } + }() + if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil { utils.Debugf("Error hijack: %s", err) return err From 945033f1ccbc4896e1a784d00f1f2b465ed9e04b Mon Sep 17 00:00:00 2001 From: Sridatta Thatipamala Date: Mon, 22 Jul 2013 14:55:07 -0700 Subject: [PATCH 016/242] change permissions of initLayer to be readable by non-root users --- graph.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph.go b/graph.go index 1e60d6dbe8..b75ddb7ec0 100644 --- a/graph.go +++ b/graph.go @@ -205,7 +205,7 @@ func (graph *Graph) getDockerInitLayer() (string, error) { return "", err } initLayer := tmp.imageRoot("_dockerinit") - if err := os.Mkdir(initLayer, 0700); err != nil && !os.IsExist(err) { + if err := os.Mkdir(initLayer, 0755); err != nil && !os.IsExist(err) { // If directory already existed, keep going. // For all other errors, abort. return "", err From 0e71e368a8a781f593b25fdd1318d3882e6d28e5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 15:41:34 +0000 Subject: [PATCH 017/242] Add ID to JSONMessage in pull Use goroutines to pull in parallel If multiple images pulled at the same time, each progress is displayed on a new line --- commands.go | 2 +- graph.go | 2 +- server.go | 63 ++++++++++++++++++++++++++++++-------------------- utils/utils.go | 13 +++++++---- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/commands.go b/commands.go index 2d8ea4efb5..946c83dd11 100644 --- a/commands.go +++ b/commands.go @@ -196,7 +196,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // FIXME: ProgressReader shouldn't be this annoyning to use if context != nil { sf := utils.NewStreamFormatter(false) - body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf) + body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s", ""), sf) } // Upload the build context v := &url.Values{} diff --git a/graph.go b/graph.go index 42d1bdbd4c..3ae342e7c5 100644 --- a/graph.go +++ b/graph.go @@ -175,7 +175,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *uti if err != nil { return nil, err } - return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root) + return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)", ""), sf), tmp.Root) } // Mktemp creates a temporary sub-directory inside the graph's filesystem. diff --git a/server.go b/server.go index 4179a1e160..1a221d3f00 100644 --- a/server.go +++ b/server.go @@ -145,7 +145,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } - if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), path); err != nil { + if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%8v/%v (%v)", ""), sf), path); err != nil { return "", err } // FIXME: Handle custom repo, tag comment, author @@ -425,7 +425,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return err } defer layer.Close() - if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { + if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)", id), sf), false, img); err != nil { return err } } @@ -477,30 +477,43 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName repoData.ImgList[id].Tag = askedTag } - for _, img := range repoData.ImgList { - if askedTag != "" && img.Tag != askedTag { - utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID) - continue - } - - if img.Tag == "" { - utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) - continue - } - out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, localName)) - success := false - for _, ep := range repoData.Endpoints { - if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { - out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)) - continue + errors := make(chan error) + for _, image := range repoData.ImgList { + go func(img *registry.ImgData) { + if askedTag != "" && img.Tag != askedTag { + utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID) + errors <- nil + return } - success = true - break - } - if !success { - return fmt.Errorf("Could not find repository on any of the indexed registries.") + + if img.Tag == "" { + utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) + errors <- nil + return + } + out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, localName)) + success := false + for _, ep := range repoData.Endpoints { + if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { + out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)) + continue + } + success = true + break + } + if !success { + errors <- fmt.Errorf("Could not find repository on any of the indexed registries.") + } + errors <- nil + }(image) + } + + for i := 0; i < len(repoData.ImgList); i++ { + if err := <-errors; err != nil { + return err } } + for tag, id := range tagsList { if askedTag != "" && tag != askedTag { continue @@ -748,7 +761,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // Send the layer - if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { + if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)", ""), sf), ep, token); err != nil { return err } return nil @@ -818,7 +831,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write if err != nil { return err } - archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%8v/%v (%v)"), sf) + archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%8v/%v (%v)", ""), sf) } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { diff --git a/utils/utils.go b/utils/utils.go index acb015becd..ffba2352a7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -107,7 +107,7 @@ func (r *progressReader) Close() error { func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader { tpl := string(template) if tpl == "" { - tpl = string(sf.FormatProgress("", "%8v/%v (%v)")) + tpl = string(sf.FormatProgress("", "%8v/%v (%v)", "")) } return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf} } @@ -587,11 +587,14 @@ type NopFlusher struct{} func (f *NopFlusher) Flush() {} type WriteFlusher struct { + sync.Mutex w io.Writer flusher http.Flusher } func (wf *WriteFlusher) Write(b []byte) (n int, err error) { + wf.Lock() + defer wf.Unlock() n, err = wf.w.Write(b) wf.flusher.Flush() return n, err @@ -619,7 +622,9 @@ func (jm *JSONMessage) Display(out io.Writer) (error) { if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } - if jm.Progress != "" { + if jm.Progress != "" && jm.ID != ""{ + fmt.Fprintf(out, "\n%s %s %s\r", jm.Status, jm.ID, jm.Progress) + } else if jm.Progress != "" { fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) } else if jm.Error != "" { return fmt.Errorf(jm.Error) @@ -665,10 +670,10 @@ func (sf *StreamFormatter) FormatError(err error) []byte { return []byte("Error: " + err.Error() + "\r\n") } -func (sf *StreamFormatter) FormatProgress(action, str string) []byte { +func (sf *StreamFormatter) FormatProgress(action, str, id string) []byte { sf.used = true if sf.json { - b, err := json.Marshal(&JSONMessage{Status: action, Progress: str}) + b, err := json.Marshal(&JSONMessage{Status: action, Progress: str, ID:id}) if err != nil { return nil } From 8742649aa7f3524bbfa99b68c8d87ffc5aba0af9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 17:10:59 +0000 Subject: [PATCH 018/242] improve client output --- commands.go | 7 +++++-- graph.go | 2 +- server.go | 44 ++++++++++++++++++++++---------------------- utils/utils.go | 30 ++++++++++++------------------ 4 files changed, 40 insertions(+), 43 deletions(-) diff --git a/commands.go b/commands.go index 946c83dd11..2a672d49e3 100644 --- a/commands.go +++ b/commands.go @@ -196,7 +196,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // FIXME: ProgressReader shouldn't be this annoyning to use if context != nil { sf := utils.NewStreamFormatter(false) - body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s", ""), sf) + body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("", "Uploading context", "%v bytes%0.0s%0.0s"), sf) } // Upload the build context v := &url.Values{} @@ -1537,8 +1537,8 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if resp.Header.Get("Content-Type") == "application/json" { dec := json.NewDecoder(resp.Body) + jm := utils.JSONMessage{} for { - var jm utils.JSONMessage if err := dec.Decode(&jm); err == io.EOF { break } else if err != nil { @@ -1546,6 +1546,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e } jm.Display(out) } + if jm.Progress != "" { + fmt.Fprintf(out, "\n") + } } else { if _, err := io.Copy(out, resp.Body); err != nil { return err diff --git a/graph.go b/graph.go index 3ae342e7c5..5b9162b871 100644 --- a/graph.go +++ b/graph.go @@ -175,7 +175,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *uti if err != nil { return nil, err } - return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)", ""), sf), tmp.Root) + return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf), tmp.Root) } // Mktemp creates a temporary sub-directory inside the graph's filesystem. diff --git a/server.go b/server.go index 1a221d3f00..a1d22d3c44 100644 --- a/server.go +++ b/server.go @@ -145,7 +145,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } - if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%8v/%v (%v)", ""), sf), path); err != nil { + if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf), path); err != nil { return "", err } // FIXME: Handle custom repo, tag comment, author @@ -153,7 +153,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. if err != nil { return "", err } - out.Write(sf.FormatStatus(img.ID)) + out.Write(sf.FormatStatus("", img.ID)) return img.ShortID(), nil } @@ -407,7 +407,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin // FIXME: Launch the getRemoteImage() in goroutines for _, id := range history { if !srv.runtime.graph.Exists(id) { - out.Write(sf.FormatStatus("Pulling %s metadata", id)) + out.Write(sf.FormatStatus(utils.TruncateID(id), "Pulling metadata")) imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token) if err != nil { // FIXME: Keep goging in case of error? @@ -419,13 +419,13 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin } // Get the layer - out.Write(sf.FormatStatus("Pulling %s fs layer", id)) + out.Write(sf.FormatStatus(utils.TruncateID(id), "Pulling fs layer")) layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token) if err != nil { return err } defer layer.Close() - if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)", id), sf), false, img); err != nil { + if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { return err } } @@ -434,7 +434,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin } func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter) error { - out.Write(sf.FormatStatus("Pulling repository %s", localName)) + out.Write(sf.FormatStatus("", "Pulling repository %s", localName)) repoData, err := r.GetRepositoryData(indexEp, remoteName) if err != nil { @@ -491,11 +491,11 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName errors <- nil return } - out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, localName)) + out.Write(sf.FormatStatus(utils.TruncateID(img.ID), "Pulling image (%s) from %s", img.Tag, localName)) success := false for _, ep := range repoData.Endpoints { if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { - out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)) + out.Write(sf.FormatStatus(utils.TruncateID(img.ID), "Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)) continue } success = true @@ -665,12 +665,12 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) - out.Write(sf.FormatStatus("Processing checksums")) + out.Write(sf.FormatStatus("", "Processing checksums")) imgList, err := srv.getImageList(localRepo) if err != nil { return err } - out.Write(sf.FormatStatus("Sending image list")) + out.Write(sf.FormatStatus("", "Sending image list")) var repoData *registry.RepositoryData repoData, err = r.PushImageJSONIndex(indexEp, remoteName, imgList, false, nil) @@ -679,21 +679,21 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName } for _, ep := range repoData.Endpoints { - out.Write(sf.FormatStatus("Pushing repository %s (%d tags)", localName, len(localRepo))) + out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, len(localRepo))) // For each image within the repo, push them for _, elem := range imgList { if _, exists := repoData.ImgList[elem.ID]; exists { - out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) continue } else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) { - out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) continue } if err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { // FIXME: Continue on error? return err } - out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) + out.Write(sf.FormatStatus("", "Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { return err } @@ -713,7 +713,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, if err != nil { return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err) } - out.Write(sf.FormatStatus("Pushing %s", imgID)) + out.Write(sf.FormatStatus("", "Pushing %s", imgID)) // Make sure we have the image's checksum checksum, err := srv.getChecksum(imgID) @@ -728,7 +728,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, // Send the json if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { if err == registry.ErrAlreadyExists { - out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID)) + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", imgData.ID)) return nil } return err @@ -761,7 +761,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // Send the layer - if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)", ""), sf), ep, token); err != nil { + if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { return err } return nil @@ -789,7 +789,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo if err != nil { reposLen := len(srv.runtime.repositories.Repositories[localName]) - out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", localName, reposLen)) + out.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { if err := srv.pushRepository(r, out, localName, remoteName, localRepo, endpoint, sf); err != nil { @@ -801,7 +801,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo } var token []string - out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName)) + out.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName)) if err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { return err } @@ -824,14 +824,14 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write u.Host = src u.Path = "" } - out.Write(sf.FormatStatus("Downloading from %s", u)) + out.Write(sf.FormatStatus("", "Downloading from %s", u)) // Download with curl (pretty progress bar) // If curl is not available, fallback to http.Get() resp, err = utils.Download(u.String(), out) if err != nil { return err } - archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%8v/%v (%v)", ""), sf) + archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("", "Importing", "%8v/%v (%v)"), sf) } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { @@ -843,7 +843,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write return err } } - out.Write(sf.FormatStatus(img.ShortID())) + out.Write(sf.FormatStatus("", img.ShortID())) return nil } diff --git a/utils/utils.go b/utils/utils.go index ffba2352a7..6361945eef 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -94,11 +94,6 @@ func (r *progressReader) Read(p []byte) (n int, err error) { } r.lastUpdate = r.readProgress } - // Send newline when complete - if err != nil { - r.output.Write(r.sf.FormatStatus("")) - } - return read, err } func (r *progressReader) Close() error { @@ -619,24 +614,23 @@ type JSONMessage struct { } func (jm *JSONMessage) Display(out io.Writer) (error) { + if jm.Error != "" { + return fmt.Errorf(jm.Error) + } if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } - if jm.Progress != "" && jm.ID != ""{ - fmt.Fprintf(out, "\n%s %s %s\r", jm.Status, jm.ID, jm.Progress) - } else if jm.Progress != "" { + if jm.ID != "" { + fmt.Fprintf(out, "%s: ", jm.ID) + } + if jm.Progress != "" { fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) - } else if jm.Error != "" { - return fmt.Errorf(jm.Error) - } else if jm.ID != "" { - fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status) } else { fmt.Fprintf(out, "%s\n", jm.Status) } return nil } - type StreamFormatter struct { json bool used bool @@ -646,11 +640,11 @@ func NewStreamFormatter(json bool) *StreamFormatter { return &StreamFormatter{json, false} } -func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte { +func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []byte { sf.used = true str := fmt.Sprintf(format, a...) if sf.json { - b, err := json.Marshal(&JSONMessage{Status: str}) + b, err := json.Marshal(&JSONMessage{ID: id, Status: str}) if err != nil { return sf.FormatError(err) } @@ -670,16 +664,16 @@ func (sf *StreamFormatter) FormatError(err error) []byte { return []byte("Error: " + err.Error() + "\r\n") } -func (sf *StreamFormatter) FormatProgress(action, str, id string) []byte { +func (sf *StreamFormatter) FormatProgress(id, action, progress string) []byte { sf.used = true if sf.json { - b, err := json.Marshal(&JSONMessage{Status: action, Progress: str, ID:id}) + b, err := json.Marshal(&JSONMessage{Status: action, Progress: progress, ID:id}) if err != nil { return nil } return b } - return []byte(action + " " + str + "\r") + return []byte(action + " " + progress + "\r") } func (sf *StreamFormatter) Used() bool { From b06f62713986681c7be5e9e0a59c3f717fe837cd Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 24 Jun 2013 16:57:22 -0700 Subject: [PATCH 019/242] Library: hipache and ubuntu base --- library/hipache | 1 + library/ubuntu | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 library/hipache create mode 100644 library/ubuntu diff --git a/library/hipache b/library/hipache new file mode 100644 index 0000000000..dcbd143dea --- /dev/null +++ b/library/hipache @@ -0,0 +1 @@ +git://github.com/dotcloud/hipache diff --git a/library/ubuntu b/library/ubuntu new file mode 100644 index 0000000000..4568b67784 --- /dev/null +++ b/library/ubuntu @@ -0,0 +1,2 @@ +git://github.com/dotcloud/ubuntu-quantal +git://github.com/dotcloud/ubuntu-precise From a45490243b29f67f09854fdfe355a144d8c3346e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 10 Jul 2013 13:40:23 -0700 Subject: [PATCH 020/242] Adding Joffrey as library maintainer --- library/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 library/MAINTAINERS diff --git a/library/MAINTAINERS b/library/MAINTAINERS new file mode 100644 index 0000000000..b1c52939ef --- /dev/null +++ b/library/MAINTAINERS @@ -0,0 +1 @@ +Joffrey Fuhrer From 844a9ab85e793517743beecdda651fa42d42a518 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 10 Jul 2013 13:44:07 -0700 Subject: [PATCH 021/242] Define tags in the library files instead of upstream --- library/hipache | 3 ++- library/ubuntu | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/library/hipache b/library/hipache index dcbd143dea..1a153c5e9e 100644 --- a/library/hipache +++ b/library/hipache @@ -1 +1,2 @@ -git://github.com/dotcloud/hipache +latest git://github.com/dotcloud/hipache@7362ff5b812f +0.2.4 git://github.com/dotcloud/hipache@7362ff5b812f diff --git a/library/ubuntu b/library/ubuntu index 4568b67784..046c8db319 100644 --- a/library/ubuntu +++ b/library/ubuntu @@ -1,2 +1,5 @@ -git://github.com/dotcloud/ubuntu-quantal -git://github.com/dotcloud/ubuntu-precise +latest git://github.com/dotcloud/ubuntu-quantal +quantal git://github.com/dotcloud/ubuntu-quantal +12.10 git://github.com/dotcloud/ubuntu-quantal +precise git://github.com/dotcloud/ubuntu-precise +12.04 git://github.com/dotcloud/ubuntu-precise From c19fa83a8aa592d9716a883ac28ce1a20ce2bffb Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Fri, 12 Jul 2013 18:28:06 +0200 Subject: [PATCH 022/242] Use extended syntax (indicate what type of object needs to be checked out) --- library/hipache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/hipache b/library/hipache index 1a153c5e9e..1c72b3772f 100644 --- a/library/hipache +++ b/library/hipache @@ -1,2 +1,2 @@ -latest git://github.com/dotcloud/hipache@7362ff5b812f -0.2.4 git://github.com/dotcloud/hipache@7362ff5b812f +latest git://github.com/dotcloud/hipache C:7362ff5b812f +0.2.4 git://github.com/dotcloud/hipache C:7362ff5b812f From 0ac672fea6fc8e670be9dce18bd63d50689230ef Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 17 Jul 2013 17:25:20 +0200 Subject: [PATCH 023/242] Use full hash references in library/ Brew doesn't currently support abridged hashes --- library/hipache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/hipache b/library/hipache index 1c72b3772f..ded0582c63 100644 --- a/library/hipache +++ b/library/hipache @@ -1,2 +1,2 @@ -latest git://github.com/dotcloud/hipache C:7362ff5b812f -0.2.4 git://github.com/dotcloud/hipache C:7362ff5b812f +latest git://github.com/dotcloud/hipache C:7362ff5b812f93eceafbdbf5e5959f676f731f80 +0.2.4 git://github.com/dotcloud/hipache C:7362ff5b812f93eceafbdbf5e5959f676f731f80 From d47df21a332db1c1d8efedfeb0e36934b09e7a88 Mon Sep 17 00:00:00 2001 From: shin- Date: Thu, 18 Jul 2013 19:22:07 +0200 Subject: [PATCH 024/242] Add brew script to the contrib folder --- contrib/brew/.gitignore | 1 + contrib/brew/README.md | 78 +++++++++++++++++++++++++++++++ contrib/brew/brew/__init__.py | 1 + contrib/brew/brew/brew.py | 87 +++++++++++++++++++++++++++++++++++ contrib/brew/brew/git.py | 48 +++++++++++++++++++ contrib/brew/docker-brew | 25 ++++++++++ contrib/brew/requirements.txt | 2 + contrib/brew/setup.py | 22 +++++++++ 8 files changed, 264 insertions(+) create mode 100644 contrib/brew/.gitignore create mode 100644 contrib/brew/README.md create mode 100644 contrib/brew/brew/__init__.py create mode 100644 contrib/brew/brew/brew.py create mode 100644 contrib/brew/brew/git.py create mode 100755 contrib/brew/docker-brew create mode 100644 contrib/brew/requirements.txt create mode 100644 contrib/brew/setup.py diff --git a/contrib/brew/.gitignore b/contrib/brew/.gitignore new file mode 100644 index 0000000000..0d20b6487c --- /dev/null +++ b/contrib/brew/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/contrib/brew/README.md b/contrib/brew/README.md new file mode 100644 index 0000000000..d50e66f2a8 --- /dev/null +++ b/contrib/brew/README.md @@ -0,0 +1,78 @@ +# docker-brew + +docker-brew is a command-line tool used to build the docker standard library. + +## Install instructions + +1. Install python if it isn't already available on your OS of choice +1. Install the easy_install tool (`sudo apt-get install python-setuptools` +for Debian) +1. Install the python package manager, `pip` (`easy_install pip`) +1. Run the following command: `pip install -r requirements.txt` +1. You should now be able to use the `docker-brew` script as such. + +## Basics + + ./docker-brew -h + +Display usage and help. + + ./docker-brew + +Default build from the default repo/branch. Images will be created under the +`library/` namespace. Does not perform a remote push. + + ./docker-brew -n mycorp.com -b stable --push git://github.com/mycorp/docker + +Will fetch the library definition files in the `stable` branch of the +`git://github.com/mycorp/docker` repository and create images under the +`mycorp.com` namespace (e.g. `mycorp.com/ubuntu`). Created images will then +be pushed to the official docker repository (pending: support for private +repositories) + +## Library definition files + +The library definition files are plain text files found in the `library/` +subfolder of the docker repository. + +### File names + +The name of a definition file will determine the name of the image(s) it +creates. For example, the `library/ubuntu` file will create images in the +`/ubuntu` repository. If multiple instructions are present in +a single file, all images are expected to be created under a different tag. + +### Instruction format + +Each line represents a build instruction. +There are different formats that `docker-brew` is able to parse. + + + git://github.com/dotcloud/hipache + https://github.com/dotcloud/docker.git + +The simplest format. `docker-brew` will fetch data from the provided git +repository from the `HEAD`of its `master` branch. Generated image will be +tagged as `latest`. Use of this format is discouraged because there is no +way to ensure stability. + + + bleeding-edge git://github.com/dotcloud/docker + unstable https://github.com/dotcloud/docker-redis.git + +A more advanced format. `docker-brew` will fetch data from the provided git +repository from the `HEAD`of its `master` branch. Generated image will be +tagged as ``. Recommended if we always want to provide a snapshot +of the latest development. Again, no way to ensure stability. + + T: + 2.4.0 git://github.com/dotcloud/docker-redis T:2.4.0 + B: + zfs git://github.com/dotcloud/docker B:zfs-support + C: + 2.2.0 https://github.com/dotcloud/docker-redis.git C:a4bf8923ee4ec566d3ddc212 + +The most complete format. `docker-brew` will fetch data from the provided git +repository from the provided reference (if it's a branch, brew will fetch its +`HEAD`). Generated image will be tagged as ``. Recommended whenever +possible. \ No newline at end of file diff --git a/contrib/brew/brew/__init__.py b/contrib/brew/brew/__init__.py new file mode 100644 index 0000000000..f693ad67a4 --- /dev/null +++ b/contrib/brew/brew/__init__.py @@ -0,0 +1 @@ +from brew import build_library diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py new file mode 100644 index 0000000000..69529201d6 --- /dev/null +++ b/contrib/brew/brew/brew.py @@ -0,0 +1,87 @@ +import os +import logging + +import docker + +import git + +DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker' +DEFAULT_BRANCH = 'library' + +logger = logging.getLogger(__name__) +logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level='DEBUG') +client = docker.Client() +processed = {} + + +def build_library(repository=None, branch=None, namespace=None, push=False): + if repository is None: + repository = DEFAULT_REPOSITORY + if branch is None: + branch = DEFAULT_BRANCH + + logger.info('Cloning docker repo from {0}, branch: {1}'.format( + repository, branch)) + #FIXME: set destination folder and only pull latest changes instead of + # cloning the whole repo everytime + dst_folder = git.clone_branch(repository, branch) + for buildfile in os.listdir(os.path.join(dst_folder, 'library')): + if buildfile == 'MAINTAINERS': + continue + f = open(os.path.join(dst_folder, 'library', buildfile)) + for line in f: + logger.debug('{0} ---> {1}'.format(buildfile, line)) + args = line.split() + try: + if len(args) > 3: + raise RuntimeError('Incorrect line format, ' + 'please refer to the docs') + + url = None + ref = 'refs/heads/master' + tag = None + if len(args) == 1: # Just a URL, simple mode + url = args[0] + elif len(args) == 2 or len(args) == 3: # docker-tag url + url = args[1] + tag = args[0] + + if len(args) == 3: # docker-tag url B:branch or T:tag + ref = None + if args[2].startswith('B:'): + ref = 'refs/heads/' + args[2][2:] + elif args[2].startswith('T:'): + ref = 'refs/tags/' + args[2][2:] + elif args[2].startswith('C:'): + ref = args[2][2:] + else: + raise RuntimeError('Incorrect line format, ' + 'please refer to the docs') + img = build_repo(url, ref, buildfile, tag, namespace, push) + processed['{0}@{1}'.format(url, ref)] = img + except Exception as e: + logger.exception(e) + f.close() + + +def build_repo(repository, ref, docker_repo, docker_tag, namespace, push): + docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo) + img_id = None + if '{0}@{1}'.format(repository, ref) not in processed.keys(): + logger.info('Cloning {0} (ref: {1})'.format(repository, ref)) + dst_folder = git.clone(repository, ref) + if not 'Dockerfile' in os.listdir(dst_folder): + raise RuntimeError('Dockerfile not found in cloned repository') + logger.info('Building using dockerfile...') + img_id, logs = client.build(path=dst_folder) + + if not img_id: + img_id = processed['{0}@{1}'.format(repository, ref)] + logger.info('Committing to {0}:{1}'.format(docker_repo, + docker_tag or 'latest')) + client.tag(img_id, docker_repo, docker_tag) + if push: + logger.info('Pushing result to the main registry') + client.push(docker_repo) + return img_id diff --git a/contrib/brew/brew/git.py b/contrib/brew/brew/git.py new file mode 100644 index 0000000000..40cae8753b --- /dev/null +++ b/contrib/brew/brew/git.py @@ -0,0 +1,48 @@ +import tempfile +import logging + +from dulwich import index +from dulwich.client import get_transport_and_path +from dulwich.repo import Repo + +logger = logging.getLogger(__name__) + + +def clone_branch(repo_url, branch="master", folder=None): + return clone(repo_url, 'refs/heads/' + branch, folder) + + +def clone_tag(repo_url, tag, folder=None): + return clone(repo_url, 'refs/tags/' + tag, folder) + + +def clone(repo_url, ref=None, folder=None): + is_commit = False + if ref is None: + ref = 'refs/heads/master' + elif not ref.startswith('refs/'): + is_commit = True + logger.debug("clone repo_url={0}, ref={1}".format(repo_url, ref)) + if folder is None: + folder = tempfile.mkdtemp() + logger.debug("folder = {0}".format(folder)) + rep = Repo.init(folder) + client, relative_path = get_transport_and_path(repo_url) + logger.debug("client={0}".format(client)) + + remote_refs = client.fetch(relative_path, rep) + for k, v in remote_refs.iteritems(): + try: + rep.refs.add_if_new(k, v) + except: + pass + + if is_commit: + rep['HEAD'] = rep.commit(ref) + else: + rep['HEAD'] = remote_refs[ref] + indexfile = rep.index_path() + tree = rep["HEAD"].tree + index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree) + logger.debug("done") + return folder diff --git a/contrib/brew/docker-brew b/contrib/brew/docker-brew new file mode 100755 index 0000000000..d1843d412b --- /dev/null +++ b/contrib/brew/docker-brew @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import argparse + +import brew + + +DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker' +DEFAULT_BRANCH = 'library' + +if __name__ == '__main__': + parser = argparse.ArgumentParser('Build the docker standard library') + parser.add_argument('--push', action='store_true', default=False, + help='push generated repositories to the official registry') + parser.add_argument('-n', metavar='NAMESPACE', default='library', + help='namespace used for generated repositories.' + ' Default is library') + parser.add_argument('-b', metavar='BRANCH', default=DEFAULT_BRANCH, + help='branch in the repository where the library definition' + ' files will be fetched. Default is ' + DEFAULT_BRANCH) + parser.add_argument('repository', default=DEFAULT_REPOSITORY, nargs='?', + help='git repository containing the library definition files.' + ' Default is ' + DEFAULT_REPOSITORY) + args = parser.parse_args() + brew.build_library(args.repository, args.b, args.n, args.push) \ No newline at end of file diff --git a/contrib/brew/requirements.txt b/contrib/brew/requirements.txt new file mode 100644 index 0000000000..5278498791 --- /dev/null +++ b/contrib/brew/requirements.txt @@ -0,0 +1,2 @@ +dulwich==0.9.0 +docker==0.1.0 \ No newline at end of file diff --git a/contrib/brew/setup.py b/contrib/brew/setup.py new file mode 100644 index 0000000000..8a099c99ff --- /dev/null +++ b/contrib/brew/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +from setuptools import setup + +ROOT_DIR = os.path.dirname(__file__) +SOURCE_DIR = os.path.join(ROOT_DIR) + +test_requirements = [] +setup( + name="docker-brew", + version='0.0.1', + description="-", + packages=['dockerbrew'], + install_requires=['dulwich', 'docker'] + test_requirements, + zip_safe=False, + classifiers=['Development Status :: 3 - Alpha', + 'Environment :: Other Environment', + 'Intended Audience :: Developers', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Utilities'], + ) From 3781a2cc4ba80373d1820c22555e82c42d92a787 Mon Sep 17 00:00:00 2001 From: shin- Date: Fri, 19 Jul 2013 19:52:23 +0200 Subject: [PATCH 025/242] Added definition file for busybox and updated ubuntu --- library/busybox | 1 + library/ubuntu | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 library/busybox diff --git a/library/busybox b/library/busybox new file mode 100644 index 0000000000..b091054451 --- /dev/null +++ b/library/busybox @@ -0,0 +1 @@ +latest git://github.com/dotcloud/docker-busybox \ No newline at end of file diff --git a/library/ubuntu b/library/ubuntu index 046c8db319..7a4c77d99f 100644 --- a/library/ubuntu +++ b/library/ubuntu @@ -1,5 +1,7 @@ latest git://github.com/dotcloud/ubuntu-quantal quantal git://github.com/dotcloud/ubuntu-quantal 12.10 git://github.com/dotcloud/ubuntu-quantal -precise git://github.com/dotcloud/ubuntu-precise -12.04 git://github.com/dotcloud/ubuntu-precise +precise git://github.com/dotcloud/ubuntu-quantal B:precise +12.04 git://github.com/dotcloud/ubuntu-quantal B:precise +raring git://github.com/dotcloud/ubuntu-quantal B:raring +13.04 git://github.com/dotcloud/ubuntu-quantal B:raring From 7813f2a25e40018f9ad0a780df1c2e1d897666f5 Mon Sep 17 00:00:00 2001 From: shin- Date: Fri, 19 Jul 2013 20:05:17 +0200 Subject: [PATCH 026/242] Updated git repos for precise and raring --- library/ubuntu | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/ubuntu b/library/ubuntu index 7a4c77d99f..8a5cdfd445 100644 --- a/library/ubuntu +++ b/library/ubuntu @@ -1,7 +1,7 @@ latest git://github.com/dotcloud/ubuntu-quantal quantal git://github.com/dotcloud/ubuntu-quantal 12.10 git://github.com/dotcloud/ubuntu-quantal -precise git://github.com/dotcloud/ubuntu-quantal B:precise -12.04 git://github.com/dotcloud/ubuntu-quantal B:precise -raring git://github.com/dotcloud/ubuntu-quantal B:raring -13.04 git://github.com/dotcloud/ubuntu-quantal B:raring +precise git://github.com/shin-/ubuntu B:precise +12.04 git://github.com/shin-/ubuntu B:precise +raring git://github.com/shin-/ubuntu B:raring +13.04 git://github.com/shin-/ubuntu B:raring From 362f1735e6b60b6fcc6d435b29586252029f15a8 Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 24 Jul 2013 16:47:50 +0200 Subject: [PATCH 027/242] Brew: Avoid duplicate commands, added --debug option, added local repo support --- contrib/brew/brew/__init__.py | 2 +- contrib/brew/brew/brew.py | 18 +++++++++++++----- contrib/brew/docker-brew | 17 ++++++++--------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/contrib/brew/brew/__init__.py b/contrib/brew/brew/__init__.py index f693ad67a4..a9476ff5a4 100644 --- a/contrib/brew/brew/__init__.py +++ b/contrib/brew/brew/__init__.py @@ -1 +1 @@ -from brew import build_library +from brew import build_library, DEFAULT_REPOSITORY, DEFAULT_BRANCH diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index 69529201d6..ab0b886b2c 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -10,22 +10,30 @@ DEFAULT_BRANCH = 'library' logger = logging.getLogger(__name__) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', - level='DEBUG') + level='INFO') client = docker.Client() processed = {} -def build_library(repository=None, branch=None, namespace=None, push=False): +def build_library(repository=None, branch=None, namespace=None, push=False, + debug=False): if repository is None: repository = DEFAULT_REPOSITORY if branch is None: branch = DEFAULT_BRANCH + if debug: + logger.setLevel('DEBUG') + + if not (repository.startswith('https://') or repository.startswith('git://')): + logger.info('Repository provided assumed to be a local path') + dst_folder = repository - logger.info('Cloning docker repo from {0}, branch: {1}'.format( - repository, branch)) #FIXME: set destination folder and only pull latest changes instead of # cloning the whole repo everytime - dst_folder = git.clone_branch(repository, branch) + if not dst_folder: + logger.info('Cloning docker repo from {0}, branch: {1}'.format( + repository, branch)) + dst_folder = git.clone_branch(repository, branch) for buildfile in os.listdir(os.path.join(dst_folder, 'library')): if buildfile == 'MAINTAINERS': continue diff --git a/contrib/brew/docker-brew b/contrib/brew/docker-brew index d1843d412b..3bcd99a3ad 100755 --- a/contrib/brew/docker-brew +++ b/contrib/brew/docker-brew @@ -5,21 +5,20 @@ import argparse import brew -DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker' -DEFAULT_BRANCH = 'library' - if __name__ == '__main__': parser = argparse.ArgumentParser('Build the docker standard library') parser.add_argument('--push', action='store_true', default=False, help='push generated repositories to the official registry') + parser.add_argument('--debug', default=False, action='store_true', + help='Enable debugging output') parser.add_argument('-n', metavar='NAMESPACE', default='library', help='namespace used for generated repositories.' ' Default is library') - parser.add_argument('-b', metavar='BRANCH', default=DEFAULT_BRANCH, + parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH, help='branch in the repository where the library definition' - ' files will be fetched. Default is ' + DEFAULT_BRANCH) - parser.add_argument('repository', default=DEFAULT_REPOSITORY, nargs='?', - help='git repository containing the library definition files.' - ' Default is ' + DEFAULT_REPOSITORY) + ' files will be fetched. Default is ' + brew.DEFAULT_BRANCH) + parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY, + nargs='?', help='git repository containing the library definition' + ' files. Default is ' + brew.DEFAULT_REPOSITORY) args = parser.parse_args() - brew.build_library(args.repository, args.b, args.n, args.push) \ No newline at end of file + brew.build_library(args.repository, args.b, args.n, args.push, args.debug) From 77ff53769714d4b33555b8e412868aad2cc788f9 Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 24 Jul 2013 16:49:19 +0200 Subject: [PATCH 028/242] Brew: Fixed docker-py requirement --- contrib/brew/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/brew/requirements.txt b/contrib/brew/requirements.txt index 5278498791..c66d0798d2 100644 --- a/contrib/brew/requirements.txt +++ b/contrib/brew/requirements.txt @@ -1,2 +1,2 @@ dulwich==0.9.0 -docker==0.1.0 \ No newline at end of file +docker-py==0.1.1 \ No newline at end of file From 94053b42252a1f70c16d2b72399904f79af0b688 Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 24 Jul 2013 23:47:44 +0200 Subject: [PATCH 029/242] Brew: Added cache prefilling and build summary --- contrib/brew/brew/brew.py | 54 ++++++++++++++++++++++++++++++++++++++- contrib/brew/docker-brew | 5 +++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index ab0b886b2c..f361783d54 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -16,7 +16,9 @@ processed = {} def build_library(repository=None, branch=None, namespace=None, push=False, - debug=False): + debug=False, prefill=True): + dst_folder = None + summary = Summary() if repository is None: repository = DEFAULT_REPOSITORY if branch is None: @@ -38,7 +40,9 @@ def build_library(repository=None, branch=None, namespace=None, push=False, if buildfile == 'MAINTAINERS': continue f = open(os.path.join(dst_folder, 'library', buildfile)) + linecnt = 0 for line in f: + linecnt = linecnt + 1 logger.debug('{0} ---> {1}'.format(buildfile, line)) args = line.split() try: @@ -66,11 +70,19 @@ def build_library(repository=None, branch=None, namespace=None, push=False, else: raise RuntimeError('Incorrect line format, ' 'please refer to the docs') + if prefill: + logger.debug('Pulling {0} from official repository (cache ' + 'fill)'.format(buildfile)) + client.pull(buildfile) img = build_repo(url, ref, buildfile, tag, namespace, push) + summary.add_success(buildfile, (linecnt, line), img) processed['{0}@{1}'.format(url, ref)] = img except Exception as e: logger.exception(e) + summary.add_exception(buildfile, (linecnt, line), e) + f.close() + summary.print_summary() def build_repo(repository, ref, docker_repo, docker_tag, namespace, push): @@ -93,3 +105,43 @@ def build_repo(repository, ref, docker_repo, docker_tag, namespace, push): logger.info('Pushing result to the main registry') client.push(docker_repo) return img_id + + +class Summary(object): + def __init__(self): + self._summary = {} + self._has_exc = False + + def _add_data(self, image, linestr, data): + if image not in self._summary: + self._summary[image] = { linestr: data } + else: + self._summary[image][linestr] = data + + def add_exception(self, image, line, exc): + lineno, linestr = line + self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) }) + self._has_exc = True + + def add_success(self, image, line, img_id): + lineno, linestr = line + self._add_data(image, linestr, { 'line': lineno, 'id': img_id }) + + def print_summary(self, logger=None): + linesep = ''.center(61, '-') + '\n' + s = 'BREW BUILD SUMMARY\n' + linesep + success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc) + details = linesep + for image, lines in self._summary.iteritems(): + details = details + '{}\n{}'.format(image, linesep) + for linestr, data in lines.iteritems(): + details = details + '{0:2} | {1} | {2:50}\n'.format( + data['line'], + 'KO' if 'exc' in data else 'OK', + data['exc'] if 'exc' in data else data['id'] + ) + details = details + linesep + if logger: + logger.info(s + success + details) + else: + print s, success, details \ No newline at end of file diff --git a/contrib/brew/docker-brew b/contrib/brew/docker-brew index 3bcd99a3ad..29052771c6 100755 --- a/contrib/brew/docker-brew +++ b/contrib/brew/docker-brew @@ -11,6 +11,8 @@ if __name__ == '__main__': help='push generated repositories to the official registry') parser.add_argument('--debug', default=False, action='store_true', help='Enable debugging output') + parser.add_argument('--noprefill', default=True, action='store_false', + dest='prefill', help='Disable cache prefill') parser.add_argument('-n', metavar='NAMESPACE', default='library', help='namespace used for generated repositories.' ' Default is library') @@ -21,4 +23,5 @@ if __name__ == '__main__': nargs='?', help='git repository containing the library definition' ' files. Default is ' + brew.DEFAULT_REPOSITORY) args = parser.parse_args() - brew.build_library(args.repository, args.b, args.n, args.push, args.debug) + brew.build_library(args.repository, args.b, args.n, args.push, args.debug, + args.prefill) From f1dd299227b15696872822f40a4ab9f1a54098a7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 20:16:02 +0000 Subject: [PATCH 030/242] Use VT100 escape codes : --- commands.go | 14 +------------- utils/utils.go | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/commands.go b/commands.go index 2a672d49e3..99d91ac771 100644 --- a/commands.go +++ b/commands.go @@ -1536,19 +1536,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e } if resp.Header.Get("Content-Type") == "application/json" { - dec := json.NewDecoder(resp.Body) - jm := utils.JSONMessage{} - for { - if err := dec.Decode(&jm); err == io.EOF { - break - } else if err != nil { - return err - } - jm.Display(out) - } - if jm.Progress != "" { - fmt.Fprintf(out, "\n") - } + utils.DisplayJSONMessagesStream(resp.Body, out) } else { if _, err := io.Copy(out, resp.Body); err != nil { return err diff --git a/utils/utils.go b/utils/utils.go index 6361945eef..6081935b41 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -617,6 +617,7 @@ func (jm *JSONMessage) Display(out io.Writer) (error) { if jm.Error != "" { return fmt.Errorf(jm.Error) } + fmt.Fprintf(out, "%c[2K", 27) if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } @@ -626,8 +627,43 @@ func (jm *JSONMessage) Display(out io.Writer) (error) { if jm.Progress != "" { fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) } else { - fmt.Fprintf(out, "%s\n", jm.Status) + fmt.Fprintf(out, "%s\r", jm.Status) } + if jm.ID == "" { + fmt.Fprintf(out, "\n") + } + return nil +} + +func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error { + dec := json.NewDecoder(in) + jm := JSONMessage{} + ids := make(map[string]int) + diff := 0 + for { + if err := dec.Decode(&jm); err == io.EOF { + break + } else if err != nil { + return err + } + if jm.ID != "" { + line, ok := ids[jm.ID] + if !ok { + line = len(ids) + ids[jm.ID] = line + fmt.Fprintf(out, "\n") + diff = 0 + } else { + diff = len(ids) - line + } + fmt.Fprintf(out, "%c[%dA", 27, diff) + } + jm.Display(out) + if jm.ID != "" { + fmt.Fprintf(out, "%c[%dB", 27, diff) + } + } +// fmt.Fprintf(out, "\n") return nil } From 01e98bf0dd26a1de7fd280fd4b0f0a79aedd0cdd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 25 Jul 2013 14:32:46 +0000 Subject: [PATCH 031/242] fix errors --- commands.go | 2 +- utils/utils.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 99d91ac771..f0d8f61999 100644 --- a/commands.go +++ b/commands.go @@ -1536,7 +1536,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e } if resp.Header.Get("Content-Type") == "application/json" { - utils.DisplayJSONMessagesStream(resp.Body, out) + return utils.DisplayJSONMessagesStream(resp.Body, out) } else { if _, err := io.Copy(out, resp.Body); err != nil { return err diff --git a/utils/utils.go b/utils/utils.go index 6081935b41..a66319a1ba 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -658,12 +658,14 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error { } fmt.Fprintf(out, "%c[%dA", 27, diff) } - jm.Display(out) + err := jm.Display(out) if jm.ID != "" { fmt.Fprintf(out, "%c[%dB", 27, diff) } + if err != nil { + return err + } } -// fmt.Fprintf(out, "\n") return nil } From 12d575a6b1125d74168986ca39250c833ddbb033 Mon Sep 17 00:00:00 2001 From: shin- Date: Thu, 25 Jul 2013 21:00:36 +0200 Subject: [PATCH 032/242] Script cleans up downloaded repos, uses quiet build --- contrib/brew/brew/brew.py | 11 +++++++---- contrib/brew/requirements.txt | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index f361783d54..91f7b01b94 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -1,5 +1,6 @@ import os import logging +from shutil import rmtree import docker @@ -82,7 +83,9 @@ def build_library(repository=None, branch=None, namespace=None, push=False, summary.add_exception(buildfile, (linecnt, line), e) f.close() - summary.print_summary() + if dst_folder != repository: + rmtree(dst_folder, True) + summary.print_summary(logger) def build_repo(repository, ref, docker_repo, docker_tag, namespace, push): @@ -94,9 +97,9 @@ def build_repo(repository, ref, docker_repo, docker_tag, namespace, push): if not 'Dockerfile' in os.listdir(dst_folder): raise RuntimeError('Dockerfile not found in cloned repository') logger.info('Building using dockerfile...') - img_id, logs = client.build(path=dst_folder) - - if not img_id: + img_id, logs = client.build(path=dst_folder, quiet=True) + rmtree(dst_folder, True) + else: img_id = processed['{0}@{1}'.format(repository, ref)] logger.info('Committing to {0}:{1}'.format(docker_repo, docker_tag or 'latest')) diff --git a/contrib/brew/requirements.txt b/contrib/brew/requirements.txt index c66d0798d2..8006177ce6 100644 --- a/contrib/brew/requirements.txt +++ b/contrib/brew/requirements.txt @@ -1,2 +1,2 @@ dulwich==0.9.0 -docker-py==0.1.1 \ No newline at end of file +docker-py==0.1.2 \ No newline at end of file From f4b63d9eeaddae6cb15775bea13ec4bca6e115da Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Thu, 25 Jul 2013 17:19:58 -0700 Subject: [PATCH 033/242] Enabled the docs to generate manpages. * changed conf.py to reference toctree.rst instead of index * Added note to README to upgrade your sphinx to the latest version to prevent a bug with .. note:: blocks. --- docs/README.md | 5 +++-- docs/sources/conf.py | 6 +++--- docs/sources/use/builder.rst | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/README.md b/docs/README.md index 74ab2bd0cd..32be549ef5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,8 +39,6 @@ When you need to add images, try to make them as small as possible (e.g. as gif) Notes ----- -* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification. -So changes to those pages should be made directly in html * For the template the css is compiled from less. When changes are needed they can be compiled using lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css`` @@ -75,3 +73,6 @@ Guides on using sphinx * Code examples Start without $, so it's easy to copy and paste. + +* To make the manpages, simply run 'make man'. Pleae note there is a bug in spinx 1.1.3 which makes this fail. +Upgrade to the latest version of sphinx. \ No newline at end of file diff --git a/docs/sources/conf.py b/docs/sources/conf.py index ea8e1f43f0..b4c23f0c58 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -203,7 +203,7 @@ latex_elements = { # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Docker.tex', u'Docker Documentation', + ('toctree', 'Docker.tex', u'Docker Documentation', u'Team Docker', 'manual'), ] @@ -233,7 +233,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'docker', u'Docker Documentation', + ('toctree', 'docker', u'Docker Documentation', [u'Team Docker'], 1) ] @@ -247,7 +247,7 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Docker', u'Docker Documentation', + ('toctree', 'Docker', u'Docker Documentation', u'Team Docker', 'Docker', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index aa2fd6b92a..4a9786ee47 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -107,8 +107,8 @@ the image. This is functionally equivalent to running ``docker commit -run '{"Cmd": }'`` outside the builder. .. note:: - Don't confuse `RUN` with `CMD`. `RUN` actually runs a - command and commits the result; `CMD` does not execute anything at + Don't confuse ``RUN`` with ``CMD``. ``RUN`` actually runs a + command and commits the result; ``CMD`` does not execute anything at build time, but specifies the intended command for the image. 3.5 EXPOSE From 63876e7dbdd372745855345632af62b8cd976dfa Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 29 Jul 2013 12:15:27 +0000 Subject: [PATCH 034/242] use ParseRepositoryTag instead on split on : in imagedelete --- server.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/server.go b/server.go index ce1fc8eaf8..f416d8f06d 100644 --- a/server.go +++ b/server.go @@ -1037,13 +1037,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { return nil, nil } - var tag string - if strings.Contains(name, ":") { - nameParts := strings.Split(name, ":") - name = nameParts[0] - tag = nameParts[1] - } - + name, tag := utils.ParseRepositoryTag(name) return srv.deleteImage(img, name, tag) } From 3852d0599097581d5dc0bcfcb7aa010d564beb9a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 29 Jul 2013 12:16:01 +0000 Subject: [PATCH 035/242] add ParseRepositoryTag tests --- utils/utils.go | 7 +++---- utils/utils_test.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index acb015becd..c70e80b72e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -611,11 +611,11 @@ type JSONMessage struct { Status string `json:"status,omitempty"` Progress string `json:"progress,omitempty"` Error string `json:"error,omitempty"` - ID string `json:"id,omitempty"` - Time int64 `json:"time,omitempty"` + ID string `json:"id,omitempty"` + Time int64 `json:"time,omitempty"` } -func (jm *JSONMessage) Display(out io.Writer) (error) { +func (jm *JSONMessage) Display(out io.Writer) error { if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } @@ -631,7 +631,6 @@ func (jm *JSONMessage) Display(out io.Writer) (error) { return nil } - type StreamFormatter struct { json bool used bool diff --git a/utils/utils_test.go b/utils/utils_test.go index 5caa809f67..5c480b9438 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -282,3 +282,24 @@ func TestParseHost(t *testing.T) { t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr) } } + +func TestParseRepositoryTag(t *testing.T) { + if repo, tag := ParseRepositoryTag("root"); repo != "root" || tag != "" { + t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "", repo, tag) + } + if repo, tag := ParseRepositoryTag("root:tag"); repo != "root" || tag != "tag" { + t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "tag", repo, tag) + } + if repo, tag := ParseRepositoryTag("user/repo"); repo != "user/repo" || tag != "" { + t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "", repo, tag) + } + if repo, tag := ParseRepositoryTag("user/repo:tag"); repo != "user/repo" || tag != "tag" { + t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "tag", repo, tag) + } + if repo, tag := ParseRepositoryTag("url:5000/repo"); repo != "url:5000/repo" || tag != "" { + t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "", repo, tag) + } + if repo, tag := ParseRepositoryTag("url:5000/repo:tag"); repo != "url:5000/repo" || tag != "tag" { + t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag) + } +} From bb241c10e2f124406f00c8e40e00fca67934638a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 29 Jul 2013 12:16:14 +0000 Subject: [PATCH 036/242] add regression tests --- server_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server_test.go b/server_test.go index 8612b3fcea..0caf8a5f24 100644 --- a/server_test.go +++ b/server_test.go @@ -20,7 +20,7 @@ func TestContainerTagImageDelete(t *testing.T) { if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil { t.Fatal(err) } - if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil { + if err := srv.runtime.repositories.Set("utest:5000/docker", "tag2", unitTestImageName, false); err != nil { t.Fatal(err) } @@ -33,7 +33,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images)) } - if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil { + if _, err := srv.ImageDelete("utest:5000/docker:tag2", true); err != nil { t.Fatal(err) } From b2aa877bf0a3f140516d1c17117369b2194f2b91 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 29 Jul 2013 16:40:35 +0000 Subject: [PATCH 037/242] fix #1314 discard error when loading old container format --- container.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index d0b6ca4ce2..cbcd3f17fa 100644 --- a/container.go +++ b/container.go @@ -266,7 +266,8 @@ func (container *Container) FromDisk() error { return err } // Load container settings - if err := json.Unmarshal(data, container); err != nil { + // udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it + if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") { return err } return nil From 17ffb0ac84874656a61a477fa29048cb82b28748 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Mon, 29 Jul 2013 09:45:19 -0700 Subject: [PATCH 038/242] testing, issue #1331: Add registry functional test to docker-ci --- testing/README.rst | 4 ++++ testing/Vagrantfile | 4 +++- testing/buildbot/credentials.cfg | 5 +++++ testing/buildbot/master.cfg | 20 ++++++++++++++++++-- testing/buildbot/requirements.txt | 1 + testing/buildbot/setup_credentials.sh | 17 +++++++++++++++++ testing/functionaltests/test_registry.sh | 11 +++++++++++ 7 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 testing/buildbot/credentials.cfg create mode 100755 testing/buildbot/setup_credentials.sh create mode 100755 testing/functionaltests/test_registry.sh diff --git a/testing/README.rst b/testing/README.rst index 3b11092f9f..ce5aa837a4 100644 --- a/testing/README.rst +++ b/testing/README.rst @@ -40,6 +40,10 @@ Deployment export SMTP_USER=xxxxxxxxxxxx export SMTP_PWD=xxxxxxxxxxxx + # Define docker registry functional test credentials + export REGISTRY_USER=xxxxxxxxxxxx + export REGISTRY_PWD=xxxxxxxxxxxx + # Checkout docker git clone git://github.com/dotcloud/docker.git diff --git a/testing/Vagrantfile b/testing/Vagrantfile index 47257201dc..e76a951508 100644 --- a/testing/Vagrantfile +++ b/testing/Vagrantfile @@ -29,7 +29,9 @@ Vagrant::Config.run do |config| "chown #{USER}.#{USER} /data; cd /data; " \ "#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH} #{ENV['BUILDBOT_PWD']} " \ "#{ENV['IRC_PWD']} #{ENV['IRC_CHANNEL']} #{ENV['SMTP_USER']} " \ - "#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; " + "#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; " \ + "#{CFG_PATH}/setup_credentials.sh #{USER} " \ + "#{ENV['REGISTRY_USER']} #{ENV['REGISTRY_PWD']}; " # Install docker dependencies pkg_cmd << "apt-get install -q -y python-software-properties; " \ "add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \ diff --git a/testing/buildbot/credentials.cfg b/testing/buildbot/credentials.cfg new file mode 100644 index 0000000000..fbdd35d578 --- /dev/null +++ b/testing/buildbot/credentials.cfg @@ -0,0 +1,5 @@ +# Credentials for tests. Buildbot source this file on tests +# when needed. + +# Docker registry credentials. Format: 'username:password' +export DOCKER_CREDS='' diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index 61912808ec..29926dbe5f 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -19,6 +19,7 @@ TEST_USER = 'buildbot' # Credential to authenticate build triggers TEST_PWD = 'docker' # Credential to authenticate build triggers BUILDER_NAME = 'docker' GITHUB_DOCKER = 'github.com/dotcloud/docker' +BUILDBOT_PATH = '/data/buildbot' DOCKER_PATH = '/data/docker' BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME) DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker' @@ -41,16 +42,19 @@ c['db'] = {'db_url':"sqlite:///state.sqlite"} c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)] c['slavePortnum'] = PORT_MASTER + # Schedulers c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[BUILDER_NAME, - 'coverage'])] + 'registry','coverage'])] c['schedulers'] += [SingleBranchScheduler(name="all", change_filter=filter.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=[BUILDER_NAME])] -c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage'], +c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage','registry'], hour=0, minute=30)] + # Builders +# Docker commit test factory = BuildFactory() factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; " @@ -58,6 +62,7 @@ factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, "go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))])) c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] + # Docker coverage test coverage_cmd = ('GOPATH=`pwd` go get -d github.com/dotcloud/docker\n' 'GOPATH=`pwd` go get github.com/axw/gocov/gocov\n' @@ -69,6 +74,17 @@ factory.addStep(ShellCommand(description='Coverage',logEnviron=False,usePTY=True c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'], factory=factory)] +# Registry Functionaltest builder +factory = BuildFactory() +factory.addStep(ShellCommand(description='registry', logEnviron=False, + command='. {0}/master/credentials.cfg; ' + '{1}/testing/functionaltests/test_registry.sh'.format(BUILDBOT_PATH, + DOCKER_PATH), usePTY=True)) + +c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'], + factory=factory)] + + # Status authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]), forceBuild='auth') diff --git a/testing/buildbot/requirements.txt b/testing/buildbot/requirements.txt index 0e451b017d..4e183ba062 100644 --- a/testing/buildbot/requirements.txt +++ b/testing/buildbot/requirements.txt @@ -4,3 +4,4 @@ buildbot==0.8.7p1 buildbot_slave==0.8.7p1 nose==1.2.1 requests==1.1.0 +flask==0.10.1 diff --git a/testing/buildbot/setup_credentials.sh b/testing/buildbot/setup_credentials.sh new file mode 100755 index 0000000000..f093815d60 --- /dev/null +++ b/testing/buildbot/setup_credentials.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Setup of test credentials. Called by Vagrantfile +export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin" + +USER=$1 +REGISTRY_USER=$2 +REGISTRY_PWD=$3 + +BUILDBOT_PATH="/data/buildbot" +DOCKER_PATH="/data/docker" + +function run { su $USER -c "$1"; } + +run "cp $DOCKER_PATH/testing/buildbot/credentials.cfg $BUILDBOT_PATH/master" +cd $BUILDBOT_PATH/master +run "sed -i -E 's#(export DOCKER_CREDS=).+#\1\"$REGISTRY_USER:$REGISTRY_PWD\"#' credentials.cfg" diff --git a/testing/functionaltests/test_registry.sh b/testing/functionaltests/test_registry.sh new file mode 100755 index 0000000000..095a731631 --- /dev/null +++ b/testing/functionaltests/test_registry.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# Cleanup +rm -rf docker-registry + +# Get latest docker registry +git clone https://github.com/dotcloud/docker-registry.git + +# Configure and run registry tests +cd docker-registry; cp config_sample.yml config.yml +cd test; python -m unittest workflow From f7542664e3efb35b6b153dc2b2cc9cdac181989a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 29 Jul 2013 11:03:09 -0700 Subject: [PATCH 039/242] Make sure ADD will create everything in 0755 --- archive.go | 2 +- docs/sources/use/builder.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/archive.go b/archive.go index 01af86006f..bb79cd34d4 100644 --- a/archive.go +++ b/archive.go @@ -173,7 +173,7 @@ func CopyWithTar(src, dst string) error { } // Create dst, copy src's content into it utils.Debugf("Creating dest directory: %s", dst) - if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) { + if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) { return err } utils.Debugf("Calling TarUntar(%s, %s)", src, dst) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index aa2fd6b92a..9f5ee8b795 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -182,7 +182,7 @@ The copy obeys the following rules: written at ````. * If ```` doesn't exist, it is created along with all missing directories in its path. All new files and directories are created - with mode 0700, uid and gid 0. + with mode 0755, uid and gid 0. 3.8 ENTRYPOINT -------------- From 8ca7b0646e6c4346075656f46847f53c2e868a3d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 17 Jul 2013 12:13:22 -0700 Subject: [PATCH 040/242] Refactor checksum --- auth/auth.go | 4 +- graph.go | 61 ++--------------- image.go | 99 --------------------------- registry/registry.go | 58 +++++++++++++--- server.go | 96 +++++---------------------- utils/tarsum.go | 155 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 226 insertions(+), 247 deletions(-) create mode 100644 utils/tarsum.go diff --git a/auth/auth.go b/auth/auth.go index 39de876875..e402031ca2 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -16,9 +16,9 @@ import ( const CONFIGFILE = ".dockercfg" // Only used for user auth + account creation -const INDEXSERVER = "https://index.docker.io/v1/" +//const INDEXSERVER = "https://index.docker.io/v1/" -//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/" +const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" var ( ErrConfigFileMissing = errors.New("The Auth config file is missing") diff --git a/graph.go b/graph.go index 42d1bdbd4c..eea2cbec8a 100644 --- a/graph.go +++ b/graph.go @@ -1,9 +1,7 @@ package docker import ( - "encoding/json" "fmt" - "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -11,17 +9,13 @@ import ( "path" "path/filepath" "strings" - "sync" "time" ) // A Graph is a store for versioned filesystem images and the relationship between them. type Graph struct { - Root string - idIndex *utils.TruncIndex - checksumLock map[string]*sync.Mutex - lockSumFile *sync.Mutex - lockSumMap *sync.Mutex + Root string + idIndex *utils.TruncIndex } // NewGraph instantiates a new graph at the given root path in the filesystem. @@ -36,11 +30,8 @@ func NewGraph(root string) (*Graph, error) { return nil, err } graph := &Graph{ - Root: abspath, - idIndex: utils.NewTruncIndex(), - checksumLock: make(map[string]*sync.Mutex), - lockSumFile: &sync.Mutex{}, - lockSumMap: &sync.Mutex{}, + Root: abspath, + idIndex: utils.NewTruncIndex(), } if err := graph.restore(); err != nil { return nil, err @@ -99,11 +90,6 @@ func (graph *Graph) Get(name string) (*Image, error) { return nil, err } } - graph.lockSumMap.Lock() - defer graph.lockSumMap.Unlock() - if _, exists := graph.checksumLock[img.ID]; !exists { - graph.checksumLock[img.ID] = &sync.Mutex{} - } return img, nil } @@ -126,7 +112,6 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut if err := graph.Register(layerData, layerData != nil, img); err != nil { return nil, err } - go img.Checksum() return img, nil } @@ -154,7 +139,6 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { } img.graph = graph graph.idIndex.Add(img.ID) - graph.checksumLock[img.ID] = &sync.Mutex{} return nil } @@ -311,40 +295,3 @@ func (graph *Graph) Heads() (map[string]*Image, error) { func (graph *Graph) imageRoot(id string) string { return path.Join(graph.Root, id) } - -func (graph *Graph) getStoredChecksums() (map[string]string, error) { - checksums := make(map[string]string) - // FIXME: Store the checksum in memory - - if checksumDict, err := ioutil.ReadFile(path.Join(graph.Root, "checksums")); err == nil { - if err := json.Unmarshal(checksumDict, &checksums); err != nil { - return nil, err - } - } - return checksums, nil -} - -func (graph *Graph) storeChecksums(checksums map[string]string) error { - checksumJSON, err := json.Marshal(checksums) - if err != nil { - return err - } - if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJSON, 0600); err != nil { - return err - } - return nil -} - -func (graph *Graph) UpdateChecksums(newChecksums map[string]*registry.ImgData) error { - graph.lockSumFile.Lock() - defer graph.lockSumFile.Unlock() - - localChecksums, err := graph.getStoredChecksums() - if err != nil { - return err - } - for id, elem := range newChecksums { - localChecksums[id] = elem.Checksum - } - return graph.storeChecksums(localChecksums) -} diff --git a/image.go b/image.go index 5240ec776f..dd066f88cd 100644 --- a/image.go +++ b/image.go @@ -2,7 +2,6 @@ package docker import ( "crypto/rand" - "crypto/sha256" "encoding/hex" "encoding/json" "fmt" @@ -72,26 +71,6 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error { return err } - if store { - layerArchive := layerArchivePath(root) - file, err := os.OpenFile(layerArchive, os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - return err - } - // FIXME: Retrieve the image layer size from here? - if _, err := io.Copy(file, layerData); err != nil { - return err - } - // FIXME: Don't close/open, read/write instead of Copy - file.Close() - - file, err = os.Open(layerArchive) - if err != nil { - return err - } - defer file.Close() - layerData = file - } // If layerData is not nil, unpack it into the new layer if layerData != nil { start := time.Now() @@ -128,10 +107,6 @@ func layerPath(root string) string { return path.Join(root, "layer") } -func layerArchivePath(root string) string { - return path.Join(root, "layer.tar.xz") -} - func jsonPath(root string) string { return path.Join(root, "json") } @@ -308,80 +283,6 @@ func (img *Image) layer() (string, error) { return layerPath(root), nil } -func (img *Image) Checksum() (string, error) { - img.graph.checksumLock[img.ID].Lock() - defer img.graph.checksumLock[img.ID].Unlock() - - root, err := img.root() - if err != nil { - return "", err - } - - checksums, err := img.graph.getStoredChecksums() - if err != nil { - return "", err - } - if checksum, ok := checksums[img.ID]; ok { - return checksum, nil - } - - layer, err := img.layer() - if err != nil { - return "", err - } - jsonData, err := ioutil.ReadFile(jsonPath(root)) - if err != nil { - return "", err - } - - var layerData io.Reader - - if file, err := os.Open(layerArchivePath(root)); err != nil { - if os.IsNotExist(err) { - layerData, err = Tar(layer, Xz) - if err != nil { - return "", err - } - } else { - return "", err - } - } else { - defer file.Close() - layerData = file - } - - h := sha256.New() - if _, err := h.Write(jsonData); err != nil { - return "", err - } - if _, err := h.Write([]byte("\n")); err != nil { - return "", err - } - - if _, err := io.Copy(h, layerData); err != nil { - return "", err - } - hash := "sha256:" + hex.EncodeToString(h.Sum(nil)) - - // Reload the json file to make sure not to overwrite faster sums - img.graph.lockSumFile.Lock() - defer img.graph.lockSumFile.Unlock() - - checksums, err = img.graph.getStoredChecksums() - if err != nil { - return "", err - } - - checksums[img.ID] = hash - - // Dump the checksums to disc - if err := img.graph.storeChecksums(checksums); err != nil { - return hash, err - } - - return hash, nil -} - func (img *Image) getParentsSize(size int64) int64 { parentImage, err := img.GetParent() if err != nil || parentImage == nil { diff --git a/registry/registry.go b/registry/registry.go index adef1c7baa..cac77ba049 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -330,16 +330,52 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e }, nil } +func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error { + + utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum") + + req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil) + if err != nil { + return err + } + req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + req.Header.Set("X-Docker-Checksum", imgData.Checksum) + + res, err := doWithCookies(r.client, req) + if err != nil { + return fmt.Errorf("Failed to upload metadata: %s", err) + } + defer res.Body.Close() + if len(res.Cookies()) > 0 { + r.client.Jar.SetCookies(req.URL, res.Cookies()) + } + if res.StatusCode != 200 { + errBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) + } + var jsonBody map[string]string + if err := json.Unmarshal(errBody, &jsonBody); err != nil { + errBody = []byte(err.Error()) + } else if jsonBody["error"] == "Image already exists" { + return ErrAlreadyExists + } + return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody) + } + return nil +} + // Push a local image to the registry func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error { - // FIXME: try json with UTF8 + + utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json") + req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw)) if err != nil { return err } req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) - req.Header.Set("X-Docker-Checksum", imgData.Checksum) r.setUserAgent(req) utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum) @@ -364,10 +400,14 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return nil } -func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) error { - req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", layer) +func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) (checksum string, err error) { + + utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") + + tarsumLayer := &utils.TarSum{Reader: layer} + req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) if err != nil { - return err + return "", err } req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} @@ -375,18 +415,18 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { - return fmt.Errorf("Failed to upload layer: %s", err) + return "", fmt.Errorf("Failed to upload layer: %s", err) } defer res.Body.Close() if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) + return "", fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) } - return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody) + return "", fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody) } - return nil + return tarsumLayer.Sum(), nil } func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) { diff --git a/server.go b/server.go index ce1fc8eaf8..6944df315f 100644 --- a/server.go +++ b/server.go @@ -455,12 +455,6 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName return err } - utils.Debugf("Updating checksums") - // Reload the json file to make sure not to overwrite faster sums - if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil { - return err - } - utils.Debugf("Retrieving the tag list") tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) if err != nil { @@ -598,41 +592,6 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut return nil } -// Retrieve the checksum of an image -// Priority: -// - Check on the stored checksums -// - Check if the archive exists, if it does not, ask the registry -// - If the archive does exists, process the checksum from it -// - If the archive does not exists and not found on registry, process checksum from layer -func (srv *Server) getChecksum(imageID string) (string, error) { - // FIXME: Use in-memory map instead of reading the file each time - if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil { - return "", err - } else if checksum, exists := sums[imageID]; exists { - return checksum, nil - } - - img, err := srv.runtime.graph.Get(imageID) - if err != nil { - return "", err - } - - if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageID))); err != nil { - if os.IsNotExist(err) { - // TODO: Ask the registry for the checksum - // As the archive is not there, it is supposed to come from a pull. - } else { - return "", err - } - } - - checksum, err := img.Checksum() - if err != nil { - return "", err - } - return checksum, nil -} - // Retrieve the all the images to be uploaded in the correct order // Note: we can't use a map as it is not ordered func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgData, error) { @@ -649,14 +608,10 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat return nil } imageSet[img.ID] = struct{}{} - checksum, err := srv.getChecksum(img.ID) - if err != nil { - return err - } + imgList = append([]*registry.ImgData{{ - ID: img.ID, - Checksum: checksum, - Tag: tag, + ID: img.ID, + Tag: tag, }}, imgList...) return nil }) @@ -666,7 +621,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) - out.Write(sf.FormatStatus("Processing checksums")) + imgList, err := srv.getImageList(localRepo) if err != nil { return err @@ -716,14 +671,8 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } out.Write(sf.FormatStatus("Pushing %s", imgID)) - // Make sure we have the image's checksum - checksum, err := srv.getChecksum(imgID) - if err != nil { - return err - } imgData := ®istry.ImgData{ - ID: imgID, - Checksum: checksum, + ID: imgID, } // Send the json @@ -735,36 +684,23 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, return err } - // Retrieve the tarball to be sent - var layerData *TempArchive - // If the archive exists, use it - file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgID))) + layerData, err := srv.runtime.graph.TempLayerArchive(imgID, Uncompressed, sf, out) if err != nil { - if os.IsNotExist(err) { - // If the archive does not exist, create one from the layer - layerData, err = srv.runtime.graph.TempLayerArchive(imgID, Xz, sf, out) - if err != nil { - return fmt.Errorf("Failed to generate layer archive: %s", err) - } - } else { - return err - } - } else { - defer file.Close() - st, err := file.Stat() - if err != nil { - return err - } - layerData = &TempArchive{ - File: file, - Size: st.Size(), - } + return fmt.Errorf("Failed to generate layer archive: %s", err) } // Send the layer - if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { + if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { + return err + } else { + imgData.Checksum = checksum + } + + // Send the checksum + if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil { return err } + return nil } diff --git a/utils/tarsum.go b/utils/tarsum.go new file mode 100644 index 0000000000..0fd5ac106b --- /dev/null +++ b/utils/tarsum.go @@ -0,0 +1,155 @@ +package utils + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "hash" + "io" + "sort" + "strconv" +) + +type verboseHash struct { + hash.Hash +} + +func (h verboseHash) Write(buf []byte) (int, error) { + Debugf("--->%s<---", buf) + return h.Hash.Write(buf) +} + +type TarSum struct { + io.Reader + tarR *tar.Reader + tarW *tar.Writer + gz *gzip.Writer + bufTar *bytes.Buffer + bufGz *bytes.Buffer + h hash.Hash + h2 verboseHash + sums []string + finished bool + first bool +} + +func (ts *TarSum) encodeHeader(h *tar.Header) error { + for _, elem := range [][2]string{ + {"name", h.Name}, + {"mode", strconv.Itoa(int(h.Mode))}, + {"uid", strconv.Itoa(h.Uid)}, + {"gid", strconv.Itoa(h.Gid)}, + {"size", strconv.Itoa(int(h.Size))}, + {"mtime", strconv.Itoa(int(h.ModTime.UTC().Unix()))}, + {"typeflag", string([]byte{h.Typeflag})}, + {"linkname", h.Linkname}, + {"uname", h.Uname}, + {"gname", h.Gname}, + {"devmajor", strconv.Itoa(int(h.Devmajor))}, + {"devminor", strconv.Itoa(int(h.Devminor))}, + // {"atime", strconv.Itoa(int(h.AccessTime.UTC().Unix()))}, + // {"ctime", strconv.Itoa(int(h.ChangeTime.UTC().Unix()))}, + } { + // Debugf("-->%s<-- -->%s<--", elem[0], elem[1]) + if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil { + return err + } + } + return nil +} + +func (ts *TarSum) Read(buf []byte) (int, error) { + if ts.gz == nil { + ts.bufTar = bytes.NewBuffer([]byte{}) + ts.bufGz = bytes.NewBuffer([]byte{}) + ts.tarR = tar.NewReader(ts.Reader) + ts.tarW = tar.NewWriter(ts.bufTar) + ts.gz = gzip.NewWriter(ts.bufGz) + ts.h = sha256.New() + // ts.h = verboseHash{sha256.New()} + ts.h.Reset() + ts.first = true + } + + if ts.finished { + return ts.bufGz.Read(buf) + } + buf2 := make([]byte, len(buf), cap(buf)) + + n, err := ts.tarR.Read(buf2) + if err != nil { + if err == io.EOF { + if _, err := ts.h.Write(buf2[:n]); err != nil { + return 0, err + } + if !ts.first { + ts.sums = append(ts.sums, hex.EncodeToString(ts.h.Sum(nil))) + ts.h.Reset() + } else { + ts.first = false + } + + currentHeader, err := ts.tarR.Next() + if err != nil { + if err == io.EOF { + if err := ts.gz.Close(); err != nil { + return 0, err + } + ts.finished = true + return n, nil + } + return n, err + } + if err := ts.encodeHeader(currentHeader); err != nil { + return 0, err + } + if err := ts.tarW.WriteHeader(currentHeader); err != nil { + return 0, err + } + if _, err := ts.tarW.Write(buf2[:n]); err != nil { + return 0, err + } + ts.tarW.Flush() + if _, err := io.Copy(ts.gz, ts.bufTar); err != nil { + return 0, err + } + ts.gz.Flush() + + return ts.bufGz.Read(buf) + } + return n, err + } + + // Filling the hash buffer + if _, err = ts.h.Write(buf2[:n]); err != nil { + return 0, err + } + + // Filling the tar writter + if _, err = ts.tarW.Write(buf2[:n]); err != nil { + return 0, err + } + ts.tarW.Flush() + + // Filling the gz writter + if _, err = io.Copy(ts.gz, ts.bufTar); err != nil { + return 0, err + } + ts.gz.Flush() + + return ts.bufGz.Read(buf) +} + +func (ts *TarSum) Sum() string { + sort.Strings(ts.sums) + h := sha256.New() + for _, sum := range ts.sums { + Debugf("-->%s<--", sum) + h.Write([]byte(sum)) + } + checksum := "tarsum+sha256:" + hex.EncodeToString(ts.h.Sum(nil)) + Debugf("checksum processed: %s", checksum) + return checksum +} From e3f68b22d8f0635a8c08ab56721e56dbe570a49a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 14:50:32 -0700 Subject: [PATCH 041/242] Handle extra-paremeter within checksum calculations --- registry/registry.go | 17 +++++++++++------ server.go | 2 +- utils/tarsum.go | 7 +++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index cac77ba049..40b9872a4a 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -17,8 +17,10 @@ import ( "strings" ) -var ErrAlreadyExists = errors.New("Image already exists") -var ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") +var ( + ErrAlreadyExists = errors.New("Image already exists") + ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") +) func pingRegistryEndpoint(endpoint string) error { if endpoint == auth.IndexServerAddress() { @@ -266,8 +268,11 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ } func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) { + repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) + utils.Debugf("[registry] Calling GET %s", repositoryTarget) + req, err := r.opaqueRequest("GET", repositoryTarget, nil) if err != nil { return nil, err @@ -378,7 +383,6 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) r.setUserAgent(req) - utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum) res, err := doWithCookies(r.client, req) if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) @@ -400,11 +404,12 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return nil } -func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) (checksum string, err error) { +func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, err error) { utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") tarsumLayer := &utils.TarSum{Reader: layer} + req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) if err != nil { return "", err @@ -426,7 +431,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr } return "", fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody) } - return tarsumLayer.Sum(), nil + return tarsumLayer.Sum(jsonRaw), nil } func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) { @@ -474,7 +479,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData } u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix) - utils.Debugf("PUT %s", u) + utils.Debugf("[registry] PUT %s", u) utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON) req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON)) if err != nil { diff --git a/server.go b/server.go index 6944df315f..7309279805 100644 --- a/server.go +++ b/server.go @@ -690,7 +690,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // Send the layer - if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { + if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token, jsonRaw); err != nil { return err } else { imgData.Checksum = checksum diff --git a/utils/tarsum.go b/utils/tarsum.go index 0fd5ac106b..015b9b3076 100644 --- a/utils/tarsum.go +++ b/utils/tarsum.go @@ -142,14 +142,17 @@ func (ts *TarSum) Read(buf []byte) (int, error) { return ts.bufGz.Read(buf) } -func (ts *TarSum) Sum() string { +func (ts *TarSum) Sum(extra []byte) string { sort.Strings(ts.sums) h := sha256.New() for _, sum := range ts.sums { Debugf("-->%s<--", sum) h.Write([]byte(sum)) } - checksum := "tarsum+sha256:" + hex.EncodeToString(ts.h.Sum(nil)) + if extra != nil { + h.Write(extra) + } + checksum := "tarsum+sha256:" + hex.EncodeToString(h.Sum(nil)) Debugf("checksum processed: %s", checksum) return checksum } From 0badda9f1587c11a13dca17c68b30addd757237c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 15:40:33 -0700 Subject: [PATCH 042/242] Refactor the image size storage --- graph.go | 6 +++--- image.go | 43 ++++++++++++++++++++++++++++++++++--------- server.go | 2 +- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/graph.go b/graph.go index eea2cbec8a..1fe14f6458 100644 --- a/graph.go +++ b/graph.go @@ -109,7 +109,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut img.Container = container.ID img.ContainerConfig = *container.Config } - if err := graph.Register(layerData, layerData != nil, img); err != nil { + if err := graph.Register(nil, layerData, layerData != nil, img); err != nil { return nil, err } return img, nil @@ -117,7 +117,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut // Register imports a pre-existing image into the graph. // FIXME: pass img as first argument -func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { +func (graph *Graph) Register(jsonData []byte, layerData Archive, store bool, img *Image) error { if err := ValidateID(img.ID); err != nil { return err } @@ -130,7 +130,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { if err != nil { return fmt.Errorf("Mktemp failed: %s", err) } - if err := StoreImage(img, layerData, tmp, store); err != nil { + if err := StoreImage(img, jsonData, layerData, tmp, store); err != nil { return err } // Commit diff --git a/image.go b/image.go index dd066f88cd..7f03cc4bf8 100644 --- a/image.go +++ b/image.go @@ -13,6 +13,7 @@ import ( "os/exec" "path" "path/filepath" + "strconv" "strings" "time" ) @@ -46,6 +47,19 @@ func LoadImage(root string) (*Image, error) { if err := ValidateID(img.ID); err != nil { return nil, err } + + if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + } else { + if size, err := strconv.Atoi(string(buf)); err != nil { + return nil, err + } else { + img.Size = int64(size) + } + } + // Check that the filesystem layer exists if stat, err := os.Stat(layerPath(root)); err != nil { if os.IsNotExist(err) { @@ -58,7 +72,7 @@ func LoadImage(root string) (*Image, error) { return img, nil } -func StoreImage(img *Image, layerData Archive, root string, store bool) error { +func StoreImage(img *Image, jsonData []byte, layerData Archive, root string, store bool) error { // Check that root doesn't already exist if _, err := os.Stat(root); err == nil { return fmt.Errorf("Image %s already exists", img.ID) @@ -81,25 +95,36 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error { utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds()) } + // If raw json is provided, then use it + if jsonData != nil { + return ioutil.WriteFile(jsonPath(root), jsonData, 0600) + } else { // Otherwise, unmarshal the image + jsonData, err := json.Marshal(img) + if err != nil { + return err + } + if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { + return err + } + } + return StoreSize(img, root) } func StoreSize(img *Image, root string) error { layer := layerPath(root) + var totalSize int64 = 0 filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error { - img.Size += fileInfo.Size() + totalSize += fileInfo.Size() return nil }) + img.Size = totalSize - // Store the json ball - jsonData, err := json.Marshal(img) - if err != nil { - return err - } - if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { - return err + if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil { + return nil } + return nil } diff --git a/server.go b/server.go index 7309279805..de73d6c815 100644 --- a/server.go +++ b/server.go @@ -439,7 +439,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return err } defer layer.Close() - if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { + if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { return err } } From 0f134b4bf81a4d0160932852854b190b7ee7e3b9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 15:44:55 -0700 Subject: [PATCH 043/242] Remove unused parameter --- graph.go | 6 +++--- image.go | 2 +- server.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/graph.go b/graph.go index 1fe14f6458..bf27f93ed4 100644 --- a/graph.go +++ b/graph.go @@ -109,7 +109,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut img.Container = container.ID img.ContainerConfig = *container.Config } - if err := graph.Register(nil, layerData, layerData != nil, img); err != nil { + if err := graph.Register(nil, layerData, img); err != nil { return nil, err } return img, nil @@ -117,7 +117,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut // Register imports a pre-existing image into the graph. // FIXME: pass img as first argument -func (graph *Graph) Register(jsonData []byte, layerData Archive, store bool, img *Image) error { +func (graph *Graph) Register(jsonData []byte, layerData Archive, img *Image) error { if err := ValidateID(img.ID); err != nil { return err } @@ -130,7 +130,7 @@ func (graph *Graph) Register(jsonData []byte, layerData Archive, store bool, img if err != nil { return fmt.Errorf("Mktemp failed: %s", err) } - if err := StoreImage(img, jsonData, layerData, tmp, store); err != nil { + if err := StoreImage(img, jsonData, layerData, tmp); err != nil { return err } // Commit diff --git a/image.go b/image.go index 7f03cc4bf8..220f1c70a5 100644 --- a/image.go +++ b/image.go @@ -72,7 +72,7 @@ func LoadImage(root string) (*Image, error) { return img, nil } -func StoreImage(img *Image, jsonData []byte, layerData Archive, root string, store bool) error { +func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) error { // Check that root doesn't already exist if _, err := os.Stat(root); err == nil { return fmt.Errorf("Image %s already exists", img.ID) diff --git a/server.go b/server.go index de73d6c815..aecc730794 100644 --- a/server.go +++ b/server.go @@ -439,7 +439,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return err } defer layer.Close() - if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { + if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), img); err != nil { return err } } From 394941b6b0a30fecf8ae7b6de5880fa553141f93 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 16:16:31 -0700 Subject: [PATCH 044/242] Switch json/payload order --- auth/auth.go | 4 ++-- utils/tarsum.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index e402031ca2..6dd6ceb620 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -16,9 +16,9 @@ import ( const CONFIGFILE = ".dockercfg" // Only used for user auth + account creation -//const INDEXSERVER = "https://index.docker.io/v1/" +const INDEXSERVER = "https://index.docker.io/v1/" -const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" +//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" var ( ErrConfigFileMissing = errors.New("The Auth config file is missing") diff --git a/utils/tarsum.go b/utils/tarsum.go index 015b9b3076..d3e1db61f1 100644 --- a/utils/tarsum.go +++ b/utils/tarsum.go @@ -145,13 +145,13 @@ func (ts *TarSum) Read(buf []byte) (int, error) { func (ts *TarSum) Sum(extra []byte) string { sort.Strings(ts.sums) h := sha256.New() + if extra != nil { + h.Write(extra) + } for _, sum := range ts.sums { Debugf("-->%s<--", sum) h.Write([]byte(sum)) } - if extra != nil { - h.Write(extra) - } checksum := "tarsum+sha256:" + hex.EncodeToString(h.Sum(nil)) Debugf("checksum processed: %s", checksum) return checksum From 5b27652ac6eaf1bc4c2a16e51919ec4272a58fd6 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 16:44:34 -0700 Subject: [PATCH 045/242] Make sure the index also receives the checksums --- registry/registry.go | 14 +++++++++++++- server.go | 22 ++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 40b9872a4a..4e9dd8895f 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -469,7 +469,19 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token } func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { - imgListJSON, err := json.Marshal(imgList) + cleanImgList := []*ImgData{} + + if validate { + for _, elem := range imgList { + if elem.Checksum != "" { + cleanImgList = append(cleanImgList, elem) + } + } + } else { + cleanImgList = imgList + } + + imgListJSON, err := json.Marshal(cleanImgList) if err != nil { return nil, err } diff --git a/server.go b/server.go index aecc730794..88ff2ac3a6 100644 --- a/server.go +++ b/server.go @@ -645,9 +645,11 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) continue } - if err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { + if checksum, err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { // FIXME: Continue on error? return err + } else { + elem.Checksum = checksum } out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { @@ -663,11 +665,11 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName return nil } -func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) error { +func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) { out = utils.NewWriteFlusher(out) jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgID, "json")) if err != nil { - return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err) + return "", fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err) } out.Write(sf.FormatStatus("Pushing %s", imgID)) @@ -679,29 +681,29 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { if err == registry.ErrAlreadyExists { out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID)) - return nil + return "", nil } - return err + return "", err } layerData, err := srv.runtime.graph.TempLayerArchive(imgID, Uncompressed, sf, out) if err != nil { - return fmt.Errorf("Failed to generate layer archive: %s", err) + return "", fmt.Errorf("Failed to generate layer archive: %s", err) } // Send the layer if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token, jsonRaw); err != nil { - return err + return "", err } else { imgData.Checksum = checksum } // Send the checksum if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil { - return err + return "", err } - return nil + return imgData.Checksum, nil } // FIXME: Allow to interupt current push when new push of same image is done. @@ -739,7 +741,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo var token []string out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName)) - if err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { + if _, err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { return err } return nil From 7d0b8c726c0d178291063751ac735d6caebfb45a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Jul 2013 13:47:29 +0200 Subject: [PATCH 046/242] add ufw doc --- docs/sources/installation/ubuntulinux.rst | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index ed592d3a9d..299b7a29e9 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -19,6 +19,8 @@ Docker has the following dependencies * Linux kernel 3.8 (read more about :ref:`kernel`) * AUFS file system support (we are working on BTRFS support as an alternative) +Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated Firewall) `_ + .. _ubuntu_precise: Ubuntu Precise 12.04 (LTS) (64-bit) @@ -135,3 +137,35 @@ Verify it worked **Done!**, now continue with the :ref:`hello_world` example. + + +.. _ufw: + +Docker and UFW +^^^^^^^^^^^^^^ + +Docker uses a bridge to manage containers networking, by default UFW drop all `forwarding`, a first step is to enable forwarding: + +.. code-block:: bash + + sudo nano /etc/default/ufw + ---- + # Change: + # DEFAULT_FORWARD_POLICY="DROP" + # to + DEFAULT_FORWARD_POLICY="ACCEPT" + +Then reload UFW: + +.. code-block:: bash + + sudo ufw reload + + +UFW's default set of rules denied all `incoming`, so if you want to be able to reach your containers from another host, +you should allow incoming connexions on the docker port (default 4243): + +.. code-block:: bash + + sudo ufw allow 4243/tcp + From 46f59dd9333baa578b184eb25b386ac2f41caf04 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Jul 2013 12:09:07 +0000 Subject: [PATCH 047/242] add parallel pull to 1.4 --- api.go | 2 +- buildfile.go | 2 +- docs/sources/api/docker_remote_api.rst | 4 ++++ runtime_test.go | 2 +- server.go | 24 ++++++++++++++++-------- utils/utils.go | 5 +---- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/api.go b/api.go index 4ad2ba461a..cc7482be71 100644 --- a/api.go +++ b/api.go @@ -386,7 +386,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 diff --git a/buildfile.go b/buildfile.go index 736725e915..fda6e5bf41 100644 --- a/buildfile.go +++ b/buildfile.go @@ -55,7 +55,7 @@ func (b *buildFile) CmdFrom(name string) error { if err != nil { if b.runtime.graph.IsNotExist(err) { remote, tag := utils.ParseRepositoryTag(name) - if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil { + if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil, true); err != nil { return err } image, err = b.runtime.repositories.LookupImage(name) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 193be501d0..dd017cde8d 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -40,6 +40,10 @@ You can still call an old version of the api using What's new ---------- +.. http:post:: /images/create + + **New!** When pull a repo, all images are now downloaded in parallel. + .. http:get:: /containers/(id)/top **New!** You can now use ps args with docker top, like `docker top aux` diff --git a/runtime_test.go b/runtime_test.go index 0b0f62f199..7ecf199e09 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -101,7 +101,7 @@ func init() { // If the unit test is not found, try to download it. if img, err := globalRuntime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { // Retrieve the Image - if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil { + if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil, true); err != nil { panic(err) } } diff --git a/server.go b/server.go index dc08ee5fbd..7417264e50 100644 --- a/server.go +++ b/server.go @@ -446,7 +446,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return nil } -func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter) error { +func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter, parallel bool) error { out.Write(sf.FormatStatus("", "Pulling repository %s", localName)) repoData, err := r.GetRepositoryData(indexEp, remoteName) @@ -492,7 +492,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName errors := make(chan error) for _, image := range repoData.ImgList { - go func(img *registry.ImgData) { + downloadImage := func(img *registry.ImgData) { if askedTag != "" && img.Tag != askedTag { utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID) errors <- nil @@ -518,12 +518,20 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName errors <- fmt.Errorf("Could not find repository on any of the indexed registries.") } errors <- nil - }(image) + } + + if parallel { + go downloadImage(image) + } else { + downloadImage(image) + } } - for i := 0; i < len(repoData.ImgList); i++ { - if err := <-errors; err != nil { - return err + if parallel { + for i := 0; i < len(repoData.ImgList); i++ { + if err := <-errors; err != nil { + return err + } } } @@ -577,7 +585,7 @@ func (srv *Server) poolRemove(kind, key string) error { return nil } -func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { +func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, parallel bool) error { r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionInfos()...) if err != nil { return err @@ -599,7 +607,7 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut } out = utils.NewWriteFlusher(out) - err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf) + err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel) if err != nil { if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil { return err diff --git a/utils/utils.go b/utils/utils.go index 77f701c248..f91fb0663c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -635,7 +635,6 @@ func (jm *JSONMessage) Display(out io.Writer) error { return nil } - func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error { dec := json.NewDecoder(in) jm := JSONMessage{} @@ -670,8 +669,6 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error { return nil } -======= ->>>>>>> master type StreamFormatter struct { json bool used bool @@ -708,7 +705,7 @@ func (sf *StreamFormatter) FormatError(err error) []byte { func (sf *StreamFormatter) FormatProgress(id, action, progress string) []byte { sf.used = true if sf.json { - b, err := json.Marshal(&JSONMessage{Status: action, Progress: progress, ID:id}) + b, err := json.Marshal(&JSONMessage{Status: action, Progress: progress, ID: id}) if err != nil { return nil } From b14c251862021b2bc82aa8e0146e5e5e80f1c713 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Jul 2013 13:13:18 +0000 Subject: [PATCH 048/242] fix tests about refactor checksums --- graph_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph_test.go b/graph_test.go index 18682338d9..2898fccf99 100644 --- a/graph_test.go +++ b/graph_test.go @@ -38,7 +38,7 @@ func TestInterruptedRegister(t *testing.T) { Comment: "testing", Created: time.Now(), } - go graph.Register(badArchive, false, image) + go graph.Register(nil, badArchive, image) time.Sleep(200 * time.Millisecond) w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling) if _, err := graph.Get(image.ID); err == nil { @@ -49,7 +49,7 @@ func TestInterruptedRegister(t *testing.T) { if err != nil { t.Fatal(err) } - if err := graph.Register(goodArchive, false, image); err != nil { + if err := graph.Register(nil, goodArchive, image); err != nil { t.Fatal(err) } } @@ -95,7 +95,7 @@ func TestRegister(t *testing.T) { Comment: "testing", Created: time.Now(), } - err = graph.Register(archive, false, image) + err = graph.Register(nil, archive, image) if err != nil { t.Fatal(err) } @@ -225,7 +225,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } // Test delete twice (pull -> rm -> pull -> rm) - if err := graph.Register(archive, false, img1); err != nil { + if err := graph.Register(nil, archive, img1); err != nil { t.Fatal(err) } if err := graph.Delete(img1.ID); err != nil { From e4752c8c1a09fc3cc96dbb9be7183b271db3d6b7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Jul 2013 16:39:35 +0000 Subject: [PATCH 049/242] Add check that the request is good --- utils/utils.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index 190dc175cd..def5ae5a50 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -736,6 +736,9 @@ func GetReleaseVersion() string { return "" } defer resp.Body.Close() + if resp.ContentLength > 24 || resp.StatusCode != 200 { + return "" + } body, err := ioutil.ReadAll(resp.Body) if err != nil { return "" From e66e0289abc213164dca1e1eadfb0380b6e81904 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Jul 2013 17:18:19 +0000 Subject: [PATCH 050/242] update http://get.docker.io/latest --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6050000582..dd365dc30e 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ release: $(BINRELEASE) s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker + echo $(RELEASE_VERSION) > latest ; s3cmd -P put latest s3://get.docker.io/latest ; rm latest srcrelease: $(SRCRELEASE) deps: $(DOCKER_DIR) @@ -65,7 +66,6 @@ $(BINRELEASE): $(SRCRELEASE) rm -f $(BINRELEASE) cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION) cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest - clean: @rm -rf $(dir $(DOCKER_BIN)) ifeq ($(GOPATH), $(BUILD_DIR)) From 3043c2641990d94298c6377b7ef14709263a4709 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 24 Jul 2013 03:01:24 +0000 Subject: [PATCH 051/242] Return registy status code in error Added Details map to the JSONMessage --- api.go | 12 ++++++++---- commands.go | 29 +++++++++++++++++++---------- registry/registry.go | 24 ++++++++++++------------ utils/error.go | 18 ++++++++++++++++++ utils/utils.go | 26 ++++++++++++++++++-------- 5 files changed, 75 insertions(+), 34 deletions(-) create mode 100644 utils/error.go diff --git a/api.go b/api.go index 4ad2ba461a..5869669df0 100644 --- a/api.go +++ b/api.go @@ -388,7 +388,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht if image != "" { //pull if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil { if sf.Used() { - w.Write(sf.FormatError(err)) + w.Write(sf.FormatError(err, 0)) return nil } return err @@ -396,7 +396,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht } else { //import if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil { if sf.Used() { - w.Write(sf.FormatError(err)) + w.Write(sf.FormatError(err, 0)) return nil } return err @@ -441,7 +441,7 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht imgID, err := srv.ImageInsert(name, url, path, w, sf) if err != nil { if sf.Used() { - w.Write(sf.FormatError(err)) + w.Write(sf.FormatError(err, 0)) return nil } } @@ -472,7 +472,11 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http sf := utils.NewStreamFormatter(version > 1.0) if err := srv.ImagePush(name, w, sf, authConfig); err != nil { if sf.Used() { - w.Write(sf.FormatError(err)) + var code int + if httpErr, ok := err.(*utils.HTTPRequestError); ok { + code = httpErr.StatusCode + } + w.Write(sf.FormatError(err, code)) return nil } return err diff --git a/commands.go b/commands.go index 95ddca1f1d..9ad2c367ae 100644 --- a/commands.go +++ b/commands.go @@ -30,7 +30,8 @@ import ( const VERSION = "0.5.0-dev" var ( - GITCOMMIT string + GITCOMMIT string + AuthRequiredError error = fmt.Errorf("Authentication is required.") ) func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { @@ -814,10 +815,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { return nil } - if err := cli.checkIfLogged("push"); err != nil { - return err - } - // If we're not using a custom registry, we know the restrictions // applied to repository names and can warn the user in advance. // Custom repositories can have different rules, and we must also @@ -826,13 +823,22 @@ func (cli *DockerCli) CmdPush(args ...string) error { return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name) } - buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) - if err != nil { - return err + v := url.Values{} + push := func() error { + buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) + if err != nil { + return err + } + + return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out) } - v := url.Values{} - if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil { + if err := push(); err != nil { + if err == AuthRequiredError { + if err = cli.checkIfLogged("push"); err == nil { + return push() + } + } return err } return nil @@ -1559,6 +1565,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e } else if err != nil { return err } + if jm.Error != nil && jm.Error.Code == 401 { + return AuthRequiredError + } jm.Display(out) } } else { diff --git a/registry/registry.go b/registry/registry.go index 4e9dd8895f..ed6f4c7df8 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -147,7 +147,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s res, err := doWithCookies(r.client, req) if err != nil || res.StatusCode != 200 { if res != nil { - return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID) + return nil, utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) } return nil, err } @@ -197,7 +197,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ } defer res.Body.Close() if res.StatusCode != 200 { - return nil, -1, fmt.Errorf("HTTP code %d", res.StatusCode) + return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } imageSize, err := strconv.Atoi(res.Header.Get("X-Docker-Size")) @@ -289,12 +289,12 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e } defer res.Body.Close() if res.StatusCode == 401 { - return nil, fmt.Errorf("Please login first (HTTP code %d)", res.StatusCode) + return nil, utils.NewHTTPRequestError(fmt.Sprintf("Please login first (HTTP code %d)", res.StatusCode), res) } // TODO: Right now we're ignoring checksums in the response body. // In the future, we need to use them to check image validity. if res.StatusCode != 200 { - return nil, fmt.Errorf("HTTP code: %d", res.StatusCode) + return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) } var tokens []string @@ -391,7 +391,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) + return utils.NewHTTPRequestError(fmt.Sprint("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } var jsonBody map[string]string if err := json.Unmarshal(errBody, &jsonBody); err != nil { @@ -399,7 +399,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis } else if jsonBody["error"] == "Image already exists" { return ErrAlreadyExists } - return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody) + return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody), res) } return nil } @@ -427,9 +427,9 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return "", fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) + return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } - return "", fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody) + return utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody), res) } return tarsumLayer.Sum(jsonRaw), nil } @@ -463,7 +463,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token } res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 201 { - return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote) + return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res) } return nil } @@ -540,7 +540,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData if err != nil { return nil, err } - return nil, fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody) + return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody), res) } if res.Header.Get("X-Docker-Token") != "" { tokens = res.Header["X-Docker-Token"] @@ -564,7 +564,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData if err != nil { return nil, err } - return nil, fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody) + return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody), res) } } @@ -586,7 +586,7 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { } defer res.Body.Close() if res.StatusCode != 200 { - return nil, fmt.Errorf("Unexepected status code %d", res.StatusCode) + return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexepected status code %d", res.StatusCode), res) } rawData, err := ioutil.ReadAll(res.Body) if err != nil { diff --git a/utils/error.go b/utils/error.go new file mode 100644 index 0000000000..7e3c846ebc --- /dev/null +++ b/utils/error.go @@ -0,0 +1,18 @@ +package utils + +import ( + "net/http" +) + +type HTTPRequestError struct { + Message string + StatusCode int +} + +func (e *HTTPRequestError) Error() string { + return e.Message +} + +func NewHTTPRequestError(msg string, resp *http.Response) error { + return &HTTPRequestError{Message: msg, StatusCode: resp.StatusCode} +} diff --git a/utils/utils.go b/utils/utils.go index c70e80b72e..659d960d0a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -607,12 +607,22 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher { return &WriteFlusher{w: w, flusher: flusher} } +type JSONError struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + type JSONMessage struct { - Status string `json:"status,omitempty"` - Progress string `json:"progress,omitempty"` - Error string `json:"error,omitempty"` - ID string `json:"id,omitempty"` - Time int64 `json:"time,omitempty"` + Status string `json:"status,omitempty"` + Progress string `json:"progress,omitempty"` + ErrorMessage string `json:"error,omitempty"` //deprecated + ID string `json:"id,omitempty"` + Time int64 `json:"time,omitempty"` + Error *JSONError `json:"errorDetail,omitempty"` +} + +func (e *JSONError) Error() string { + return e.Message } func (jm *JSONMessage) Display(out io.Writer) error { @@ -621,8 +631,8 @@ func (jm *JSONMessage) Display(out io.Writer) error { } if jm.Progress != "" { fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) - } else if jm.Error != "" { - return fmt.Errorf(jm.Error) + } else if jm.Error != nil { + return jm.Error } else if jm.ID != "" { fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status) } else { @@ -656,7 +666,7 @@ func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte func (sf *StreamFormatter) FormatError(err error) []byte { sf.used = true if sf.json { - if b, err := json.Marshal(&JSONMessage{Error: err.Error()}); err == nil { + if b, err := json.Marshal(&JSONMessage{Error: &JSONError{Code: code, Message: err.Error()}, ErrorMessage: err.Error()}); err == nil { return b } return []byte("{\"error\":\"format error\"}") From 73c6d9f135493220f034d440d26fedc0242e133a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 31 Jul 2013 07:56:53 +0000 Subject: [PATCH 052/242] improve tests --- server_test.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/server_test.go b/server_test.go index 0caf8a5f24..de95d743ba 100644 --- a/server_test.go +++ b/server_test.go @@ -20,7 +20,11 @@ func TestContainerTagImageDelete(t *testing.T) { if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil { t.Fatal(err) } - if err := srv.runtime.repositories.Set("utest:5000/docker", "tag2", unitTestImageName, false); err != nil { + + if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil { + t.Fatal(err) + } + if err := srv.runtime.repositories.Set("utest:5000/docker", "tag3", unitTestImageName, false); err != nil { t.Fatal(err) } @@ -29,11 +33,24 @@ func TestContainerTagImageDelete(t *testing.T) { t.Fatal(err) } + if len(images) != len(initialImages)+3 { + t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images)) + } + + if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil { + t.Fatal(err) + } + + images, err = srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + if len(images) != len(initialImages)+2 { t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images)) } - if _, err := srv.ImageDelete("utest:5000/docker:tag2", true); err != nil { + if _, err := srv.ImageDelete("utest:5000/docker:tag3", true); err != nil { t.Fatal(err) } From a7068510a5ee4af6776221ba00bc332266f97088 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 31 Jul 2013 08:01:20 +0000 Subject: [PATCH 053/242] fix same issue in api.go --- api.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/api.go b/api.go index 834c41a68c..d0a8ada249 100644 --- a/api.go +++ b/api.go @@ -786,12 +786,7 @@ 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] - } + repoName, tag := utils.ParseRepositoryTag(repoName) var context io.Reader From 9a604acc23d30d00ae907acdf756cbcdf0e4cf83 Mon Sep 17 00:00:00 2001 From: Nolan Date: Tue, 30 Jul 2013 13:23:34 -0500 Subject: [PATCH 054/242] Add hostname to the container environment. --- container.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container.go b/container.go index d610c3c7d4..ccc7ab3e9f 100644 --- a/container.go +++ b/container.go @@ -652,6 +652,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { "-e", "HOME=/", "-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "-e", "container=lxc", + "-e", "HOSTNAME="+container.Config.Hostname, ) for _, elem := range container.Config.Env { From e0c24ccfc37b64eb919c5675d8a8f383201fa7cb Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Wed, 31 Jul 2013 12:17:42 -0700 Subject: [PATCH 055/242] Solved the logo being squished in Safari --- docs/theme/docker/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index 0b22f22fab..ca26f44dc0 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -79,7 +79,7 @@
- +
From 26229d78f2b77592ab6761a4ffce38ab7d859f3b Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Wed, 31 Jul 2013 13:44:10 -0700 Subject: [PATCH 056/242] Updated docs README with instructions to preview the generated manfile, and where to get the one generated by sphinx. --- docs/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 32be549ef5..7d7b67ca04 100644 --- a/docs/README.md +++ b/docs/README.md @@ -74,5 +74,12 @@ Guides on using sphinx Start without $, so it's easy to copy and paste. +Manpages +-------- + * To make the manpages, simply run 'make man'. Pleae note there is a bug in spinx 1.1.3 which makes this fail. -Upgrade to the latest version of sphinx. \ No newline at end of file +Upgrade to the latest version of sphinx. +* Then preview the manpage by running `man _build/man/docker.1`, where _build/man/docker.1 is the path to the generated +manfile + +The manpages are also autogenerated by our hosted readthedocs here: http://docs-docker.dotcloud.com/projects/docker/downloads/ \ No newline at end of file From ad3b091d535cde5544ec3c11da4cf8d9b278a325 Mon Sep 17 00:00:00 2001 From: Thatcher Date: Wed, 31 Jul 2013 13:59:56 -0700 Subject: [PATCH 057/242] Some more improvements on the docs readme. Removed references to website. --- docs/README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/docs/README.md b/docs/README.md index 7d7b67ca04..1a40b5bb5c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,14 +1,12 @@ -Docker documentation and website -================================ +Docker Documentation +==================== Documentation ------------- -This is your definite place to contribute to the docker documentation. The documentation is generated from the -.rst files under sources. - -The folder also contains the other files to create the http://docker.io website, but you can generally ignore -most of those. +This is your definite place to contribute to the docker documentation. After each push to master the documentation +is automatically generated and made available on [docs.docker.io](http://docs.docker.io) +Each of the .rst files under sources reflects a page on the documentation. Installation ------------ @@ -36,13 +34,11 @@ Images ------ When you need to add images, try to make them as small as possible (e.g. as gif). - Notes ----- * For the template the css is compiled from less. When changes are needed they can be compiled using lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css`` - Guides on using sphinx ---------------------- * To make links to certain pages create a link target like so: @@ -77,9 +73,8 @@ Guides on using sphinx Manpages -------- -* To make the manpages, simply run 'make man'. Pleae note there is a bug in spinx 1.1.3 which makes this fail. +* To make the manpages, simply run 'make man'. Please note there is a bug in spinx 1.1.3 which makes this fail. Upgrade to the latest version of sphinx. * Then preview the manpage by running `man _build/man/docker.1`, where _build/man/docker.1 is the path to the generated manfile - -The manpages are also autogenerated by our hosted readthedocs here: http://docs-docker.dotcloud.com/projects/docker/downloads/ \ No newline at end of file +* The manpages are also autogenerated by our hosted readthedocs here: http://docs-docker.dotcloud.com/projects/docker/downloads/ From 2e72882216ce13169a578614202830a5b084bfb4 Mon Sep 17 00:00:00 2001 From: Steeve Morin Date: Thu, 1 Aug 2013 02:42:22 +0200 Subject: [PATCH 058/242] Handle ip route showing mask-less IP addresses Sometimes `ip route` will show mask-less IPs, so net.ParseCIDR will fail. If it does we check if we can net.ParseIP, and fail only if we can't. Fixes #1214 Fixes #362 --- network.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/network.go b/network.go index 2e2dc7785c..daffbfcde4 100644 --- a/network.go +++ b/network.go @@ -104,7 +104,11 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error { continue } if _, network, err := net.ParseCIDR(strings.Split(line, " ")[0]); err != nil { - return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line) + // is this a mask-less IP address? + if ip := net.ParseIP(strings.Split(line, " ")[0]); ip == nil { + // fail only if it's neither a network nor a mask-less IP address + return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line) + } } else if networkOverlaps(dockerNetwork, network) { return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork.String(), line) } From 2e7df5182cc94e3699ebc1031e89b1605f9d42f9 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Mon, 29 Jul 2013 09:45:19 -0700 Subject: [PATCH 059/242] testing, issue #1331: Add registry functional test to docker-ci --- testing/README.rst | 4 ++++ testing/Vagrantfile | 4 +++- testing/buildbot/credentials.cfg | 5 +++++ testing/buildbot/master.cfg | 20 ++++++++++++++++++-- testing/buildbot/requirements.txt | 1 + testing/buildbot/setup_credentials.sh | 17 +++++++++++++++++ testing/functionaltests/test_registry.sh | 11 +++++++++++ 7 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 testing/buildbot/credentials.cfg create mode 100755 testing/buildbot/setup_credentials.sh create mode 100755 testing/functionaltests/test_registry.sh diff --git a/testing/README.rst b/testing/README.rst index 3b11092f9f..ce5aa837a4 100644 --- a/testing/README.rst +++ b/testing/README.rst @@ -40,6 +40,10 @@ Deployment export SMTP_USER=xxxxxxxxxxxx export SMTP_PWD=xxxxxxxxxxxx + # Define docker registry functional test credentials + export REGISTRY_USER=xxxxxxxxxxxx + export REGISTRY_PWD=xxxxxxxxxxxx + # Checkout docker git clone git://github.com/dotcloud/docker.git diff --git a/testing/Vagrantfile b/testing/Vagrantfile index 47257201dc..e76a951508 100644 --- a/testing/Vagrantfile +++ b/testing/Vagrantfile @@ -29,7 +29,9 @@ Vagrant::Config.run do |config| "chown #{USER}.#{USER} /data; cd /data; " \ "#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH} #{ENV['BUILDBOT_PWD']} " \ "#{ENV['IRC_PWD']} #{ENV['IRC_CHANNEL']} #{ENV['SMTP_USER']} " \ - "#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; " + "#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; " \ + "#{CFG_PATH}/setup_credentials.sh #{USER} " \ + "#{ENV['REGISTRY_USER']} #{ENV['REGISTRY_PWD']}; " # Install docker dependencies pkg_cmd << "apt-get install -q -y python-software-properties; " \ "add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \ diff --git a/testing/buildbot/credentials.cfg b/testing/buildbot/credentials.cfg new file mode 100644 index 0000000000..fbdd35d578 --- /dev/null +++ b/testing/buildbot/credentials.cfg @@ -0,0 +1,5 @@ +# Credentials for tests. Buildbot source this file on tests +# when needed. + +# Docker registry credentials. Format: 'username:password' +export DOCKER_CREDS='' diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index 61912808ec..29926dbe5f 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -19,6 +19,7 @@ TEST_USER = 'buildbot' # Credential to authenticate build triggers TEST_PWD = 'docker' # Credential to authenticate build triggers BUILDER_NAME = 'docker' GITHUB_DOCKER = 'github.com/dotcloud/docker' +BUILDBOT_PATH = '/data/buildbot' DOCKER_PATH = '/data/docker' BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME) DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker' @@ -41,16 +42,19 @@ c['db'] = {'db_url':"sqlite:///state.sqlite"} c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)] c['slavePortnum'] = PORT_MASTER + # Schedulers c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[BUILDER_NAME, - 'coverage'])] + 'registry','coverage'])] c['schedulers'] += [SingleBranchScheduler(name="all", change_filter=filter.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=[BUILDER_NAME])] -c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage'], +c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage','registry'], hour=0, minute=30)] + # Builders +# Docker commit test factory = BuildFactory() factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; " @@ -58,6 +62,7 @@ factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, "go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))])) c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] + # Docker coverage test coverage_cmd = ('GOPATH=`pwd` go get -d github.com/dotcloud/docker\n' 'GOPATH=`pwd` go get github.com/axw/gocov/gocov\n' @@ -69,6 +74,17 @@ factory.addStep(ShellCommand(description='Coverage',logEnviron=False,usePTY=True c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'], factory=factory)] +# Registry Functionaltest builder +factory = BuildFactory() +factory.addStep(ShellCommand(description='registry', logEnviron=False, + command='. {0}/master/credentials.cfg; ' + '{1}/testing/functionaltests/test_registry.sh'.format(BUILDBOT_PATH, + DOCKER_PATH), usePTY=True)) + +c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'], + factory=factory)] + + # Status authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]), forceBuild='auth') diff --git a/testing/buildbot/requirements.txt b/testing/buildbot/requirements.txt index 0e451b017d..4e183ba062 100644 --- a/testing/buildbot/requirements.txt +++ b/testing/buildbot/requirements.txt @@ -4,3 +4,4 @@ buildbot==0.8.7p1 buildbot_slave==0.8.7p1 nose==1.2.1 requests==1.1.0 +flask==0.10.1 diff --git a/testing/buildbot/setup_credentials.sh b/testing/buildbot/setup_credentials.sh new file mode 100755 index 0000000000..f093815d60 --- /dev/null +++ b/testing/buildbot/setup_credentials.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Setup of test credentials. Called by Vagrantfile +export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin" + +USER=$1 +REGISTRY_USER=$2 +REGISTRY_PWD=$3 + +BUILDBOT_PATH="/data/buildbot" +DOCKER_PATH="/data/docker" + +function run { su $USER -c "$1"; } + +run "cp $DOCKER_PATH/testing/buildbot/credentials.cfg $BUILDBOT_PATH/master" +cd $BUILDBOT_PATH/master +run "sed -i -E 's#(export DOCKER_CREDS=).+#\1\"$REGISTRY_USER:$REGISTRY_PWD\"#' credentials.cfg" diff --git a/testing/functionaltests/test_registry.sh b/testing/functionaltests/test_registry.sh new file mode 100755 index 0000000000..095a731631 --- /dev/null +++ b/testing/functionaltests/test_registry.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# Cleanup +rm -rf docker-registry + +# Get latest docker registry +git clone https://github.com/dotcloud/docker-registry.git + +# Configure and run registry tests +cd docker-registry; cp config_sample.yml config.yml +cd test; python -m unittest workflow From f5a8e90d101cd2dbb4ce19543ed15fff48579877 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 1 Aug 2013 18:12:39 -0700 Subject: [PATCH 060/242] Make sure the routes IP are taken into consideration + add unit test for network overlap detection --- network.go | 31 ++++++++++++++++++++----------- network_test.go | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/network.go b/network.go index daffbfcde4..eefd36df3b 100644 --- a/network.go +++ b/network.go @@ -93,24 +93,29 @@ func iptables(args ...string) error { return nil } -func checkRouteOverlaps(dockerNetwork *net.IPNet) error { - output, err := ip("route") - if err != nil { - return err - } - utils.Debugf("Routes:\n\n%s", output) - for _, line := range strings.Split(output, "\n") { +func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error { + utils.Debugf("Routes:\n\n%s", routes) + for _, line := range strings.Split(routes, "\n") { if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") { continue } - if _, network, err := net.ParseCIDR(strings.Split(line, " ")[0]); err != nil { + _, network, err := net.ParseCIDR(strings.Split(line, " ")[0]) + if err != nil { // is this a mask-less IP address? if ip := net.ParseIP(strings.Split(line, " ")[0]); ip == nil { // fail only if it's neither a network nor a mask-less IP address return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line) + } else { + _, network, err = net.ParseCIDR(ip.String() + "/32") + if err != nil { + return err + } + } + } + if err == nil && network != nil { + if networkOverlaps(dockerNetwork, network) { + return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, line) } - } else if networkOverlaps(dockerNetwork, network) { - return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork.String(), line) } } return nil @@ -146,7 +151,11 @@ func CreateBridgeIface(ifaceName string) error { if err != nil { return err } - if err := checkRouteOverlaps(dockerNetwork); err == nil { + routes, err := ip("route") + if err != nil { + return err + } + if err := checkRouteOverlaps(routes, dockerNetwork); err == nil { ifaceAddr = addr break } else { diff --git a/network_test.go b/network_test.go index 8e6eaad773..bd3a16a1be 100644 --- a/network_test.go +++ b/network_test.go @@ -383,3 +383,22 @@ func TestNetworkOverlaps(t *testing.T) { //netX starts and ends before netY AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t) } + +func TestCheckRouteOverlaps(t *testing.T) { + routes := `default via 10.0.2.2 dev eth0 +10.0.2.0 dev eth0 proto kernel scope link src 10.0.2.15 +10.0.3.0/24 dev lxcbr0 proto kernel scope link src 10.0.3.1 +10.0.42.0/24 dev testdockbr0 proto kernel scope link src 10.0.42.1 +172.16.42.0/24 dev docker0 proto kernel scope link src 172.16.42.1 +192.168.142.0/24 dev eth1 proto kernel scope link src 192.168.142.142` + + _, netX, _ := net.ParseCIDR("172.16.0.1/24") + if err := checkRouteOverlaps(routes, netX); err != nil { + t.Fatal(err) + } + + _, netX, _ = net.ParseCIDR("10.0.2.0/24") + if err := checkRouteOverlaps(routes, netX); err == nil { + t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't") + } +} From 2424480e2ce14d848c041c53cc041aab91308081 Mon Sep 17 00:00:00 2001 From: Tobias Schmidt Date: Fri, 2 Aug 2013 12:24:38 +0700 Subject: [PATCH 061/242] Move note about officially supported kernel It seems this a general note about kernel issues and not specific to Cgroups or namespaces. --- docs/sources/installation/kernel.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/sources/installation/kernel.rst b/docs/sources/installation/kernel.rst index 58730f8191..7c5715a62d 100644 --- a/docs/sources/installation/kernel.rst +++ b/docs/sources/installation/kernel.rst @@ -15,12 +15,11 @@ In short, Docker has the following kernel requirements: - Cgroups and namespaces must be enabled. - - The officially supported kernel is the one recommended by the - :ref:`ubuntu_linux` installation path. It is the one that most developers - will use, and the one that receives the most attention from the core - contributors. If you decide to go with a different kernel and hit a bug, - please try to reproduce it with the official kernels first. +The officially supported kernel is the one recommended by the +:ref:`ubuntu_linux` installation path. It is the one that most developers +will use, and the one that receives the most attention from the core +contributors. If you decide to go with a different kernel and hit a bug, +please try to reproduce it with the official kernels first. If you cannot or do not want to use the "official" kernels, here is some technical background about the features (both optional and From 793fd983ef937d2bea1edf5d8d855e2a452a4aa7 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 2 Aug 2013 02:47:58 -0400 Subject: [PATCH 062/242] http utils --- utils/http.go | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 utils/http.go diff --git a/utils/http.go b/utils/http.go new file mode 100644 index 0000000000..61fdbff04e --- /dev/null +++ b/utils/http.go @@ -0,0 +1,129 @@ +package utils + +import ( + "bytes" + "io" + "net/http" + "strings" +) + +// VersionInfo is used to model entities which has a version. +// It is basically a tupple with name and version. +type VersionInfo interface { + Name() string + Version() string +} + +func validVersion(version VersionInfo) bool { + stopChars := " \t\r\n/" + if strings.ContainsAny(version.Name(), stopChars) { + return false + } + if strings.ContainsAny(version.Version(), stopChars) { + return false + } + return true +} + +// Convert versions to a string and append the string to the string base. +// +// Each VersionInfo will be converted to a string in the format of +// "product/version", where the "product" is get from the Name() method, while +// version is get from the Version() method. Several pieces of verson information +// will be concatinated and separated by space. +func appendVersions(base string, versions ...VersionInfo) string { + if len(versions) == 0 { + return base + } + + var buf bytes.Buffer + if len(base) > 0 { + buf.Write([]byte(base)) + } + + for _, v := range versions { + name := []byte(v.Name()) + version := []byte(v.Version()) + + if len(name) == 0 || len(version) == 0 { + continue + } + if !validVersion(v) { + continue + } + buf.Write([]byte(v.Name())) + buf.Write([]byte("/")) + buf.Write([]byte(v.Version())) + buf.Write([]byte(" ")) + } + return buf.String() +} + +// HTTPRequestDecorator is used to change an instance of +// http.Request. It could be used to add more header fields, +// change body, etc. +type HTTPRequestDecorator interface { + // ChangeRequest() changes the request accordingly. + // The changed request will be returned or err will be non-nil + // if an error occur. + ChangeRequest(req *http.Request) (newReq *http.Request, err error) +} + +// HTTPUserAgentDecorator appends the product/version to the user agent field +// of a request. +type HTTPUserAgentDecorator struct { + versions []VersionInfo +} + +func NewHTTPUserAgentDecorator(versions ...VersionInfo) HTTPRequestDecorator { + ret := new(HTTPUserAgentDecorator) + ret.versions = versions + return ret +} + +func (self *HTTPUserAgentDecorator) ChangeRequest(req *http.Request) (newReq *http.Request, err error) { + if req == nil { + return req, nil + } + + userAgent := appendVersions(req.UserAgent(), self.versions...) + if len(userAgent) > 0 { + req.Header.Set("User-Agent", userAgent) + } + return req, nil +} + +// HTTPRequestFactory creates an HTTP request +// and applies a list of decorators on the request. +type HTTPRequestFactory struct { + decorators []HTTPRequestDecorator +} + +func NewHTTPRequestFactory(d ...HTTPRequestDecorator) *HTTPRequestFactory { + ret := new(HTTPRequestFactory) + ret.decorators = d + return ret +} + +// NewRequest() creates a new *http.Request, +// applies all decorators in the HTTPRequestFactory on the request, +// then applies decorators provided by d on the request. +func (self *HTTPRequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...HTTPRequestDecorator) (*http.Request, error) { + req, err := http.NewRequest(method, urlStr, body) + if err != nil { + return nil, err + } + for _, dec := range self.decorators { + req, err = dec.ChangeRequest(req) + if err != nil { + return nil, err + } + } + for _, dec := range d { + req, err = dec.ChangeRequest(req) + if err != nil { + return nil, err + } + } + return req, err +} From 7dac26ce69b442d55122caa2897572d3ac8255fa Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 2 Aug 2013 03:08:08 -0400 Subject: [PATCH 063/242] reqFactory in Registry --- registry/registry.go | 101 +++++++------------------------------------ 1 file changed, 15 insertions(+), 86 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 4e9dd8895f..da5c83bff1 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -100,13 +100,6 @@ func ResolveRepositoryName(reposName string) (string, string, error) { return endpoint, reposName, err } -// VersionInfo is used to model entities which has a version. -// It is basically a tupple with name and version. -type VersionInfo interface { - Name() string - Version() string -} - func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { for _, cookie := range c.Jar.Cookies(req.URL) { req.AddCookie(cookie) @@ -121,29 +114,14 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { return res, err } -// Set the user agent field in the header based on the versions provided -// in NewRegistry() and extra. -func (r *Registry) setUserAgent(req *http.Request, extra ...VersionInfo) { - if len(r.baseVersions)+len(extra) == 0 { - return - } - if len(extra) == 0 { - req.Header.Set("User-Agent", r.baseVersionsStr) - } else { - req.Header.Set("User-Agent", appendVersions(r.baseVersionsStr, extra...)) - } - return -} - // Retrieve the history of a given image from the Registry. // Return a list of the parent's json (requested image included) func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) { - req, err := http.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil) + req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil || res.StatusCode != 200 { if res != nil { @@ -170,7 +148,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool { rt := &http.Transport{Proxy: http.ProxyFromEnvironment} - req, err := http.NewRequest("GET", registry+"images/"+imgID+"/json", nil) + req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil) if err != nil { return false } @@ -185,12 +163,11 @@ func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) boo // Retrieve an image from the Registry. func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) { // Get the JSON - req, err := http.NewRequest("GET", registry+"images/"+imgID+"/json", nil) + req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) @@ -213,12 +190,11 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ } func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) { - req, err := http.NewRequest("GET", registry+"images/"+imgID+"/layer", nil) + req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/layer", nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { return nil, err @@ -239,7 +215,6 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, err } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { return nil, err @@ -281,7 +256,6 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) } req.Header.Set("X-Docker-Token", "true") - r.setUserAgent(req) res, err := r.client.Do(req) if err != nil { @@ -339,7 +313,7 @@ func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum") - req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil) + req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil) if err != nil { return err } @@ -375,13 +349,12 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json") - req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw)) + req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw)) if err != nil { return err } req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) - r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { @@ -410,14 +383,13 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr tarsumLayer := &utils.TarSum{Reader: layer} - req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) + req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) if err != nil { return "", err } req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) - r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { return "", fmt.Errorf("Failed to upload layer: %s", err) @@ -435,7 +407,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr } func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) { - req, err := http.NewRequest(method, urlStr, body) + req, err := r.reqFactory.NewRequest(method, urlStr, body) if err != nil { return nil, err } @@ -455,7 +427,6 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token } req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) - r.setUserAgent(req) req.ContentLength = int64(len(revision)) res, err := doWithCookies(r.client, req) if err != nil { @@ -500,7 +471,6 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.ContentLength = int64(len(imgListJSON)) req.Header.Set("X-Docker-Token", "true") - r.setUserAgent(req) if validate { req.Header["X-Docker-Endpoints"] = regs } @@ -521,7 +491,6 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.ContentLength = int64(len(imgListJSON)) req.Header.Set("X-Docker-Token", "true") - r.setUserAgent(req) if validate { req.Header["X-Docker-Endpoints"] = regs } @@ -576,7 +545,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term) - req, err := http.NewRequest("GET", u, nil) + req, err := r.reqFactory.NewRequest("GET", u, nil) if err != nil { return nil, err } @@ -628,52 +597,12 @@ type ImgData struct { } type Registry struct { - client *http.Client - authConfig *auth.AuthConfig - baseVersions []VersionInfo - baseVersionsStr string + client *http.Client + authConfig *auth.AuthConfig + reqFactory *utils.HTTPRequestFactory } -func validVersion(version VersionInfo) bool { - stopChars := " \t\r\n/" - if strings.ContainsAny(version.Name(), stopChars) { - return false - } - if strings.ContainsAny(version.Version(), stopChars) { - return false - } - return true -} - -// Convert versions to a string and append the string to the string base. -// -// Each VersionInfo will be converted to a string in the format of -// "product/version", where the "product" is get from the Name() method, while -// version is get from the Version() method. Several pieces of verson information -// will be concatinated and separated by space. -func appendVersions(base string, versions ...VersionInfo) string { - if len(versions) == 0 { - return base - } - - var buf bytes.Buffer - if len(base) > 0 { - buf.Write([]byte(base)) - } - - for _, v := range versions { - if !validVersion(v) { - continue - } - buf.Write([]byte(v.Name())) - buf.Write([]byte("/")) - buf.Write([]byte(v.Version())) - buf.Write([]byte(" ")) - } - return buf.String() -} - -func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...VersionInfo) (r *Registry, err error) { +func NewRegistry(root string, authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory) (r *Registry, err error) { httpTransport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, @@ -689,7 +618,7 @@ func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...Versi if err != nil { return nil, err } - r.baseVersions = baseVersions - r.baseVersionsStr = appendVersions("", baseVersions...) + + r.reqFactory = factory return r, nil } From 6a56b7b391ac967540915c2ee8f82b23714ad84c Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 2 Aug 2013 03:23:46 -0400 Subject: [PATCH 064/242] Server now use request factory --- server.go | 15 ++++++++++----- utils/http.go | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/server.go b/server.go index cb7b2cf1be..b015e459f3 100644 --- a/server.go +++ b/server.go @@ -52,9 +52,9 @@ func (v *simpleVersionInfo) Version() string { // docker, go, git-commit (of the docker) and the host's kernel. // // Such information will be used on call to NewRegistry(). -func (srv *Server) versionInfos() []registry.VersionInfo { +func (srv *Server) versionInfos() []utils.VersionInfo { v := srv.DockerVersion() - ret := make([]registry.VersionInfo, 0, 4) + ret := make([]utils.VersionInfo, 0, 4) ret = append(ret, &simpleVersionInfo{"docker", v.Version}) if len(v.GoVersion) > 0 { @@ -102,7 +102,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { } func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { - r, err := registry.NewRegistry(srv.runtime.root, nil, srv.versionInfos()...) + r, err := registry.NewRegistry(srv.runtime.root, nil, srv.reqFactory) if err != nil { return nil, err } @@ -559,7 +559,7 @@ func (srv *Server) poolRemove(kind, key string) error { } func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionInfos()...) + r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.reqFactory) if err != nil { return err } @@ -720,7 +720,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) - r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionInfos()...) + r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.reqFactory) if err2 != nil { return err2 } @@ -1164,7 +1164,11 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( pushingPool: make(map[string]struct{}), events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events listeners: make(map[string]chan utils.JSONMessage), + reqFactory: nil, } + ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...) + factory := utils.NewHTTPRequestFactory(ud) + srv.reqFactory = factory runtime.srv = srv return srv, nil } @@ -1189,4 +1193,5 @@ type Server struct { pushingPool map[string]struct{} events []utils.JSONMessage listeners map[string]chan utils.JSONMessage + reqFactory *utils.HTTPRequestFactory } diff --git a/utils/http.go b/utils/http.go index 61fdbff04e..8c1e4b7a79 100644 --- a/utils/http.go +++ b/utils/http.go @@ -113,6 +113,11 @@ func (self *HTTPRequestFactory) NewRequest(method, urlStr string, body io.Reader if err != nil { return nil, err } + + // By default, a nil factory should work. + if self == nil { + return req, nil + } for _, dec := range self.decorators { req, err = dec.ChangeRequest(req) if err != nil { From 4bd287e107eab1623a0e77aeaecda77fc26e7536 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 2 Aug 2013 03:30:45 -0400 Subject: [PATCH 065/242] auth with user agent --- api.go | 2 +- auth/auth.go | 5 +++-- server.go | 9 +++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index b8b7897c32..77faaaf23c 100644 --- a/api.go +++ b/api.go @@ -87,7 +87,7 @@ func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reque if err != nil { return err } - status, err := auth.Login(authConfig) + status, err := auth.Login(authConfig, srv.HTTPRequestFactory()) if err != nil { return err } diff --git a/auth/auth.go b/auth/auth.go index 6dd6ceb620..b9e1ee153b 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dotcloud/docker/utils" "io/ioutil" "net/http" "os" @@ -140,7 +141,7 @@ func SaveConfig(configFile *ConfigFile) error { } // try to register/login to the registry server -func Login(authConfig *AuthConfig) (string, error) { +func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { client := &http.Client{} reqStatusCode := 0 var status string @@ -171,7 +172,7 @@ func Login(authConfig *AuthConfig) (string, error) { "Please check your e-mail for a confirmation link.") } else if reqStatusCode == 400 { if string(reqBody) == "\"Username or email already exists\"" { - req, err := http.NewRequest("GET", IndexServerAddress()+"users/", nil) + req, err := factory.NewRequest("GET", IndexServerAddress()+"users/", nil) req.SetBasicAuth(authConfig.Username, authConfig.Password) resp, err := client.Do(req) if err != nil { diff --git a/server.go b/server.go index b015e459f3..fa6f19ad4b 100644 --- a/server.go +++ b/server.go @@ -1173,6 +1173,15 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( return srv, nil } +func (srv *Server) HTTPRequestFactory() *utils.HTTPRequestFactory { + if srv.reqFactory == nil { + ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...) + factory := utils.NewHTTPRequestFactory(ud) + srv.reqFactory = factory + } + return srv.reqFactory +} + func (srv *Server) LogEvent(action, id string) { now := time.Now().Unix() jm := utils.JSONMessage{Status: action, ID: id, Time: now} From 5bc344ab73f73a1fb5bea0933a031ad0418cb8f8 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 2 Aug 2013 04:10:26 -0400 Subject: [PATCH 066/242] factory generated from one place. --- server.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server.go b/server.go index fa6f19ad4b..d77bcdb59e 100644 --- a/server.go +++ b/server.go @@ -102,7 +102,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { } func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { - r, err := registry.NewRegistry(srv.runtime.root, nil, srv.reqFactory) + r, err := registry.NewRegistry(srv.runtime.root, nil, srv.HTTPRequestFactory()) if err != nil { return nil, err } @@ -559,7 +559,7 @@ func (srv *Server) poolRemove(kind, key string) error { } func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.reqFactory) + r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory()) if err != nil { return err } @@ -720,7 +720,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) - r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.reqFactory) + r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory()) if err2 != nil { return err2 } @@ -1166,9 +1166,6 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( listeners: make(map[string]chan utils.JSONMessage), reqFactory: nil, } - ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...) - factory := utils.NewHTTPRequestFactory(ud) - srv.reqFactory = factory runtime.srv = srv return srv, nil } From 3a123bc479457c4dfa14e39b7c42d9a9dccf8c32 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 2 Aug 2013 16:18:54 +0000 Subject: [PATCH 067/242] Add no cache for docker build Add a new flag to disable the image cache when building images. --- api.go | 7 ++- buildfile.go | 55 ++++++++++++--------- buildfile_test.go | 2 +- commands.go | 4 ++ docs/sources/api/docker_remote_api_v1.4.rst | 1 + docs/sources/commandline/command/build.rst | 1 + 6 files changed, 44 insertions(+), 26 deletions(-) diff --git a/api.go b/api.go index b8b7897c32..95b9c98de8 100644 --- a/api.go +++ b/api.go @@ -793,6 +793,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ remoteURL := r.FormValue("remote") repoName := r.FormValue("t") rawSuppressOutput := r.FormValue("q") + rawNoCache := r.FormValue("nocache") repoName, tag := utils.ParseRepositoryTag(repoName) var context io.Reader @@ -839,8 +840,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) diff --git a/buildfile.go b/buildfile.go index 736725e915..159d7ba704 100644 --- a/buildfile.go +++ b/buildfile.go @@ -26,11 +26,12 @@ type buildFile struct { builder *Builder srv *Server - image string - maintainer string - config *Config - context string - verbose bool + image string + maintainer string + config *Config + context string + verbose bool + utilizeCache bool tmpContainers map[string]struct{} tmpImages map[string]struct{} @@ -94,15 +95,17 @@ func (b *buildFile) CmdRun(args string) error { utils.Debugf("Command to be executed: %v", b.config.Cmd) - if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { - return err - } else if cache != nil { - fmt.Fprintf(b.out, " ---> Using cache\n") - utils.Debugf("[BUILDER] Use cached version") - b.image = cache.ID - return nil - } else { - utils.Debugf("[BUILDER] Cache miss") + if b.utilizeCache { + if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { + return err + } else if cache != nil { + fmt.Fprintf(b.out, " ---> Using cache\n") + utils.Debugf("[BUILDER] Use cached version") + b.image = cache.ID + return nil + } else { + utils.Debugf("[BUILDER] Cache miss") + } } cid, err := b.run() @@ -397,16 +400,19 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} defer func(cmd []string) { b.config.Cmd = cmd }(cmd) - if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { - return err - } else if cache != nil { - fmt.Fprintf(b.out, " ---> Using cache\n") - utils.Debugf("[BUILDER] Use cached version") - b.image = cache.ID - return nil - } else { - utils.Debugf("[BUILDER] Cache miss") + if b.utilizeCache { + if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { + return err + } else if cache != nil { + fmt.Fprintf(b.out, " ---> Using cache\n") + utils.Debugf("[BUILDER] Use cached version") + b.image = cache.ID + return nil + } else { + utils.Debugf("[BUILDER] Cache miss") + } } + container, err := b.builder.Create(b.config) if err != nil { return err @@ -500,7 +506,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { return "", fmt.Errorf("An error occured during the build\n") } -func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile { +func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache bool) BuildFile { return &buildFile{ builder: NewBuilder(srv.runtime), runtime: srv.runtime, @@ -510,5 +516,6 @@ func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile { tmpContainers: make(map[string]struct{}), tmpImages: make(map[string]struct{}), verbose: verbose, + utilizeCache: utilizeCache, } } diff --git a/buildfile_test.go b/buildfile_test.go index 78e53b8419..eda047d8ea 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -227,7 +227,7 @@ func buildImage(context testContextTemplate, t *testing.T) *Image { ip := runtime.networkManager.bridgeNetwork.IP dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := NewBuildFile(srv, ioutil.Discard, false) + buildfile := NewBuildFile(srv, ioutil.Discard, false, true) id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err != nil { t.Fatal(err) diff --git a/commands.go b/commands.go index 95ddca1f1d..7a1935ee61 100644 --- a/commands.go +++ b/commands.go @@ -160,6 +160,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH") tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success") suppressOutput := cmd.Bool("q", false, "Suppress verbose build output") + noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image") if err := cmd.Parse(args); err != nil { return nil @@ -208,6 +209,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if isRemote { v.Set("remote", cmd.Arg(0)) } + if *noCache { + v.Set("nocache", "1") + } req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body) if err != nil { return err diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 6ee0b35fa2..6830bacde0 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -928,6 +928,7 @@ Build an image from Dockerfile via stdin :query t: tag to be applied to the resulting image in case of success :query q: suppress verbose build output + :query nocache: do not use the cache when building the image :statuscode 200: no error :statuscode 500: server error diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index 45b6d2ec8e..fcd78dd1ab 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -12,6 +12,7 @@ Build a new container image from the source code at PATH -t="": Tag to be applied to the resulting image in case of success. -q=false: Suppress verbose build output. + -no-cache: Do not use the cache when building the image. When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context From 7bade49d4c661c5037de586e6f69291999038ef9 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 2 Aug 2013 14:08:16 -0400 Subject: [PATCH 068/242] update auth_test.go --- auth/auth_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index d94d429da1..24a0666cf7 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -33,7 +33,7 @@ func TestLogin(t *testing.T) { os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"} - status, err := Login(authConfig) + status, err := Login(authConfig, nil) if err != nil { t.Fatal(err) } @@ -53,7 +53,7 @@ func TestCreateAccount(t *testing.T) { token := hex.EncodeToString(tokenBuffer)[:12] username := "ut" + token authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"} - status, err := Login(authConfig) + status, err := Login(authConfig, nil) if err != nil { t.Fatal(err) } @@ -63,7 +63,7 @@ func TestCreateAccount(t *testing.T) { t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) } - status, err = Login(authConfig) + status, err = Login(authConfig, nil) if err == nil { t.Fatalf("Expected error but found nil instead") } From b9f06959244e3f77eb212c7c234b06eb7b750999 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 2 Aug 2013 19:12:38 +0000 Subject: [PATCH 069/242] Add unit tests for build no cache --- buildfile_test.go | 102 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 83 insertions(+), 19 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index eda047d8ea..0e2d9ecefc 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -195,21 +195,23 @@ func mkTestingFileServer(files [][2]string) (*httptest.Server, error) { func TestBuild(t *testing.T) { for _, ctx := range testContexts { - buildImage(ctx, t) + buildImage(ctx, t, nil, true) } } -func buildImage(context testContextTemplate, t *testing.T) *Image { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } - defer nuke(runtime) +func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image { + if srv == nil { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) - srv := &Server{ - runtime: runtime, - pullingPool: make(map[string]struct{}), - pushingPool: make(map[string]struct{}), + srv = &Server{ + runtime: runtime, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } } httpServer, err := mkTestingFileServer(context.remoteFiles) @@ -224,10 +226,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image { } port := httpServer.URL[idx+1:] - ip := runtime.networkManager.bridgeNetwork.IP + ip := srv.runtime.networkManager.bridgeNetwork.IP dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := NewBuildFile(srv, ioutil.Discard, false, true) + buildfile := NewBuildFile(srv, ioutil.Discard, false, useCache) id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err != nil { t.Fatal(err) @@ -245,7 +247,7 @@ func TestVolume(t *testing.T) { from {IMAGE} volume /test cmd Hello world - `, nil, nil}, t) + `, nil, nil}, t, nil, true) if len(img.Config.Volumes) == 0 { t.Fail() @@ -261,7 +263,7 @@ func TestBuildMaintainer(t *testing.T) { img := buildImage(testContextTemplate{` from {IMAGE} maintainer dockerio - `, nil, nil}, t) + `, nil, nil}, t, nil, true) if img.Author != "dockerio" { t.Fail() @@ -273,7 +275,7 @@ func TestBuildEnv(t *testing.T) { from {IMAGE} env port 4243 `, - nil, nil}, t) + nil, nil}, t, nil, true) hasEnv := false for _, envVar := range img.Config.Env { if envVar == "port=4243" { @@ -291,7 +293,7 @@ func TestBuildCmd(t *testing.T) { from {IMAGE} cmd ["/bin/echo", "Hello World"] `, - nil, nil}, t) + nil, nil}, t, nil, true) if img.Config.Cmd[0] != "/bin/echo" { t.Log(img.Config.Cmd[0]) @@ -308,7 +310,7 @@ func TestBuildExpose(t *testing.T) { from {IMAGE} expose 4243 `, - nil, nil}, t) + nil, nil}, t, nil, true) if img.Config.PortSpecs[0] != "4243" { t.Fail() @@ -320,8 +322,70 @@ func TestBuildEntrypoint(t *testing.T) { from {IMAGE} entrypoint ["/bin/echo"] `, - nil, nil}, t) + nil, nil}, t, nil, true) if img.Config.Entrypoint[0] != "/bin/echo" { } } + +func TestBuildImageWithCache(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + + template := testContextTemplate{` + from {IMAGE} + maintainer dockerio + `, + nil, nil} + + img := buildImage(template, t, srv, true) + imageId := img.ID + + img = nil + img = buildImage(template, t, srv, true) + + if imageId != img.ID { + t.Logf("Image ids should match: %s != %s", imageId, img.ID) + t.Fail() + } +} + +func TestBuildImageWithoutCache(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + + template := testContextTemplate{` + from {IMAGE} + maintainer dockerio + `, + nil, nil} + + img := buildImage(template, t, srv, true) + imageId := img.ID + + img = nil + img = buildImage(template, t, srv, false) + + if imageId == img.ID { + t.Logf("Image ids should not match: %s == %s", imageId, img.ID) + t.Fail() + } +} From b6bff0cbb15535b3d1bc991b975c3df35061aa83 Mon Sep 17 00:00:00 2001 From: Joe Van Dyk Date: Fri, 2 Aug 2013 15:10:57 -0700 Subject: [PATCH 070/242] Update amazon.rst to explain that Vagrant is not necessary for running Docker on ec2 --- docs/sources/installation/amazon.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index 59896bb63c..2b5d8423ce 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -5,10 +5,14 @@ Amazon EC2 ========== +This page explains how to setup and run an Amazon EC2 instance from your local machine. +Vagrant is not necessary to run Docker on EC2. You can follow the :ref:`ubuntu_linux` instructions +installing Docker on any EC2 instance running Ubuntu + Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. - - + + Installation ------------ @@ -89,4 +93,4 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant docker -Continue with the :ref:`hello_world` example. \ No newline at end of file +Continue with the :ref:`hello_world` example. From 07fee445593982ef9063a184c05ba93ecacd2e65 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 2 Aug 2013 19:18:02 -0300 Subject: [PATCH 071/242] Revert "Bind daemon to 0.0.0.0 in Vagrant. Fixes #1304" This reverts commit bdc79ac8b2dfad302f9e144711067a566726cfa2. --- Vagrantfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 7258af5bf7..aadabb8711 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -20,8 +20,6 @@ Vagrant::Config.run do |config| pkg_cmd = "apt-get update -qq; apt-get install -q -y python-software-properties; " \ "add-apt-repository -y ppa:dotcloud/lxc-docker; apt-get update -qq; " \ "apt-get install -q -y lxc-docker; " - # Listen on all interfaces so that the daemon is accessible from the host - pkg_cmd << "sed -i -E 's| /usr/bin/docker -d| /usr/bin/docker -d -H 0.0.0.0|' /etc/init/docker.conf;" # Add X.org Ubuntu backported 3.8 kernel pkg_cmd << "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \ "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; " From 3e9575e275c40acb04c505fa14c1ac63ba490b75 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 2 Aug 2013 15:23:36 -0700 Subject: [PATCH 072/242] Consider empty /etc/resolv.conf as local dns + add unit test --- api.go | 7 ++++++- builder.go | 7 ++++++- utils/utils.go | 24 ++++++++++++++++++------ utils/utils_test.go | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index 95b9c98de8..a48b11c771 100644 --- a/api.go +++ b/api.go @@ -488,7 +488,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 } diff --git a/builder.go b/builder.go index 420370b1e6..82ad1c1271 100644 --- a/builder.go +++ b/builder.go @@ -80,7 +80,12 @@ func (builder *Builder) Create(config *Config) (*Container, error) { return nil, err } - if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() { + resolvConf, err := utils.GetResolvConf() + if err != nil { + return nil, err + } + + if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { //"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns builder.runtime.Dns = defaultDns } diff --git a/utils/utils.go b/utils/utils.go index def5ae5a50..74b0e12c36 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -688,17 +688,29 @@ func IsGIT(str string) bool { return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/") } -func CheckLocalDns() bool { +// GetResolvConf opens and read the content of /etc/resolv.conf. +// It returns it as byte slice. +func GetResolvConf() ([]byte, error) { resolv, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { Debugf("Error openning resolv.conf: %s", err) - return false + return nil, err } - for _, ip := range []string{ - "127.0.0.1", - "127.0.1.1", + return resolv, nil +} + +// CheckLocalDns looks into the /etc/resolv.conf, +// it returns true if there is a local nameserver or if there is no nameserver. +func CheckLocalDns(resolvConf []byte) bool { + if !bytes.Contains(resolvConf, []byte("nameserver")) { + return true + } + + for _, ip := range [][]byte{ + []byte("127.0.0.1"), + []byte("127.0.1.1"), } { - if strings.Contains(string(resolv), ip) { + if bytes.Contains(resolvConf, ip) { return true } } diff --git a/utils/utils_test.go b/utils/utils_test.go index 5c480b9438..882165ac54 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -303,3 +303,37 @@ func TestParseRepositoryTag(t *testing.T) { t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag) } } + +func TestGetResolvConf(t *testing.T) { + resolvConfUtils, err := GetResolvConf() + if err != nil { + t.Fatal(err) + } + resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") + if err != nil { + t.Fatal(err) + } + if string(resolvConfUtils) != string(resolvConfSystem) { + t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.") + } +} + +func TestCheclLocalDns(t *testing.T) { + for resolv, result := range map[string]bool{`# Dynamic +nameserver 10.0.2.3 +search dotcloud.net`: false, + `# Dynamic +nameserver 127.0.0.1 +search dotcloud.net`: true, + `# Dynamic +nameserver 127.0.1.1 +search dotcloud.net`: true, + `# Dynamic +`: true, + ``: true, + } { + if CheckLocalDns([]byte(resolv)) != result { + t.Fatalf("Wrong local dns detection: {%s} should be %v", resolv, result) + } + } +} From dde8f74ceae83f26386ec29e42f615fdc7945e80 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 2 Aug 2013 15:58:10 -0700 Subject: [PATCH 073/242] Fix TestEnv --- container_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container_test.go b/container_test.go index a1ac0bd33a..f29ae9e4ea 100644 --- a/container_test.go +++ b/container_test.go @@ -960,6 +960,7 @@ func TestEnv(t *testing.T) { "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/", "container=lxc", + "HOSTNAME=" + container.ShortID(), } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { From dae585c6e4c19817b2dbd106171728a0bb564ccc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 30 Jul 2013 22:48:20 +0000 Subject: [PATCH 074/242] Return JSONError for HTTPResponse error --- api.go | 12 ++++-------- commands.go | 2 +- registry/registry.go | 4 ++-- utils/error.go | 18 ------------------ utils/utils.go | 13 ++++++++++++- utils_test.go | 2 +- 6 files changed, 20 insertions(+), 31 deletions(-) delete mode 100644 utils/error.go diff --git a/api.go b/api.go index 5869669df0..4ad2ba461a 100644 --- a/api.go +++ b/api.go @@ -388,7 +388,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht if image != "" { //pull if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil { if sf.Used() { - w.Write(sf.FormatError(err, 0)) + w.Write(sf.FormatError(err)) return nil } return err @@ -396,7 +396,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht } else { //import if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil { if sf.Used() { - w.Write(sf.FormatError(err, 0)) + w.Write(sf.FormatError(err)) return nil } return err @@ -441,7 +441,7 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht imgID, err := srv.ImageInsert(name, url, path, w, sf) if err != nil { if sf.Used() { - w.Write(sf.FormatError(err, 0)) + w.Write(sf.FormatError(err)) return nil } } @@ -472,11 +472,7 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http sf := utils.NewStreamFormatter(version > 1.0) if err := srv.ImagePush(name, w, sf, authConfig); err != nil { if sf.Used() { - var code int - if httpErr, ok := err.(*utils.HTTPRequestError); ok { - code = httpErr.StatusCode - } - w.Write(sf.FormatError(err, code)) + w.Write(sf.FormatError(err)) return nil } return err diff --git a/commands.go b/commands.go index 9ad2c367ae..355e91f4bf 100644 --- a/commands.go +++ b/commands.go @@ -31,7 +31,7 @@ const VERSION = "0.5.0-dev" var ( GITCOMMIT string - AuthRequiredError error = fmt.Errorf("Authentication is required.") + AuthRequiredError = fmt.Errorf("Authentication is required.") ) func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { diff --git a/registry/registry.go b/registry/registry.go index ed6f4c7df8..5b8480d183 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -427,9 +427,9 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } - return utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody), res) + return "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody), res) } return tarsumLayer.Sum(jsonRaw), nil } diff --git a/utils/error.go b/utils/error.go deleted file mode 100644 index 7e3c846ebc..0000000000 --- a/utils/error.go +++ /dev/null @@ -1,18 +0,0 @@ -package utils - -import ( - "net/http" -) - -type HTTPRequestError struct { - Message string - StatusCode int -} - -func (e *HTTPRequestError) Error() string { - return e.Message -} - -func NewHTTPRequestError(msg string, resp *http.Response) error { - return &HTTPRequestError{Message: msg, StatusCode: resp.StatusCode} -} diff --git a/utils/utils.go b/utils/utils.go index 659d960d0a..2323829f65 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -625,6 +625,13 @@ func (e *JSONError) Error() string { return e.Message } +func NewHTTPRequestError(msg string, res *http.Response) error { + return &JSONError{ + Message: msg, + Code: res.StatusCode, + } +} + func (jm *JSONMessage) Display(out io.Writer) error { if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) @@ -666,7 +673,11 @@ func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte func (sf *StreamFormatter) FormatError(err error) []byte { sf.used = true if sf.json { - if b, err := json.Marshal(&JSONMessage{Error: &JSONError{Code: code, Message: err.Error()}, ErrorMessage: err.Error()}); err == nil { + jsonError, ok := err.(*JSONError) + if !ok { + jsonError = &JSONError{Message: err.Error()} + } + if b, err := json.Marshal(&JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil { return b } return []byte("{\"error\":\"format error\"}") diff --git a/utils_test.go b/utils_test.go index c4adeb4a74..91df6183fc 100644 --- a/utils_test.go +++ b/utils_test.go @@ -191,7 +191,7 @@ func TestMergeConfig(t *testing.T) { if len(configUser.Volumes) != 3 { t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes)) } - for v, _ := range configUser.Volumes { + for v := range configUser.Volumes { if v != "/test1" && v != "/test2" && v != "/test3" { t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v) } From 708cd3458696101a4aa78d4e8086a29158e133cf Mon Sep 17 00:00:00 2001 From: Tobias Schmidt Date: Sat, 3 Aug 2013 17:31:20 +0700 Subject: [PATCH 075/242] Clarify Amazon EC2 installation type Make clear this documentation is about installing docker on EC2 with the help of vagrant, which doesn't make it easy to start multiple instances, instead of plain vanilla EC2 installation. --- docs/sources/installation/amazon.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index 59896bb63c..e32dfd40f8 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -1,9 +1,9 @@ -:title: Installation on Amazon EC2 +:title: Installation on Amazon EC2 :description: Docker installation on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required. :keywords: amazon ec2, virtualization, cloud, docker, documentation, installation -Amazon EC2 -========== +Using Vagrant (Amazon EC2) +========================== Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. From cd6aeaf97912a0c18994c978a4b58678e671d9ee Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 15:33:51 -0700 Subject: [PATCH 076/242] Sort APIImages by most recent creation date. Fixes #985. --- server.go | 2 ++ sorter.go | 36 ++++++++++++++++++++++++++++++++++++ sorter_test.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 sorter.go create mode 100644 sorter_test.go diff --git a/server.go b/server.go index cb7b2cf1be..5e30dd0118 100644 --- a/server.go +++ b/server.go @@ -241,6 +241,8 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { outs = append(outs, out) } } + + sortImagesByCreation(outs) return outs, nil } diff --git a/sorter.go b/sorter.go new file mode 100644 index 0000000000..a61be0ef75 --- /dev/null +++ b/sorter.go @@ -0,0 +1,36 @@ +package docker + +import "sort" + +type imageSorter struct { + images []APIImages + by func(i1, i2 *APIImages) bool // Closure used in the Less method. +} + +// Len is part of sort.Interface. +func (s *imageSorter) Len() int { + return len(s.images) +} + +// Swap is part of sort.Interface. +func (s *imageSorter) Swap(i, j int) { + s.images[i], s.images[j] = s.images[j], s.images[i] +} + +// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. +func (s *imageSorter) Less(i, j int) bool { + return s.by(&s.images[i], &s.images[j]) +} + +// Sort []ApiImages by most recent creation date. +func sortImagesByCreation(images []APIImages) { + creation := func(i1, i2 *APIImages) bool { + return i1.Created > i2.Created + } + + sorter := &imageSorter{ + images: images, + by: creation} + + sort.Sort(sorter) +} diff --git a/sorter_test.go b/sorter_test.go new file mode 100644 index 0000000000..3c4b3b4874 --- /dev/null +++ b/sorter_test.go @@ -0,0 +1,30 @@ +package docker + +import ( + "testing" +) + +func TestServerListOrderedImages(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + archive, err := fakeTar() + if err != nil { + t.Fatal(err) + } + _, err = runtime.graph.Create(archive, nil, "Testing", "", nil) + if err != nil { + t.Fatal(err) + } + + srv := &Server{runtime: runtime} + + images, err := srv.Images(true, "") + if err != nil { + t.Fatal(err) + } + + if images[0].Created < images[1].Created { + t.Error("Expected []APIImges to be ordered by most recent creation date.") + } +} From db0ccaac9b4e9e12d4e33e5d48e437771aaf8dcd Mon Sep 17 00:00:00 2001 From: Michael Gorsuch Date: Sat, 3 Aug 2013 22:11:59 -0500 Subject: [PATCH 077/242] typo: s/connexions/connections --- docs/sources/installation/ubuntulinux.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 299b7a29e9..cf41b79266 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -163,7 +163,7 @@ Then reload UFW: UFW's default set of rules denied all `incoming`, so if you want to be able to reach your containers from another host, -you should allow incoming connexions on the docker port (default 4243): +you should allow incoming connections on the docker port (default 4243): .. code-block:: bash From 22df1249b582a80f0a1c57d647549d0b91f1cb30 Mon Sep 17 00:00:00 2001 From: Faiz K Date: Sun, 4 Aug 2013 08:26:56 -0500 Subject: [PATCH 078/242] bash commands while in the container aren't in the transcript! Added. --- docs/sources/examples/running_ssh_service.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/sources/examples/running_ssh_service.rst b/docs/sources/examples/running_ssh_service.rst index c2f8b86aca..59ad1b1cab 100644 --- a/docs/sources/examples/running_ssh_service.rst +++ b/docs/sources/examples/running_ssh_service.rst @@ -40,7 +40,17 @@ The password is 'screencast' # now let's connect using -i for interactive and with -t for terminal # we execute /bin/bash to get a prompt. $ docker run -i -t base /bin/bash - # now let's commit it + # yes! we are in! + # now lets install openssh + $ apt-get update + $ apt-get install openssh-server + # ok. lets see if we can run it. + $ which sshd + # we need to create priviledge separation directory + $ mkdir /var/run/sshd + $ /usr/sbin/sshd + $ exit + # now let's commit it # which container was it? $ docker ps -a |more $ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd From 51d0c9238bb01088c08048e022b948a292f815ab Mon Sep 17 00:00:00 2001 From: Daniel Nordberg Date: Mon, 5 Aug 2013 01:16:00 +0300 Subject: [PATCH 079/242] 'Base' is depreciated and should no longer be referenced in the docs. https://groups.google.com/forum/\#!topic/docker-club/pEjqMgcrnqA --- docs/sources/examples/hello_world.rst | 6 ++-- docs/sources/examples/hello_world_daemon.rst | 10 +++--- docs/sources/examples/running_ssh_service.rst | 6 ++-- docs/sources/use/basics.rst | 22 ++++++------ docs/sources/use/puppet.rst | 12 +++---- packaging/debian/lxc-docker.1 | 36 +++++++++---------- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index 1d391f5fb1..448247e572 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -15,8 +15,8 @@ Download the base container .. code-block:: bash - # Download a base image - docker pull base + # Download an ubuntu image + docker pull ubuntu The *base* image is a minimal *ubuntu* based container, alternatively you can select *busybox*, a bare minimal linux system. The images are retrieved from the docker repository. @@ -47,4 +47,4 @@ See the example in action -Continue to the :ref:`hello_world_daemon` example. \ No newline at end of file +Continue to the :ref:`hello_world_daemon` example. diff --git a/docs/sources/examples/hello_world_daemon.rst b/docs/sources/examples/hello_world_daemon.rst index 7ca251aec8..7d876329dd 100644 --- a/docs/sources/examples/hello_world_daemon.rst +++ b/docs/sources/examples/hello_world_daemon.rst @@ -11,20 +11,20 @@ Hello World Daemon The most boring daemon ever written. -This example assumes you have Docker installed and with the base image already imported ``docker pull base``. -We will use the base image to run a simple hello world daemon that will just print hello world to standard +This example assumes you have Docker installed and with the ubuntu image already imported ``docker pull ubuntu``. +We will use the ubuntu image to run a simple hello world daemon that will just print hello world to standard out every second. It will continue to do this until we stop it. **Steps:** .. code-block:: bash - CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done") + CONTAINER_ID=$(docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done") -We are going to run a simple hello world daemon in a new container made from the base image. +We are going to run a simple hello world daemon in a new container made from the ubuntu image. - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon. -- **"base"** is the image we want to run the command inside of. +- **"ubuntu"** is the image we want to run the command inside of. - **"/bin/sh -c"** is the command we want to run in the container - **"while true; do echo hello world; sleep 1; done"** is the mini script we want to run, that will just print hello world once a second until we stop it. - **$CONTAINER_ID** the output of the run command will return a container id, we can use in future commands to see what is going on with this process. diff --git a/docs/sources/examples/running_ssh_service.rst b/docs/sources/examples/running_ssh_service.rst index c2f8b86aca..fa3a2a7ad5 100644 --- a/docs/sources/examples/running_ssh_service.rst +++ b/docs/sources/examples/running_ssh_service.rst @@ -34,12 +34,12 @@ The password is 'screencast' .. code-block:: bash # Hello! We are going to try and install openssh on a container and run it as a servic - # let's pull base to get a base ubuntu image. - $ docker pull base + # let's pull ubuntu to get a base ubuntu image. + $ docker pull ubuntu # I had it so it was quick # now let's connect using -i for interactive and with -t for terminal # we execute /bin/bash to get a prompt. - $ docker run -i -t base /bin/bash + $ docker run -i -t ubuntu /bin/bash # now let's commit it # which container was it? $ docker ps -a |more diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 7ce86416dd..a716ab7fa5 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -26,12 +26,12 @@ Running an interactive shell .. code-block:: bash - # Download a base image - docker pull base + # Download an ubuntu image + docker pull ubuntu - # Run an interactive shell in the base image, + # Run an interactive shell in the ubuntu image, # allocate a tty, attach stdin and stdout - docker run -i -t base /bin/bash + docker run -i -t ubuntu /bin/bash Bind Docker to another host/port or a unix socket ------------------------------------------------- @@ -52,8 +52,8 @@ For example: # Run docker in daemon mode sudo /docker -H 0.0.0.0:5555 -d & - # Download a base image - docker -H :5555 pull base + # Download an ubuntu image + docker -H :5555 pull ubuntu You can use multiple -H, for example, if you want to listen on both tcp and a unix socket @@ -62,10 +62,10 @@ on both tcp and a unix socket # Run docker in daemon mode sudo /docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d & - # Download a base image - docker pull base + # Download an ubuntu image + docker pull ubuntu # OR - docker -H unix:///var/run/docker.sock pull base + docker -H unix:///var/run/docker.sock pull ubuntu Starting a long-running worker process -------------------------------------- @@ -73,7 +73,7 @@ Starting a long-running worker process .. code-block:: bash # Start a very useful long-running process - JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done") + JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") # Collect the output of the job so far docker logs $JOB @@ -95,7 +95,7 @@ Expose a service on a TCP port .. code-block:: bash # Expose port 4444 of this container, and tell netcat to listen on it - JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444) + JOB=$(docker run -d -p 4444 ubuntu /bin/nc -l -p 4444) # Which public port is NATed to my container? PORT=$(docker port $JOB 4444) diff --git a/docs/sources/use/puppet.rst b/docs/sources/use/puppet.rst index 5606f2a863..b89d95d8fe 100644 --- a/docs/sources/use/puppet.rst +++ b/docs/sources/use/puppet.rst @@ -53,13 +53,13 @@ defined type which can be used like so: .. code-block:: ruby - docker::image { 'base': } + docker::image { 'ubuntu': } This is equivalent to running: .. code-block:: bash - docker pull base + docker pull ubuntu Note that it will only if the image of that name does not already exist. This is downloading a large binary so on first run can take a while. @@ -68,7 +68,7 @@ for exec. Note that you can also remove images you no longer need with: .. code-block:: ruby - docker::image { 'base': + docker::image { 'ubuntu': ensure => 'absent', } @@ -81,7 +81,7 @@ docker. .. code-block:: ruby docker::run { 'helloworld': - image => 'base', + image => 'ubuntu', command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"', } @@ -89,14 +89,14 @@ This is equivalent to running the following command, but under upstart: .. code-block:: bash - docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done" + docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done" Run also contains a number of optional parameters: .. code-block:: ruby docker::run { 'helloworld': - image => 'base', + image => 'ubuntu', command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"', ports => ['4444', '4555'], volumes => ['/var/lib/counchdb', '/var/log'], diff --git a/packaging/debian/lxc-docker.1 b/packaging/debian/lxc-docker.1 index cc20299fad..b92853f95b 100644 --- a/packaging/debian/lxc-docker.1 +++ b/packaging/debian/lxc-docker.1 @@ -146,7 +146,7 @@ cd docker\-master .sp .nf .ft C -sudo ./docker run \-i \-t base /bin/bash +sudo ./docker run \-i \-t ubuntu /bin/bash .ft P .fi .sp @@ -496,22 +496,22 @@ This is the most basic example available for using docker .sp This example assumes you have Docker installed. .sp -Download the base container +Download the ubuntu container .sp .nf .ft C -# Download a base image -docker pull base +# Download an ubuntu image +docker pull ubuntu .ft P .fi .sp -The \fIbase\fP image is a minimal \fIubuntu\fP based container, alternatively you can select \fIbusybox\fP, a bare -minimal linux system. The images are retrieved from the docker repository. +Alternatively you can select \fIbusybox\fP, a bare minimal linux system. The +images are retrieved from the docker repository. .sp .nf .ft C #run a simple echo command, that will echo hello world back to the console over standard out. -docker run base /bin/echo hello world +docker run ubuntu /bin/echo hello world .ft P .fi .sp @@ -520,7 +520,7 @@ docker run base /bin/echo hello world .IP \(bu 2 \fB"docker run"\fP run a command in a new container .IP \(bu 2 -\fB"base"\fP is the image we want to run the command inside of. +\fB"ubuntu"\fP is the image we want to run the command inside of. .IP \(bu 2 \fB"/bin/echo"\fP is the command we want to run in the container .IP \(bu 2 @@ -536,15 +536,15 @@ Continue to the \fIhello_world_daemon\fP example. .sp The most boring daemon ever written. .sp -This example assumes you have Docker installed and with the base image already imported \fBdocker pull base\fP. -We will use the base image to run a simple hello world daemon that will just print hello world to standard +This example assumes you have Docker installed and with the ubuntu image already imported \fBdocker pull ubuntu\fP. +We will use the ubuntu image to run a simple hello world daemon that will just print hello world to standard out every second. It will continue to do this until we stop it. .sp \fBSteps:\fP .sp .nf .ft C -$ CONTAINER_ID=$(docker run \-d base /bin/sh \-c "while true; do echo hello world; sleep 1; done") +$ CONTAINER_ID=$(docker run \-d ubuntu /bin/sh \-c "while true; do echo hello world; sleep 1; done") .ft P .fi .sp @@ -553,7 +553,7 @@ We are going to run a simple hello world daemon in a new container made from the .IP \(bu 2 \fB"docker run \-d "\fP run a command in a new container. We pass "\-d" so it runs as a daemon. .IP \(bu 2 -\fB"base"\fP is the image we want to run the command inside of. +\fB"ubuntu"\fP is the image we want to run the command inside of. .IP \(bu 2 \fB"/bin/sh \-c"\fP is the command we want to run in the container .IP \(bu 2 @@ -766,12 +766,12 @@ Contents: .sp .nf .ft C -# Download a base image -docker import base +# Download an ubuntu image +docker import ubuntu -# Run an interactive shell in the base image, +# Run an interactive shell in the ubuntu image, # allocate a tty, attach stdin and stdout -docker run \-a \-i \-t base /bin/bash +docker run \-a \-i \-t ubuntu /bin/bash .ft P .fi .SS Starting a long\-running worker process @@ -782,7 +782,7 @@ docker run \-a \-i \-t base /bin/bash (docker \-d || echo "Docker daemon already running") & # Start a very useful long\-running process -JOB=$(docker run base /bin/sh \-c "while true; do echo Hello world!; sleep 1; done") +JOB=$(docker run ubuntu /bin/sh \-c "while true; do echo Hello world!; sleep 1; done") # Collect the output of the job so far docker logs $JOB @@ -803,7 +803,7 @@ docker ps .nf .ft C # Expose port 4444 of this container, and tell netcat to listen on it -JOB=$(docker run \-p 4444 base /bin/nc \-l \-p 4444) +JOB=$(docker run \-p 4444 ubuntu /bin/nc \-l \-p 4444) # Which public port is NATed to my container? PORT=$(docker port $JOB 4444) From c860945be25c7768ee456b5a71524631ec0dddbd Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Sun, 4 Aug 2013 17:42:24 -0700 Subject: [PATCH 080/242] Reduce connect and read timeout when pinging the registry (fixes issue #1363) --- registry/registry.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 5b8480d183..aa7e524292 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -9,12 +9,14 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" + "net" "net/http" "net/http/cookiejar" "net/url" "regexp" "strconv" "strings" + "time" ) var ( @@ -28,7 +30,19 @@ func pingRegistryEndpoint(endpoint string) error { // (and we never want to fallback to http in case of error) return nil } - resp, err := http.Get(endpoint + "_ping") + httpDial := func(proto string, addr string) (net.Conn, error) { + // Set the connect timeout to 5 seconds + conn, err := net.DialTimeout(proto, addr, time.Duration(5)*time.Second) + if err != nil { + return nil, err + } + // Set the recv timeout to 10 seconds + conn.SetDeadline(time.Now().Add(time.Duration(10) * time.Second)) + return conn, nil + } + httpTransport := &http.Transport{Dial: httpDial} + client := &http.Client{Transport: httpTransport} + resp, err := client.Get(endpoint + "_ping") if err != nil { return err } From c22f2617ad8ed2450d4d9dbb6e6ec39da4e51f2f Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Sun, 4 Aug 2013 17:59:12 -0700 Subject: [PATCH 081/242] Always consider localhost as a domain name when parsing the FQN repos name --- registry/registry.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 5b8480d183..f23ef6c09b 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -69,7 +69,8 @@ func ResolveRepositoryName(reposName string) (string, string, error) { return "", "", ErrInvalidRepositoryName } nameParts := strings.SplitN(reposName, "/", 2) - if !strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") { + if !strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") && + nameParts[0] != "localhost" { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) err := validateRepositoryName(reposName) return auth.IndexServerAddress(), reposName, err From 0f249c85ea9acc7fba33994aa5d20a897463db2c Mon Sep 17 00:00:00 2001 From: Isao Jonas Date: Sun, 4 Aug 2013 19:11:23 -0500 Subject: [PATCH 082/242] fix entrypoint without cmd --- builder.go | 4 +++- buildfile.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/builder.go b/builder.go index 82ad1c1271..9124f76ac1 100644 --- a/builder.go +++ b/builder.go @@ -38,7 +38,9 @@ func (builder *Builder) Create(config *Config) (*Container, error) { MergeConfig(config, img.Config) } - if config.Cmd == nil || len(config.Cmd) == 0 { + if len(config.Entrypoint) != 0 && config.Cmd == nil { + config.Cmd = []string{} + } else if config.Cmd == nil || len(config.Cmd) == 0 { return nil, fmt.Errorf("No command specified") } diff --git a/buildfile.go b/buildfile.go index 159d7ba704..ba4427a49f 100644 --- a/buildfile.go +++ b/buildfile.go @@ -93,6 +93,8 @@ func (b *buildFile) CmdRun(args string) error { b.config.Cmd = nil MergeConfig(b.config, config) + defer func(cmd []string) { b.config.Cmd = cmd }(cmd) + utils.Debugf("Command to be executed: %v", b.config.Cmd) if b.utilizeCache { @@ -115,7 +117,7 @@ func (b *buildFile) CmdRun(args string) error { if err := b.commit(cid, cmd, "run"); err != nil { return err } - b.config.Cmd = cmd + return nil } From d00fb4096700cd8feed06ca32c93f080fb6446b1 Mon Sep 17 00:00:00 2001 From: Isao Jonas Date: Mon, 5 Aug 2013 09:03:42 -0500 Subject: [PATCH 083/242] added tests for 1405 --- buildfile_test.go | 34 ++++++++++++++++++++++++++++++++++ container_test.go | 22 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/buildfile_test.go b/buildfile_test.go index 0e2d9ecefc..9d002f0665 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -328,6 +328,40 @@ func TestBuildEntrypoint(t *testing.T) { } } +// testing #1405 - config.Cmd does not get cleaned up if +// utilizing cache +func TestBuildEntrypointRunCleanup(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + + img := buildImage(testContextTemplate{` + from {IMAGE} + run echo "hello" + `, + nil, nil}, t, srv, true) + + img = buildImage(testContextTemplate{` + from {IMAGE} + run echo "hello" + add foo /foo + entrypoint ["/bin/echo"] + `, + [][2]string{{"foo", "HEYO"}}, nil}, t, srv, true) + + if len(img.Config.Cmd) != 0 { + t.Fail() + } +} + func TestBuildImageWithCache(t *testing.T) { runtime, err := newTestRuntime() if err != nil { diff --git a/container_test.go b/container_test.go index f29ae9e4ea..2f9da15f6e 100644 --- a/container_test.go +++ b/container_test.go @@ -996,6 +996,28 @@ func TestEntrypoint(t *testing.T) { } } +func TestEntrypointNoCmd(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).ID, + Entrypoint: []string{"/bin/echo", "foobar"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + output, err := container.Output() + if err != nil { + t.Fatal(err) + } + if strings.Trim(string(output), "\r\n") != "foobar" { + t.Error(string(output)) + } +} + func grepFile(t *testing.T, path string, pattern string) { f, err := os.Open(path) if err != nil { From ce97a71adf879a228f352ee10b7fb62ff86bc4e6 Mon Sep 17 00:00:00 2001 From: Andrew Macgregor Date: Mon, 5 Aug 2013 22:47:16 +0800 Subject: [PATCH 084/242] Minor typos found while reading docs --- docs/sources/examples/python_web_app.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index 8a4ca70750..ed4a490c72 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -42,7 +42,7 @@ We attach to the new container to see what is going on. Ctrl-C to disconnect BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master) -Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name. +Save the changes we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name. .. code-block:: bash @@ -60,13 +60,13 @@ Use the new image we just created and create a new container with network port 5 docker logs $WEB_WORKER * Running on http://0.0.0.0:5000/ -view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output. +View the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output. .. code-block:: bash WEB_PORT=$(docker port $WEB_WORKER 5000) -lookup the public-facing port which is NAT-ed store the private port used by the container and store it inside of the WEB_PORT variable. +Look up the public-facing port which is NAT-ed. Find the private port used by the container and store it inside of the WEB_PORT variable. .. code-block:: bash @@ -74,7 +74,7 @@ lookup the public-facing port which is NAT-ed store the private port used by the curl http://127.0.0.1:$WEB_PORT Hello world! -access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console. +Access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console. **Video:** From 5f7abd5347fc28ff36b59a03a90dceee22e16606 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 24 Jul 2013 17:44:55 -0700 Subject: [PATCH 085/242] Implemented a Mocked version of the Registry server --- registry/registry_mock_test.go | 321 +++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 registry/registry_mock_test.go diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go new file mode 100644 index 0000000000..f1e65cad32 --- /dev/null +++ b/registry/registry_mock_test.go @@ -0,0 +1,321 @@ +package registry + +import ( + "encoding/json" + "fmt" + "github.com/gorilla/mux" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +var ( + testHttpServer *httptest.Server + testLayers = map[string]map[string]string{ + "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": { + "json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", + "comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00", + "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, + "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, + "PortSpecs":null,"Tty":false,"OpenStdin":false,"StdinOnce":false, + "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, + "VolumesFrom":"","Entrypoint":null},"Size":424242}`, + "checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", + "checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c", + "ancestry": `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, + "layer": string([]byte{ + 0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, + 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66, + 0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78, + 0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31, + 0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8, + 0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1, + 0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6, + 0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb, + 0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce, + 0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00, + }), + }, + "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": { + "json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", + "parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", + "comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00", + "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, + "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, + "PortSpecs":null,"Tty":false,"OpenStdin":false,"StdinOnce":false, + "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, + "VolumesFrom":"","Entrypoint":null},"Size":424242}`, + "checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", + "checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2", + "ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", + "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, + "layer": string([]byte{ + 0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, + 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, + 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56, + 0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5, + 0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e, + 0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93, + 0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee, + 0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9, + 0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55, + 0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17, + 0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00, + }), + }, + } + testRepositories = map[string]map[string]string{ + "foo/bar": { + "latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", + }, + } +) + +func init() { + r := mux.NewRouter() + r.HandleFunc("/v1/_ping", handlerGetPing).Methods("GET") + r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods("GET") + r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods("PUT") + r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods("GET", "DELETE") + r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods("GET") + r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods("PUT") + r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods("GET", "POST", "PUT") + r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Method("GET", "PUT", "DELETE") + r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods("PUT") + r.HandleFunc("/v1/search", handlerSearch).Methods("GET") + testHttpServer = httptest.NewServer(r) +} + +func makeURL(req string) string { + return testHttpServer.URL + req +} + +func writeHeaders(w http.ResponseWriter) { + h := w.Header() + h.Add("Server", "docker-tests/mock") + h.Add("Expires", "-1") + h.Add("Content-Type", "application/json") + h.Add("Pragma", "no-cache") + h.Add("Cache-Control", "no-cache") + h.Add("X-Docker-Registry-Version", "0.0.0") + h.Add("X-Docker-Registry-Config", "mock") +} + +func writeResponse(w http.ResponseWriter, message interface{}, code int) { + writeHeaders(w) + w.WriteHeader(code) + body, err := json.Marshal(message) + if err != nil { + io.WriteString(w, err.Error()) + return + } + w.Write(body) +} + +func readJSON(r *http.Request, dest interface{}) error { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return err + } + return json.Unmarshal(body, dest) +} + +func apiError(w http.ResponseWriter, message string, code int) { + body := map[string]string{ + "error": message, + } + writeResponse(w, body, code) +} + +func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { + if a == b { + return + } + if len(message) == 0 { + message = fmt.Sprintf("%v != %v", a, b) + } + t.Fatal(message) +} + +func requiresAuth(w http.ResponseWriter, r *http.Request) bool { + writeCookie := func() { + value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()) + cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600} + http.SetCookie(w, cookie) + } + if len(r.Cookies()) > 0 { + writeCookie() + return true + } + if len(r.Header.Get("Authorization")) > 0 { + writeCookie() + return true + } + w.Header().Add("WWW-Authenticate", "token") + apiError(w, "Wrong auth", 401) + return false +} + +func handlerGetPing(w http.ResponseWriter, r *http.Request) { + writeResponse(w, true, 200) +} + +func handlerGetImage(w http.ResponseWriter, r *http.Request) { + if !requiresAuth(w, r) { + return + } + vars := mux.Vars(r) + layer, exists := testLayers[vars["image_id"]] + if !exists { + http.NotFound(w, r) + return + } + writeHeaders(w) + io.WriteString(w, layer[vars["action"]]) +} + +func handlerPutImage(w http.ResponseWriter, r *http.Request) { + if !requiresAuth(w, r) { + return + } + vars := mux.Vars(r) + image_id := vars["image_id"] + action := vars["action"] + layer, exists := testLayers[image_id] + if !exists { + if action != "json" { + http.NotFound(w, r) + return + } + layer = make(map[string]string) + testLayers[image_id] = layer + } + if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" { + if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] { + apiError(w, "Wrong checksum", 400) + return + } + } + body, err := ioutil.ReadAll(r.Body) + if err != nil { + apiError(w, fmt.Sprintf("Error: %s", err), 500) + return + } + layer[action] = string(body) + writeResponse(w, true, 200) +} + +func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) { + if !requiresAuth(w, r) { + return + } + repositoryName := mux.Vars(r)["repository"] + tags, exists := testRepositories[repositoryName] + if !exists { + apiError(w, "Repository not found", 404) + } + if r.Method == "DELETE" { + delete(testRepositories, repositoryName) + writeResponse(w, true, 200) + return + } + writeResponse(w, tags, 200) +} + +func handlerGetTag(w http.ResponseWriter, r *http.Request) { + if !requiresAuth(w, r) { + return + } + vars := mux.Vars(r) + repositoryName := vars["repository"] + tagName := vars["tag"] + tags, exists := testRepositories[repositoryName] + if !exists { + apiError(w, "Repository not found", 404) + } + tag, exists := tags[tagName] + if !exists { + apiError(w, "Tag not found", 404) + } + writeResponse(w, tag, 200) +} + +func handlerPutTag(w http.ResponseWriter, r *http.Request) { + if !requiresAuth(w, r) { + return + } + vars := mux.Vars(r) + repositoryName := vars["repository"] + tagName := vars["tag"] + tags, exists := testRepositories[repositoryName] + if !exists { + tags := make(map[string]string) + testRepositories[repositoryName] = tags + } + tagValue := "" + readJSON(r, tagValue) + tags[tagName] = tagValue + writeResponse(w, true, 200) +} + +func handlerUsers(w http.ResponseWriter, r *http.Request) { + code := 200 + if r.Method == "POST" { + code = 201 + } else if r.Method == "PUT" { + code = 204 + } + writeResponse(w, "", code) +} + +func handlerImages(w http.ResponseWriter, r *http.Request) { + if r.Method == "PUT" { + writeResponse(w, "", 200) + return + } + if r.Method == "DELETE" { + writeResponse(w, "", 204) + return + } + images := make([]map[string]string) + for image_id, layer := range testLayers { + image := make(map[string]string) + image["id"] = image_id + image["checksum"] = layer["checksum_tarsum"] + append(images, image) + } + writeResponse(w, images, 200) +} + +func handlerAuth(w http.ResponseWriter, r *http.Request) { + writeResponse(w, "OK", 200) +} + +func handlerSearch(w http.ResponseWriter, r *http.Request) { + writeResponse(w, "{}", 200) +} + +func TestPing(t *testing.T) { + res, err := http.Get(makeURL("/v1/_ping")) + if err != nil { + t.Fatal(err) + } + assertEqual(t, res.StatusCode, 200, "") + assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock", + "This is not a Mocked Registry") +} + +/* Uncomment this to test Mocked Registry locally with curl + * WARNING: Don't push on the repos uncommented, it'll block the tests + * +func TestWait(t *testing.T) { + fmt.Println("Test HTTP server ready and waiting...") + fmt.Println(testHttpServer.URL) + c := make(chan int) + <-c +} +//*/ From 97d1d6f5d20c1475b7cdfcecdf4eeba08de888bd Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 24 Jul 2013 19:22:36 -0700 Subject: [PATCH 086/242] Fixed mocked registry --- registry/registry_mock_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index f1e65cad32..3bbef25df7 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "net/url" "testing" "time" ) @@ -69,7 +70,7 @@ var ( }, } testRepositories = map[string]map[string]string{ - "foo/bar": { + "foo42/bar": { "latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", }, } @@ -84,7 +85,7 @@ func init() { r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods("GET") r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods("PUT") r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods("GET", "POST", "PUT") - r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Method("GET", "PUT", "DELETE") + r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods("GET", "PUT", "DELETE") r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods("PUT") r.HandleFunc("/v1/search", handlerSearch).Methods("GET") testHttpServer = httptest.NewServer(r) @@ -103,6 +104,8 @@ func writeHeaders(w http.ResponseWriter) { h.Add("Cache-Control", "no-cache") h.Add("X-Docker-Registry-Version", "0.0.0") h.Add("X-Docker-Registry-Config", "mock") + u, _ := url.Parse(testHttpServer.URL) + h.Add("X-Docker-Endpoints", u.Host) } func writeResponse(w http.ResponseWriter, message interface{}, code int) { @@ -146,6 +149,9 @@ func requiresAuth(w http.ResponseWriter, r *http.Request) bool { value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()) cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600} http.SetCookie(w, cookie) + //FIXME(sam): this should be sent only on Index routes + value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano()) + w.Header().Add("X-Docker-Token", value) } if len(r.Cookies()) > 0 { writeCookie() @@ -281,12 +287,12 @@ func handlerImages(w http.ResponseWriter, r *http.Request) { writeResponse(w, "", 204) return } - images := make([]map[string]string) + images := []map[string]string{} for image_id, layer := range testLayers { image := make(map[string]string) image["id"] = image_id image["checksum"] = layer["checksum_tarsum"] - append(images, image) + images = append(images, image) } writeResponse(w, images, 200) } From 6926ba558f14da4e48ff5c409a5e78f1e9e0e991 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Thu, 25 Jul 2013 12:57:09 -0700 Subject: [PATCH 087/242] Mocked registry: Added X-Docker-Size when fetching the layer --- registry/registry_mock_test.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index 3bbef25df7..5783067266 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -6,9 +6,11 @@ import ( "github.com/gorilla/mux" "io" "io/ioutil" + "log" "net/http" "net/http/httptest" "net/url" + "strconv" "testing" "time" ) @@ -88,7 +90,15 @@ func init() { r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods("GET", "PUT", "DELETE") r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods("PUT") r.HandleFunc("/v1/search", handlerSearch).Methods("GET") - testHttpServer = httptest.NewServer(r) + testHttpServer = httptest.NewServer(handlerAccessLog(r)) +} + +func handlerAccessLog(handler http.Handler) http.Handler { + logHandler := func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) + handler.ServeHTTP(w, r) + } + return http.HandlerFunc(logHandler) } func makeURL(req string) string { @@ -104,8 +114,6 @@ func writeHeaders(w http.ResponseWriter) { h.Add("Cache-Control", "no-cache") h.Add("X-Docker-Registry-Version", "0.0.0") h.Add("X-Docker-Registry-Config", "mock") - u, _ := url.Parse(testHttpServer.URL) - h.Add("X-Docker-Endpoints", u.Host) } func writeResponse(w http.ResponseWriter, message interface{}, code int) { @@ -181,6 +189,8 @@ func handlerGetImage(w http.ResponseWriter, r *http.Request) { return } writeHeaders(w) + layer_size := len(layer["layer"]) + w.Header().Add("X-Docker-Size", strconv.Itoa(layer_size)) io.WriteString(w, layer[vars["action"]]) } @@ -279,6 +289,8 @@ func handlerUsers(w http.ResponseWriter, r *http.Request) { } func handlerImages(w http.ResponseWriter, r *http.Request) { + u, _ := url.Parse(testHttpServer.URL) + w.Header().Add("X-Docker-Endpoints", u.Host) if r.Method == "PUT" { writeResponse(w, "", 200) return @@ -292,6 +304,7 @@ func handlerImages(w http.ResponseWriter, r *http.Request) { image := make(map[string]string) image["id"] = image_id image["checksum"] = layer["checksum_tarsum"] + image["Tag"] = "latest" images = append(images, image) } writeResponse(w, images, 200) @@ -317,11 +330,11 @@ func TestPing(t *testing.T) { /* Uncomment this to test Mocked Registry locally with curl * WARNING: Don't push on the repos uncommented, it'll block the tests - * + */ func TestWait(t *testing.T) { - fmt.Println("Test HTTP server ready and waiting...") - fmt.Println(testHttpServer.URL) + log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) c := make(chan int) <-c } + //*/ From 310ddec823cbeadf694c396b27b5610474f05bcc Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 26 Jul 2013 17:28:17 -0700 Subject: [PATCH 088/242] Disabled test server in the tests --- registry/registry_mock_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index 5783067266..f634877b63 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -330,7 +330,7 @@ func TestPing(t *testing.T) { /* Uncomment this to test Mocked Registry locally with curl * WARNING: Don't push on the repos uncommented, it'll block the tests - */ + * func TestWait(t *testing.T) { log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) c := make(chan int) From 553ce165c1235542d3a5dd526c063c7a4b9904f4 Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 31 Jul 2013 19:03:14 +0200 Subject: [PATCH 089/242] registry: Fixed a bug where token and cookie info wouldn't be sent when using LookupRemoteImage(). Fixed a bug where no error would be reported when getting a non-200 status code in GetRemoteImageLayer() --- registry/registry.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 0d2a64e053..579b34e8a3 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -147,13 +147,14 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s // Check if an image exists in the Registry func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool { - rt := &http.Transport{Proxy: http.ProxyFromEnvironment} + req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil) if err != nil { return false } - res, err := rt.RoundTrip(req) + req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + res, err := doWithCookies(r.client, req) if err != nil { return false } @@ -200,6 +201,10 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) ( if err != nil { return nil, err } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)", + res.StatusCode, imgID) + } return res.Body, nil } From 29f69211c957a62f13b10036a579191a920f62a7 Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 31 Jul 2013 19:05:06 +0200 Subject: [PATCH 090/242] Mock registry: Fixed a bug where the index validation path would return a 200 status code instead of the expected 204 --- registry/registry_mock_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index f634877b63..236dc00dac 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -11,6 +11,7 @@ import ( "net/http/httptest" "net/url" "strconv" + "strings" "testing" "time" ) @@ -291,7 +292,12 @@ func handlerUsers(w http.ResponseWriter, r *http.Request) { func handlerImages(w http.ResponseWriter, r *http.Request) { u, _ := url.Parse(testHttpServer.URL) w.Header().Add("X-Docker-Endpoints", u.Host) + w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())) if r.Method == "PUT" { + if strings.HasSuffix(r.URL.Path, "images") { + writeResponse(w, "", 204) + return + } writeResponse(w, "", 200) return } @@ -330,6 +336,7 @@ func TestPing(t *testing.T) { /* Uncomment this to test Mocked Registry locally with curl * WARNING: Don't push on the repos uncommented, it'll block the tests +<<<<<<< HEAD * func TestWait(t *testing.T) { log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) @@ -338,3 +345,11 @@ func TestWait(t *testing.T) { } //*/ +======= + */ +// func TestWait(t *testing.T) { +// log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) +// c := make(chan int) +// <-c +// } +>>>>>>> Mock registry: Fixed a bug where the index validation path would return a 200 status code instead of the expected 204 From 97b7b173b999c7422a3b045c3ceffa331a7239ca Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 31 Jul 2013 19:07:31 +0200 Subject: [PATCH 091/242] New registry unit tests remade from scratch, using the mock registry --- registry/registry_test.go | 331 +++++++++++++++++++++----------------- 1 file changed, 187 insertions(+), 144 deletions(-) diff --git a/registry/registry_test.go b/registry/registry_test.go index fd955b7b73..68a5f75f1c 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -1,168 +1,211 @@ package registry -// import ( -// "crypto/rand" -// "encoding/hex" -// "github.com/dotcloud/docker" -// "github.com/dotcloud/docker/auth" -// "io/ioutil" -// "os" -// "path" -// "testing" -// ) +import ( + "github.com/dotcloud/docker/auth" + "strings" + "testing" +) +var ( + IMAGE_ID = "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d" + TOKEN = []string{"fake-token"} + REPO = "foo42/bar" +) -// func newTestRuntime() (*Runtime, error) { -// root, err := ioutil.TempDir("", "docker-test") -// if err != nil { -// return nil, err -// } -// if err := os.Remove(root); err != nil { -// return nil, err -// } +type simpleVersionInfo struct { + name string + version string +} -// if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) { -// return nil, err -// } +func (v *simpleVersionInfo) Name() string { + return v.name +} -// return runtime, nil -// } +func (v *simpleVersionInfo) Version() string { + return v.version +} -// func TestPull(t *testing.T) { -// os.Setenv("DOCKER_INDEX_URL", "") -// runtime, err := newTestRuntime() -// if err != nil { -// t.Fatal(err) -// } -// defer nuke(runtime) +func spawnTestRegistry(t *testing.T) *Registry { + versionInfo := make([]VersionInfo, 0, 4) + versionInfo = append(versionInfo, &simpleVersionInfo{"docker", "0.0.0test"}) + versionInfo = append(versionInfo, &simpleVersionInfo{"go", "test"}) + versionInfo = append(versionInfo, &simpleVersionInfo{"git-commit", "test"}) + versionInfo = append(versionInfo, &simpleVersionInfo{"kernel", "test"}) + authConfig := &auth.AuthConfig{} + r, err := NewRegistry("", authConfig, versionInfo...) + if err != nil { + t.Fatal(err) + } + return r +} -// err = runtime.graph.PullRepository(ioutil.Discard, "busybox", "", runtime.repositories, nil) -// if err != nil { -// t.Fatal(err) -// } -// img, err := runtime.repositories.LookupImage("busybox") -// if err != nil { -// t.Fatal(err) -// } +func TestPingRegistryEndpoint(t *testing.T) { + err := pingRegistryEndpoint(makeURL("/v1/")) + if err != nil { + t.Fatal(err) + } +} -// // Try to run something on this image to make sure the layer's been downloaded properly. -// config, _, err := docker.ParseRun([]string{img.Id, "echo", "Hello World"}, runtime.capabilities) -// if err != nil { -// t.Fatal(err) -// } +func TestGetRemoteHistory(t *testing.T) { + r := spawnTestRegistry(t) + hist, err := r.GetRemoteHistory(IMAGE_ID, makeURL("/v1/"), TOKEN) + if err != nil { + t.Fatal(err) + } + assertEqual(t, len(hist), 2, "Expected 2 images in history") + assertEqual(t, hist[0], IMAGE_ID, "Expected " + IMAGE_ID + "as first ancestry") + assertEqual(t, hist[1], "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", + "Unexpected second ancestry") +} -// b := NewBuilder(runtime) -// container, err := b.Create(config) -// if err != nil { -// t.Fatal(err) -// } -// if err := container.Start(); err != nil { -// t.Fatal(err) -// } +func TestLookupRemoteImage(t *testing.T) { + r := spawnTestRegistry(t) + found := r.LookupRemoteImage(IMAGE_ID, makeURL("/v1/"), TOKEN) + assertEqual(t, found, true, "Expected remote lookup to succeed") + found = r.LookupRemoteImage("abcdef", makeURL("/v1/"), TOKEN) + assertEqual(t, found, false, "Expected remote lookup to fail") +} -// if status := container.Wait(); status != 0 { -// t.Fatalf("Expected status code 0, found %d instead", status) -// } -// } +func TestGetRemoteImageJSON(t *testing.T) { + r := spawnTestRegistry(t) + json, size, err := r.GetRemoteImageJSON(IMAGE_ID, makeURL("/v1/"), TOKEN) + if err != nil { + t.Fatal(err) + } + assertEqual(t, size, 154, "Expected size 154") + if len(json) <= 0 { + t.Fatal("Expected non-empty json") + } -// func TestPullTag(t *testing.T) { -// os.Setenv("DOCKER_INDEX_URL", "") -// runtime, err := newTestRuntime() -// if err != nil { -// t.Fatal(err) -// } -// defer nuke(runtime) + _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"), TOKEN) + if err == nil { + t.Fatal("Expected image not found error") + } +} -// err = runtime.graph.PullRepository(ioutil.Discard, "ubuntu", "12.04", runtime.repositories, nil) -// if err != nil { -// t.Fatal(err) -// } -// _, err = runtime.repositories.LookupImage("ubuntu:12.04") -// if err != nil { -// t.Fatal(err) -// } +func TestGetRemoteImageLayer(t *testing.T) { + r := spawnTestRegistry(t) + data, err := r.GetRemoteImageLayer(IMAGE_ID, makeURL("/v1/"), TOKEN) + if err != nil { + t.Fatal(err) + } + if data == nil { + t.Fatal("Expected non-nil data result") + } -// img2, err := runtime.repositories.LookupImage("ubuntu:12.10") -// if img2 != nil { -// t.Fatalf("Expected nil image but found %v instead", img2.Id) -// } -// } + _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), TOKEN) + if err == nil { + t.Fatal("Expected image not found error") + } +} -// func login(runtime *Runtime) error { -// authConfig := auth.NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", runtime.root) -// runtime.authConfig = authConfig -// _, err := auth.Login(authConfig) -// return err -// } +func TestGetRemoteTags(t *testing.T) { + r := spawnTestRegistry(t) + tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO, TOKEN) + if err != nil { + t.Fatal(err) + } + assertEqual(t, len(tags), 1, "Expected one tag") + assertEqual(t, tags["latest"], IMAGE_ID, "Expected tag latest to map to " + IMAGE_ID) -// func TestPush(t *testing.T) { -// os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") -// defer os.Setenv("DOCKER_INDEX_URL", "") -// runtime, err := newTestRuntime() -// if err != nil { -// t.Fatal(err) -// } -// defer nuke(runtime) + _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz", TOKEN) + if err == nil { + t.Fatal("Expected error when fetching tags for bogus repo") + } +} -// err = login(runtime) -// if err != nil { -// t.Fatal(err) -// } +func TestGetRepositoryData(t *testing.T) { + r := spawnTestRegistry(t) + data, err := r.GetRepositoryData(makeURL("/v1/"), "foo42/bar") + if err != nil { + t.Fatal(err) + } + assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList") + assertEqual(t, len(data.Endpoints), 1, "Expected one endpoint in Endpoints") +} -// err = runtime.graph.PullRepository(ioutil.Discard, "joffrey/busybox", "", runtime.repositories, nil) -// if err != nil { -// t.Fatal(err) -// } -// tokenBuffer := make([]byte, 16) -// _, err = rand.Read(tokenBuffer) -// if err != nil { -// t.Fatal(err) -// } -// token := hex.EncodeToString(tokenBuffer)[:29] -// config, _, err := ParseRun([]string{"joffrey/busybox", "touch", "/" + token}, runtime.capabilities) -// if err != nil { -// t.Fatal(err) -// } +func TestPushImageJSONRegistry(t *testing.T) { + r := spawnTestRegistry(t) + imgData := &ImgData{ + ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", + Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", + } -// b := NewBuilder(runtime) -// container, err := b.Create(config) -// if err != nil { -// t.Fatal(err) -// } -// if err := container.Start(); err != nil { -// t.Fatal(err) -// } + err := r.PushImageJSONRegistry(imgData, []byte{ 0x42, 0xdf, 0x0 }, makeURL("/v1/"), TOKEN) + if err != nil { + t.Fatal(err) + } +} -// if status := container.Wait(); status != 0 { -// t.Fatalf("Expected status code 0, found %d instead", status) -// } +func TestPushImageLayerRegistry(t *testing.T) { + r := spawnTestRegistry(t) + layer := strings.NewReader("FAKELAYER") + r.PushImageLayerRegistry(IMAGE_ID, layer, makeURL("/v1/"), TOKEN) +} -// img, err := b.Commit(container, "unittester/"+token, "", "", "", nil) -// if err != nil { -// t.Fatal(err) -// } +func TestResolveRepositoryName(t *testing.T) { + _, _, err := ResolveRepositoryName("https://github.com/dotcloud/docker") + assertEqual(t, err, ErrInvalidRepositoryName, "Expected error invalid repo name") + ep, repo, err := ResolveRepositoryName("fooo/bar") + if err != nil { + t.Fatal(err) + } + assertEqual(t, ep, auth.IndexServerAddress(), "Expected endpoint to be index server address") + assertEqual(t, repo, "fooo/bar", "Expected resolved repo to be foo/bar") -// repo := runtime.repositories.Repositories["unittester/"+token] -// err = runtime.graph.PushRepository(ioutil.Discard, "unittester/"+token, repo, runtime.authConfig) -// if err != nil { -// t.Fatal(err) -// } + u := makeURL("")[7:] + ep, repo, err = ResolveRepositoryName(u + "/private/moonbase") + if err != nil { + t.Fatal(err) + } + assertEqual(t, ep, "http://" + u + "/v1/", "Expected endpoint to be " + u) + assertEqual(t, repo, "private/moonbase", "Expected endpoint to be private/moonbase") +} -// // Remove image so we can pull it again -// if err := runtime.graph.Delete(img.Id); err != nil { -// t.Fatal(err) -// } +func TestPushRegistryTag(t *testing.T) { + r := spawnTestRegistry(t) + err := r.PushRegistryTag("foo42/bar", IMAGE_ID, "stable", makeURL("/v1/"), TOKEN) + if err != nil { + t.Fatal(err) + } +} -// err = runtime.graph.PullRepository(ioutil.Discard, "unittester/"+token, "", runtime.repositories, runtime.authConfig) -// if err != nil { -// t.Fatal(err) -// } +func TestPushImageJSONIndex(t *testing.T) { + r := spawnTestRegistry(t) + imgData := []*ImgData{ + &ImgData{ + ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", + Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", + }, + &ImgData{ + ID: "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", + Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", + }, + } + ep := makeURL("/v1/") + repoData, err := r.PushImageJSONIndex(ep, "foo42/bar", imgData, false, nil) + if err != nil { + t.Fatal(err) + } + if repoData == nil { + t.Fatal("Expected RepositoryData object") + } + repoData, err = r.PushImageJSONIndex(ep, "foo42/bar", imgData, true, []string{ep}) + if err != nil { + t.Fatal(err) + } + if repoData == nil { + t.Fatal("Expected RepositoryData object") + } +} -// layerPath, err := img.layer() -// if err != nil { -// t.Fatal(err) -// } - -// if _, err := os.Stat(path.Join(layerPath, token)); err != nil { -// t.Fatalf("Error while trying to retrieve token file: %v", err) -// } -// } +func TestSearchRepositories(t *testing.T) { + r := spawnTestRegistry(t) + results, err := r.SearchRepositories("supercalifragilisticepsialidocious") + if err != nil { + t.Fatal(err) + } + if results == nil { + t.Fatal("Expected non-nil SearchResults object") + } + assertEqual(t, results.NumResults, 0, "Expected 0 search results") +} \ No newline at end of file From 484ba4a8c5cd4c06330bdd5fb58f98f07619e2aa Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 31 Jul 2013 19:12:40 +0200 Subject: [PATCH 092/242] gofmt --- registry/registry_test.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/registry/registry_test.go b/registry/registry_test.go index 68a5f75f1c..642fe1c02d 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -5,10 +5,11 @@ import ( "strings" "testing" ) + var ( IMAGE_ID = "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d" - TOKEN = []string{"fake-token"} - REPO = "foo42/bar" + TOKEN = []string{"fake-token"} + REPO = "foo42/bar" ) type simpleVersionInfo struct { @@ -52,7 +53,7 @@ func TestGetRemoteHistory(t *testing.T) { t.Fatal(err) } assertEqual(t, len(hist), 2, "Expected 2 images in history") - assertEqual(t, hist[0], IMAGE_ID, "Expected " + IMAGE_ID + "as first ancestry") + assertEqual(t, hist[0], IMAGE_ID, "Expected "+IMAGE_ID+"as first ancestry") assertEqual(t, hist[1], "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", "Unexpected second ancestry") } @@ -105,7 +106,7 @@ func TestGetRemoteTags(t *testing.T) { t.Fatal(err) } assertEqual(t, len(tags), 1, "Expected one tag") - assertEqual(t, tags["latest"], IMAGE_ID, "Expected tag latest to map to " + IMAGE_ID) + assertEqual(t, tags["latest"], IMAGE_ID, "Expected tag latest to map to "+IMAGE_ID) _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz", TOKEN) if err == nil { @@ -126,11 +127,11 @@ func TestGetRepositoryData(t *testing.T) { func TestPushImageJSONRegistry(t *testing.T) { r := spawnTestRegistry(t) imgData := &ImgData{ - ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", + ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", } - err := r.PushImageJSONRegistry(imgData, []byte{ 0x42, 0xdf, 0x0 }, makeURL("/v1/"), TOKEN) + err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"), TOKEN) if err != nil { t.Fatal(err) } @@ -157,7 +158,7 @@ func TestResolveRepositoryName(t *testing.T) { if err != nil { t.Fatal(err) } - assertEqual(t, ep, "http://" + u + "/v1/", "Expected endpoint to be " + u) + assertEqual(t, ep, "http://"+u+"/v1/", "Expected endpoint to be "+u) assertEqual(t, repo, "private/moonbase", "Expected endpoint to be private/moonbase") } @@ -173,11 +174,11 @@ func TestPushImageJSONIndex(t *testing.T) { r := spawnTestRegistry(t) imgData := []*ImgData{ &ImgData{ - ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", + ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", }, &ImgData{ - ID: "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", + ID: "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", }, } @@ -208,4 +209,4 @@ func TestSearchRepositories(t *testing.T) { t.Fatal("Expected non-nil SearchResults object") } assertEqual(t, results.NumResults, 0, "Expected 0 search results") -} \ No newline at end of file +} From 9159c819c3baf33844f0cc38a4baa0c6674e8b9a Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 5 Aug 2013 16:22:46 +0200 Subject: [PATCH 093/242] Mock access logs don't show up in non-debug mode --- registry/registry_mock_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index 236dc00dac..e39637c16c 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -3,10 +3,10 @@ package registry import ( "encoding/json" "fmt" + "github.com/dotcloud/docker/utils" "github.com/gorilla/mux" "io" "io/ioutil" - "log" "net/http" "net/http/httptest" "net/url" @@ -96,7 +96,7 @@ func init() { func handlerAccessLog(handler http.Handler) http.Handler { logHandler := func(w http.ResponseWriter, r *http.Request) { - log.Printf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) + utils.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) handler.ServeHTTP(w, r) } return http.HandlerFunc(logHandler) From 2c85b964e3be0f7ca3ff513b855f7bd26b936cc9 Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 5 Aug 2013 19:07:23 +0200 Subject: [PATCH 094/242] Cleanup --- registry/registry_mock_test.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index e39637c16c..e752315510 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -336,7 +336,6 @@ func TestPing(t *testing.T) { /* Uncomment this to test Mocked Registry locally with curl * WARNING: Don't push on the repos uncommented, it'll block the tests -<<<<<<< HEAD * func TestWait(t *testing.T) { log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) @@ -344,12 +343,4 @@ func TestWait(t *testing.T) { <-c } -//*/ -======= - */ -// func TestWait(t *testing.T) { -// log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) -// c := make(chan int) -// <-c -// } ->>>>>>> Mock registry: Fixed a bug where the index validation path would return a 200 status code instead of the expected 204 +//*/ \ No newline at end of file From 8aa9985ad07f71fa9bd3b31dde81c0da6d0dc655 Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 5 Aug 2013 20:28:05 +0200 Subject: [PATCH 095/242] Adapted tests to latest registry changes --- registry/registry_test.go | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/registry/registry_test.go b/registry/registry_test.go index 642fe1c02d..a8543f186d 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -2,6 +2,7 @@ package registry import ( "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/utils" "strings" "testing" ) @@ -12,27 +13,9 @@ var ( REPO = "foo42/bar" ) -type simpleVersionInfo struct { - name string - version string -} - -func (v *simpleVersionInfo) Name() string { - return v.name -} - -func (v *simpleVersionInfo) Version() string { - return v.version -} - func spawnTestRegistry(t *testing.T) *Registry { - versionInfo := make([]VersionInfo, 0, 4) - versionInfo = append(versionInfo, &simpleVersionInfo{"docker", "0.0.0test"}) - versionInfo = append(versionInfo, &simpleVersionInfo{"go", "test"}) - versionInfo = append(versionInfo, &simpleVersionInfo{"git-commit", "test"}) - versionInfo = append(versionInfo, &simpleVersionInfo{"kernel", "test"}) authConfig := &auth.AuthConfig{} - r, err := NewRegistry("", authConfig, versionInfo...) + r, err := NewRegistry("", authConfig, utils.NewHTTPRequestFactory()) if err != nil { t.Fatal(err) } @@ -139,8 +122,11 @@ func TestPushImageJSONRegistry(t *testing.T) { func TestPushImageLayerRegistry(t *testing.T) { r := spawnTestRegistry(t) - layer := strings.NewReader("FAKELAYER") - r.PushImageLayerRegistry(IMAGE_ID, layer, makeURL("/v1/"), TOKEN) + layer := strings.NewReader("") + _, err := r.PushImageLayerRegistry(IMAGE_ID, layer, makeURL("/v1/"), TOKEN, []byte{}) + if err != nil { + t.Fatal(err) + } } func TestResolveRepositoryName(t *testing.T) { From 4179f25286dde9f9e83bffa07c303373844b5ed2 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Mon, 1 Jul 2013 19:05:05 -0400 Subject: [PATCH 096/242] fixed #910. print user name to docker info output --- commands.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commands.go b/commands.go index aa991811ca..9c59857ae1 100644 --- a/commands.go +++ b/commands.go @@ -495,6 +495,11 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener) fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion) } + if cli.authConfig != nil { + fmt.Fprintf(cli.out, "Username: %v\n", cli.authConfig.Username) + // XXX Should we print registry address even if the user was not logged in? + fmt.Fprintf(cli.out, "Registry: %v\n", auth.IndexServerAddress()) + } if !out.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") } From c03561eea84f88d0eeddd84a7021876852c0a7c0 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 5 Aug 2013 14:32:54 -0700 Subject: [PATCH 097/242] Update CHANGELOG.md --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00e358c136..843f0212d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 0.5.1 (2013-07-30) + + API: Docker client now sets useragent (RFC 2616) + + Runtime: Add `ps` args to `docker top` + + Runtime: Add support for container ID files (pidfile like) + + Runtime: Add container=lxc in default env + + Runtime: Support networkless containers with `docker run -n` and `docker -d -b=none` + + API: Add /events endpoint + + Builder: ADD command now understands URLs + + Builder: CmdAdd and CmdEnv now respect Dockerfile-set ENV variables + * Hack: Simplify unit tests with helpers + * Hack: Improve docker.upstart event + * Hack: Add coverage testing into docker-ci + * Runtime: Stdout/stderr logs are now stored in the same file as JSON + * Runtime: Allocate a /16 IP range by default, with fallback to /24. Try 12 ranges instead of 3. + * Runtime: Change .dockercfg format to json and support multiple auth remote + - Runtime: Do not override volumes from config + - Runtime: Fix issue with EXPOSE override + - Builder: Create directories with 755 instead of 700 within ADD instruction + ## 0.5.0 (2013-07-17) + Runtime: List all processes running inside a container with 'docker top' + Runtime: Host directories can be mounted as volumes with 'docker run -v' From 590fc58de70bb281812371be6e44ccb7d73d3ec6 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 5 Aug 2013 14:49:37 -0700 Subject: [PATCH 098/242] Update version. --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index aa991811ca..78916f3a7c 100644 --- a/commands.go +++ b/commands.go @@ -27,7 +27,7 @@ import ( "unicode" ) -const VERSION = "0.5.0-dev" +const VERSION = "0.5.1-dev" var ( GITCOMMIT string From ff6b6f2ce1f51c38b00d1a70376182645b4a0c85 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 5 Aug 2013 15:55:40 -0700 Subject: [PATCH 099/242] Fixed some typo's and formatting issues in remote api documentation. --- docs/README.md | 2 +- docs/sources/api/docker_remote_api.rst | 2 +- docs/sources/api/docker_remote_api_v1.0.rst | 2 +- docs/sources/api/docker_remote_api_v1.1.rst | 2 +- docs/sources/api/docker_remote_api_v1.2.rst | 2 +- docs/sources/api/docker_remote_api_v1.3.rst | 2 +- docs/sources/api/docker_remote_api_v1.4.rst | 91 +++++++++++---------- 7 files changed, 53 insertions(+), 50 deletions(-) diff --git a/docs/README.md b/docs/README.md index 1a40b5bb5c..366b7ed8f2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ Usage * Change the `.rst` files with your favorite editor to your liking. * Run `make docs` to clean up old files and generate new ones. * Your static website can now be found in the `_build` directory. -* To preview what you have generated run `make server` and open in your favorite browser. +* To preview what you have generated run `make server` and open http://localhost:8000/ in your favorite browser. Working using GitHub's file editor ---------------------------------- diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index dd017cde8d..3be02141c0 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -15,7 +15,7 @@ Docker Remote API ===================== - The Remote API is replacing rcli -- Default port in the docker deamon is 4243 +- Default port in the docker daemon is 4243 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr diff --git a/docs/sources/api/docker_remote_api_v1.0.rst b/docs/sources/api/docker_remote_api_v1.0.rst index f499a5f53a..3ee3245137 100644 --- a/docs/sources/api/docker_remote_api_v1.0.rst +++ b/docs/sources/api/docker_remote_api_v1.0.rst @@ -17,7 +17,7 @@ Docker Remote API v1.0 ===================== - The Remote API is replacing rcli -- Default port in the docker deamon is 4243 +- Default port in the docker daemon is 4243 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr 2. Endpoints diff --git a/docs/sources/api/docker_remote_api_v1.1.rst b/docs/sources/api/docker_remote_api_v1.1.rst index e0159ddb65..3e906ed50f 100644 --- a/docs/sources/api/docker_remote_api_v1.1.rst +++ b/docs/sources/api/docker_remote_api_v1.1.rst @@ -17,7 +17,7 @@ Docker Remote API v1.1 ===================== - The Remote API is replacing rcli -- Default port in the docker deamon is 4243 +- Default port in the docker daemon is 4243 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr 2. Endpoints diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index bd869ca824..b956d1dfe6 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -17,7 +17,7 @@ Docker Remote API v1.2 ===================== - The Remote API is replacing rcli -- Default port in the docker deamon is 4243 +- Default port in the docker daemon is 4243 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr 2. Endpoints diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index f2b4062fef..8e5c7b2a3b 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -17,7 +17,7 @@ Docker Remote API v1.3 ===================== - The Remote API is replacing rcli -- Default port in the docker deamon is 4243 +- Default port in the docker daemon is 4243 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr 2. Endpoints diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 7406a42b9d..31e5b5f766 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -2,6 +2,8 @@ :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation +:orphan: + ====================== Docker Remote API v1.4 ====================== @@ -12,7 +14,7 @@ Docker Remote API v1.4 ===================== - The Remote API is replacing rcli -- Default port in the docker deamon is 4243 +- Default port in the docker daemon is 4243 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr 2. Endpoints @@ -765,29 +767,29 @@ Push an image on the registry .. http:post:: /images/(name)/push - Push the image ``name`` on the registry + Push the image ``name`` on the registry - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /images/test/push HTTP/1.1 - {{ authConfig }} + POST /images/test/push HTTP/1.1 + {{ authConfig }} - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - {"status":"Pushing..."} - {"status":"Pushing", "progress":"1/? (n/a)"} - {"error":"Invalid..."} - ... + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... - :query registry: the registry you wan to push, optional - :statuscode 200: no error + :query registry: the registry you wan to push, optional + :statuscode 200: no error :statuscode 404: no such image :statuscode 500: server error @@ -900,37 +902,37 @@ Build an image from Dockerfile via stdin .. http:post:: /build - Build an image from Dockerfile via stdin + Build an image from Dockerfile via stdin - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /build HTTP/1.1 - - {{ STREAM }} + POST /build HTTP/1.1 - **Example response**: + {{ STREAM }} - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - - {{ STREAM }} + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} - The stream must be a tar archive compressed with one of the following algorithms: - identity (no compression), gzip, bzip2, xz. The archive must include a file called - `Dockerfile` at its root. It may include any number of other files, which will be - accessible in the build context (See the ADD build command). + The stream must be a tar archive compressed with one of the following algorithms: + identity (no compression), gzip, bzip2, xz. The archive must include a file called + `Dockerfile` at its root. It may include any number of other files, which will be + accessible in the build context (See the ADD build command). - The Content-type header should be set to "application/tar". + The Content-type header should be set to "application/tar". :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success :query q: suppress verbose build output :query nocache: do not use the cache when building the image :statuscode 200: no error - :statuscode 500: server error + :statuscode 500: server error Check auth configuration @@ -1033,22 +1035,22 @@ Create a new image from a container's changes .. http:post:: /commit - Create a new image from a container's changes + Create a new image from a container's changes - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 201 OK - Content-Type: application/vnd.docker.raw-stream + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream - {"Id":"596069db4bf5"} + {"Id":"596069db4bf5"} :query container: source container :query repo: repository @@ -1060,7 +1062,6 @@ Create a new image from a container's changes :statuscode 404: no such container :statuscode 500: server error - 3. Going further ================ @@ -1089,6 +1090,8 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a ----------------- To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. - - docker -d -H="192.168.1.9:4243" -api-enable-cors + +.. code-block:: bash + + docker -d -H="192.168.1.9:4243" -api-enable-cors From dcf9dfb12961fc8101624af0b925d026fde4cde1 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 5 Aug 2013 16:32:25 -0700 Subject: [PATCH 100/242] Update utils_test.go --- utils/utils_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils_test.go b/utils/utils_test.go index 882165ac54..d8ba7f1c31 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -318,7 +318,7 @@ func TestGetResolvConf(t *testing.T) { } } -func TestCheclLocalDns(t *testing.T) { +func TestCheckLocalDns(t *testing.T) { for resolv, result := range map[string]bool{`# Dynamic nameserver 10.0.2.3 search dotcloud.net`: false, From 8934f13615e95a0c9f4bc52eefb5c97360e114f3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 5 Aug 2013 22:56:02 +0000 Subject: [PATCH 101/242] Change daemon to listen on unix socket by default --- api.go | 8 ++++---- docker/docker.go | 2 +- docs/sources/api/docker_remote_api.rst | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api.go b/api.go index f520d7a73b..f07a0fca66 100644 --- a/api.go +++ b/api.go @@ -18,8 +18,9 @@ import ( ) const APIVERSION = 1.4 -const DEFAULTHTTPHOST string = "127.0.0.1" -const DEFAULTHTTPPORT int = 4243 +const DEFAULTHTTPHOST = "127.0.0.1" +const DEFAULTHTTPPORT = 4243 +const DEFAULTUNIXSOCKET = "/var/run/docker.sock" func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -977,9 +978,8 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error { if e != nil { return e } - //as the daemon is launched as root, change to permission of the socket to allow non-root to connect if proto == "unix" { - os.Chmod(addr, 0777) + os.Chmod(addr, 0700) } httpSrv := http.Server{Addr: addr, Handler: r} return httpSrv.Serve(l) diff --git a/docker/docker.go b/docker/docker.go index 8d7bf9b42a..9874d756d5 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -33,7 +33,7 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") - flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)} + flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() if len(flHosts) > 1 { diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 3be02141c0..7e4b674348 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -15,7 +15,7 @@ Docker Remote API ===================== - The Remote API is replacing rcli -- Default port in the docker daemon is 4243 +- By default the Docker daemon listens on unix:///var/run/docker.sock and the client must have root access to interact with the daemon - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr From 049d28868e87df084748a91db5d1cfecb0de6d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Mon, 5 Aug 2013 18:11:13 -0700 Subject: [PATCH 102/242] fix the upstart script generated by get.docker.io (it was not starting dockerd on boot) --- contrib/install.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/install.sh b/contrib/install.sh index cf097da670..e58f054685 100755 --- a/contrib/install.sh +++ b/contrib/install.sh @@ -45,7 +45,13 @@ then echo "Upstart script already exists." else echo "Creating /etc/init/dockerd.conf..." - echo "exec env LANG=\"en_US.UTF-8\" /usr/local/bin/docker -d" > /etc/init/dockerd.conf + cat >/etc/init/dockerd.conf < Date: Mon, 5 Aug 2013 19:58:48 -0700 Subject: [PATCH 103/242] Make sure all sources have the wanted revision --- packaging/ubuntu/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile index 7bddf2376f..a1e53b1d5c 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -32,7 +32,7 @@ ubuntu: # Retrieve docker project and its go structure from internet rm -rf ${BUILD_SRC} git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH} - cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout v${VERSION} && GOPATH=${BUILD_SRC} go get -d + cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout v${VERSION} && GOPATH=${BUILD_SRC} go get -d && cd ${BUILD_SRC}/src/${GITHUB_PATH} && git checkout v${VERSION} # Add debianization mkdir ${BUILD_SRC}/debian cp Makefile ${BUILD_SRC} From 303490168fb53af2528375ca9ec818c7a179a044 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Mon, 22 Jul 2013 14:42:31 -0400 Subject: [PATCH 104/242] Added index address into APIInfo. --- api_params.go | 21 +++++++++++---------- commands.go | 10 ++++++---- server.go | 21 +++++++++++---------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/api_params.go b/api_params.go index 1ae26072e7..ca4c5503ec 100644 --- a/api_params.go +++ b/api_params.go @@ -17,16 +17,17 @@ type APIImages struct { } type APIInfo struct { - Debug bool - Containers int - Images int - NFd int `json:",omitempty"` - NGoroutines int `json:",omitempty"` - MemoryLimit bool `json:",omitempty"` - SwapLimit bool `json:",omitempty"` - LXCVersion string `json:",omitempty"` - NEventsListener int `json:",omitempty"` - KernelVersion string `json:",omitempty"` + Debug bool + Containers int + Images int + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` + MemoryLimit bool `json:",omitempty"` + SwapLimit bool `json:",omitempty"` + LXCVersion string `json:",omitempty"` + NEventsListener int `json:",omitempty"` + KernelVersion string `json:",omitempty"` + IndexServerAddress string `json:",omitempty"` } type APITop struct { diff --git a/commands.go b/commands.go index 9c59857ae1..167d33ce1d 100644 --- a/commands.go +++ b/commands.go @@ -495,10 +495,12 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener) fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion) } - if cli.authConfig != nil { - fmt.Fprintf(cli.out, "Username: %v\n", cli.authConfig.Username) - // XXX Should we print registry address even if the user was not logged in? - fmt.Fprintf(cli.out, "Registry: %v\n", auth.IndexServerAddress()) + if len(out.IndexServerAddress) != 0 { + u := cli.configFile.Configs[out.IndexServerAddress].Username + if len(u) > 0 { + fmt.Fprintf(cli.out, "Username: %v\n", u) + fmt.Fprintf(cli.out, "Registry: %v\n", out.IndexServerAddress) + } } if !out.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") diff --git a/server.go b/server.go index 7607851f49..85e0c255cd 100644 --- a/server.go +++ b/server.go @@ -265,16 +265,17 @@ func (srv *Server) DockerInfo() *APIInfo { } return &APIInfo{ - Containers: len(srv.runtime.List()), - Images: imgcount, - MemoryLimit: srv.runtime.capabilities.MemoryLimit, - SwapLimit: srv.runtime.capabilities.SwapLimit, - Debug: os.Getenv("DEBUG") != "", - NFd: utils.GetTotalUsedFds(), - NGoroutines: runtime.NumGoroutine(), - LXCVersion: lxcVersion, - NEventsListener: len(srv.events), - KernelVersion: kernelVersion, + Containers: len(srv.runtime.List()), + Images: imgcount, + MemoryLimit: srv.runtime.capabilities.MemoryLimit, + SwapLimit: srv.runtime.capabilities.SwapLimit, + Debug: os.Getenv("DEBUG") != "", + NFd: utils.GetTotalUsedFds(), + NGoroutines: runtime.NumGoroutine(), + LXCVersion: lxcVersion, + NEventsListener: len(srv.events), + KernelVersion: kernelVersion, + IndexServerAddress: auth.IndexServerAddress(), } } From 965de6ef50ceb479168c9e4f7156fe2b855e4245 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Mon, 22 Jul 2013 16:09:11 -0400 Subject: [PATCH 105/242] fixing #910 --- commands.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands.go b/commands.go index 167d33ce1d..387717f360 100644 --- a/commands.go +++ b/commands.go @@ -495,6 +495,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener) fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion) } + if len(out.IndexServerAddress) != 0 { u := cli.configFile.Configs[out.IndexServerAddress].Username if len(u) > 0 { From b9149f45bf6bdef3b7025fe7c94a930696a4b4ab Mon Sep 17 00:00:00 2001 From: Manuel Meurer Date: Tue, 6 Aug 2013 15:21:26 +0200 Subject: [PATCH 106/242] Fix indentation in Vagrantfile --- Vagrantfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index aadabb8711..18aa5b5afb 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -82,15 +82,15 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| end if !FORWARD_DOCKER_PORTS.nil? - Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| - (49000..49900).each do |port| - config.vm.forward_port port, port - end + Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| + (49000..49900).each do |port| + config.vm.forward_port port, port end + end - Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| - (49000..49900).each do |port| - config.vm.network :forwarded_port, :host => port, :guest => port - end + Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| + (49000..49900).each do |port| + config.vm.network :forwarded_port, :host => port, :guest => port end + end end From ba17f4a06a75a66e11b6cf2ca2cdb5bee4f7bfa8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 6 Aug 2013 14:31:51 +0000 Subject: [PATCH 107/242] fix small \n error un docker build --- commands.go | 2 +- graph.go | 2 +- server.go | 8 ++++---- utils/utils.go | 18 +++++++++++++----- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/commands.go b/commands.go index 78916f3a7c..d562426a1a 100644 --- a/commands.go +++ b/commands.go @@ -196,7 +196,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // FIXME: ProgressReader shouldn't be this annoyning to use if context != nil { sf := utils.NewStreamFormatter(false) - body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("", "Uploading context", "%v bytes%0.0s%0.0s"), sf) + body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("", "Uploading context", "%v bytes%0.0s%0.0s"), sf, true) } // Upload the build context v := &url.Values{} diff --git a/graph.go b/graph.go index d02017bf8f..1d2a9f807f 100644 --- a/graph.go +++ b/graph.go @@ -159,7 +159,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *uti if err != nil { return nil, err } - return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf), tmp.Root) + return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root) } // Mktemp creates a temporary sub-directory inside the graph's filesystem. diff --git a/server.go b/server.go index 7607851f49..62fd2d55da 100644 --- a/server.go +++ b/server.go @@ -145,7 +145,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } - if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf), path); err != nil { + if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, true), path); err != nil { return "", err } // FIXME: Handle custom repo, tag comment, author @@ -438,7 +438,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return err } defer layer.Close() - if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf), img); err != nil { + if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf, false), img); err != nil { return err } } @@ -711,7 +711,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // Send the layer - if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "Pushing", "%8v/%v (%v)"), sf), ep, token, jsonRaw); err != nil { + if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "Pushing", "%8v/%v (%v)"), sf, true), ep, token, jsonRaw); err != nil { return "", err } else { imgData.Checksum = checksum @@ -789,7 +789,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write if err != nil { return err } - archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("", "Importing", "%8v/%v (%v)"), sf) + archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("", "Importing", "%8v/%v (%v)"), sf, true) } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { diff --git a/utils/utils.go b/utils/utils.go index e0083d02d8..d52dcafe17 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -73,6 +73,7 @@ type progressReader struct { lastUpdate int // How many bytes read at least update template string // Template to print. Default "%v/%v (%v)" sf *StreamFormatter + newLine bool } func (r *progressReader) Read(p []byte) (n int, err error) { @@ -94,17 +95,24 @@ func (r *progressReader) Read(p []byte) (n int, err error) { } r.lastUpdate = r.readProgress } + // Send newline when complete + if r.newLine && err != nil { + r.output.Write(r.sf.FormatStatus("", "")) + } return read, err } func (r *progressReader) Close() error { return io.ReadCloser(r.reader).Close() } -func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader { - tpl := string(template) - if tpl == "" { - tpl = string(sf.FormatProgress("", "%8v/%v (%v)", "")) +func ProgressReader(r io.ReadCloser, size int, output io.Writer, tpl []byte, sf *StreamFormatter, newline bool) *progressReader { + return &progressReader{ + reader: r, + output: NewWriteFlusher(output), + readTotal: size, + template: string(tpl), + sf: sf, + newLine: newline, } - return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf} } // HumanDuration returns a human-readable approximation of a duration From 754ed9043d6e1fed98160c7fb443bed0a9464acf Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sat, 3 Aug 2013 20:55:54 +0000 Subject: [PATCH 108/242] Use mime types to parse Content-Type --- api.go | 11 ++++++++++- api_test.go | 14 ++++++++++++++ commands.go | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index f520d7a73b..f5f0df1b98 100644 --- a/api.go +++ b/api.go @@ -9,6 +9,7 @@ import ( "io" "io/ioutil" "log" + "mime" "net" "net/http" "os" @@ -81,6 +82,14 @@ 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) @@ -594,7 +603,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 } diff --git a/api_test.go b/api_test.go index be535924b7..5316b4fc5e 100644 --- a/api_test.go +++ b/api_test.go @@ -1142,6 +1142,20 @@ func TestDeleteImages(t *testing.T) { } */ } +func TestJsonContentType(t *testing.T) { + if !matchesContentType("application/json", "application/json") { + t.Fail() + } + + if !matchesContentType("application/json; charset=utf-8", "application/json") { + t.Fail() + } + + if matchesContentType("dockerapplication/json", "application/json") { + t.Fail() + } +} + // Mocked types for tests type NopConn struct { io.ReadCloser diff --git a/commands.go b/commands.go index 78916f3a7c..c52126519a 100644 --- a/commands.go +++ b/commands.go @@ -1567,7 +1567,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e return fmt.Errorf("Error: %s", body) } - if resp.Header.Get("Content-Type") == "application/json" { + if matchesContentType(resp.Header.Get("Content-Type"), "application/json") { return utils.DisplayJSONMessagesStream(resp.Body, out) } else { if _, err := io.Copy(out, resp.Body); err != nil { From 5b8cfbe15c29efd3e72ea97ba87867590aeeba25 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 16 Jul 2013 19:07:41 -0900 Subject: [PATCH 109/242] Add cp command and copy api endpoint The cp command and copy api endpoint allows users to copy files and or folders from a containers filesystem. Closes #382 --- api.go | 30 ++++++++++ api_params.go | 5 ++ api_test.go | 64 +++++++++++++++++++++ commands.go | 32 +++++++++++ container.go | 7 +++ docs/sources/api/docker_remote_api_v1.3.rst | 33 ++++++++++- docs/sources/commandline/cli.rst | 1 + docs/sources/commandline/command/cp.rst | 13 +++++ docs/sources/commandline/index.rst | 1 + server.go | 17 ++++++ 10 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 docs/sources/commandline/command/cp.rst diff --git a/api.go b/api.go index f5f0df1b98..f9257b1ccd 100644 --- a/api.go +++ b/api.go @@ -871,6 +871,35 @@ 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{} + if r.Header.Get("Content-Type") == "application/json" { + if err := json.NewDecoder(r.Body).Decode(copyData); err != nil { + return err + } + } else { + return fmt.Errorf("Content-Type not supported: %s", r.Header.Get("Content-Type")) + } + + if copyData.Resource == "" { + return fmt.Errorf("Resource cannot be empty") + } + if copyData.Resource[0] == '/' { + return fmt.Errorf("Resource cannot contain a leading /") + } + + if err := srv.ContainerCopy(name, copyData.Resource, w); err != nil { + utils.Debugf("%s", err) + 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 @@ -918,6 +947,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, diff --git a/api_params.go b/api_params.go index 1ae26072e7..b75e54c116 100644 --- a/api_params.go +++ b/api_params.go @@ -86,3 +86,8 @@ type APIImageConfig struct { ID string `json:"Id"` *Config } + +type APICopy struct { + Resource string + HostPath string +} diff --git a/api_test.go b/api_test.go index 5316b4fc5e..52baf35f38 100644 --- a/api_test.go +++ b/api_test.go @@ -1156,6 +1156,70 @@ func TestJsonContentType(t *testing.T) { } } +func TestPostContainersCopy(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + builder := NewBuilder(runtime) + + // Create a container and remove a file + container, err := builder.Create( + &Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"touch", "/test.txt"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + copyData := APICopy{HostPath: ".", Resource: "test.txt"} + + jsonData, err := json.Marshal(copyData) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("POST", "/containers/"+container.ID+"/copy", bytes.NewReader(jsonData)) + if err != nil { + t.Fatal(err) + } + req.Header.Add("Content-Type", "application/json") + if err = postContainersCopy(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { + t.Fatal(err) + } + + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) + } + + found := false + for tarReader := tar.NewReader(r.Body); ; { + h, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + t.Fatal(err) + } + if h.Name == "test.txt" { + found = true + break + } + } + if !found { + t.Fatalf("The created test file has not been found in the copied output") + } +} + // Mocked types for tests type NopConn struct { io.ReadCloser diff --git a/commands.go b/commands.go index c52126519a..eb05202ded 100644 --- a/commands.go +++ b/commands.go @@ -77,6 +77,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"attach", "Attach to a running container"}, {"build", "Build a container from a Dockerfile"}, {"commit", "Create a new image from a container's changes"}, + {"cp", "Copy files/folders from the containers filesystem to the host path"}, {"diff", "Inspect changes on a container's filesystem"}, {"events", "Get real time events from the server"}, {"export", "Stream the contents of a container as a tar archive"}, @@ -1469,6 +1470,37 @@ func (cli *DockerCli) CmdRun(args ...string) error { return nil } +func (cli *DockerCli) CmdCp(args ...string) error { + cmd := Subcmd("cp", "CONTAINER:RESOURCE HOSTPATH", "Copy files/folders from the RESOURCE to the HOSTPATH") + if err := cmd.Parse(args); err != nil { + return nil + } + + if cmd.NArg() != 2 { + cmd.Usage() + return nil + } + + var copyData APICopy + info := strings.Split(cmd.Arg(0), ":") + + copyData.Resource = info[1] + copyData.HostPath = cmd.Arg(1) + + data, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData) + if err != nil { + return err + } + + r := bytes.NewReader(data) + if statusCode == 200 { + if err := Untar(r, copyData.HostPath); err != nil { + return err + } + } + return nil +} + func (cli *DockerCli) checkIfLogged(action string) error { // If condition AND the login failed if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { diff --git a/container.go b/container.go index 6c92973920..7791d4b4af 100644 --- a/container.go +++ b/container.go @@ -1089,3 +1089,10 @@ func (container *Container) GetSize() (int64, int64) { } return sizeRw, sizeRootfs } + +func (container *Container) Copy(resource string) (Archive, error) { + if err := container.EnsureMounted(); err != nil { + return nil, err + } + return TarFilter(container.RootfsPath(), Uncompressed, []string{resource}) +} diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 8e5c7b2a3b..fe3d204d4d 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -525,6 +525,38 @@ Remove a container :statuscode 500: server error +Copy files or folders from a container +************************************** + +.. http:post:: /containers/(id)/copy + + Copy files or folders of container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + 2.2 Images ---------- @@ -1091,7 +1123,6 @@ Monitor Docker's events :statuscode 200: no error :statuscode 500: server error - 3. Going further ================ diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index e499b1f096..bdc4827a60 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -30,6 +30,7 @@ Available Commands command/attach command/build command/commit + command/cp command/diff command/export command/history diff --git a/docs/sources/commandline/command/cp.rst b/docs/sources/commandline/command/cp.rst new file mode 100644 index 0000000000..14b5061ef7 --- /dev/null +++ b/docs/sources/commandline/command/cp.rst @@ -0,0 +1,13 @@ +:title: Cp Command +:description: Copy files/folders from the containers filesystem to the host path +:keywords: cp, docker, container, documentation, copy + +=========================================================== +``cp`` -- Copy files/folders from the containers filesystem to the host path +=========================================================== + +:: + + Usage: docker cp CONTAINER:RESOURCE HOSTPATH + + Copy files/folders from the containers filesystem to the host path. Paths are relative to the root of the filesystem. diff --git a/docs/sources/commandline/index.rst b/docs/sources/commandline/index.rst index a7296b27da..5c2b373205 100644 --- a/docs/sources/commandline/index.rst +++ b/docs/sources/commandline/index.rst @@ -15,6 +15,7 @@ Contents: attach build commit + cp diff export history diff --git a/server.go b/server.go index 7607851f49..c7b2ad2dcb 100644 --- a/server.go +++ b/server.go @@ -1169,6 +1169,23 @@ func (srv *Server) ImageInspect(name string) (*Image, error) { return nil, fmt.Errorf("No such image: %s", name) } +func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) error { + if container := srv.runtime.Get(name); container != nil { + + data, err := container.Copy(resource) + if err != nil { + return err + } + + if _, err := io.Copy(out, data); err != nil { + return err + } + return nil + } + return fmt.Errorf("No such container: %s", name) + +} + func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) From d94b1860806e11038086e91c693d86a009dcd3b0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 17 Jul 2013 05:25:49 -0900 Subject: [PATCH 110/242] Strip leading forward slash from resource --- api.go | 9 +++++---- api_test.go | 2 +- commands.go | 2 +- container.go | 16 +++++++++++++++- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/api.go b/api.go index f9257b1ccd..46fe8251e0 100644 --- a/api.go +++ b/api.go @@ -878,23 +878,24 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r * name := vars["name"] copyData := &APICopy{} - if r.Header.Get("Content-Type") == "application/json" { + 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", r.Header.Get("Content-Type")) + return fmt.Errorf("Content-Type not supported: %s", contentType) } if copyData.Resource == "" { return fmt.Errorf("Resource cannot be empty") } if copyData.Resource[0] == '/' { - return fmt.Errorf("Resource cannot contain a leading /") + copyData.Resource = copyData.Resource[1:] } if err := srv.ContainerCopy(name, copyData.Resource, w); err != nil { - utils.Debugf("%s", err) + utils.Debugf("%s", err.Error()) return err } return nil diff --git a/api_test.go b/api_test.go index 52baf35f38..928daf963c 100644 --- a/api_test.go +++ b/api_test.go @@ -1181,7 +1181,7 @@ func TestPostContainersCopy(t *testing.T) { } r := httptest.NewRecorder() - copyData := APICopy{HostPath: ".", Resource: "test.txt"} + copyData := APICopy{HostPath: ".", Resource: "/test.txt"} jsonData, err := json.Marshal(copyData) if err != nil { diff --git a/commands.go b/commands.go index eb05202ded..d4cf91ea10 100644 --- a/commands.go +++ b/commands.go @@ -1492,8 +1492,8 @@ func (cli *DockerCli) CmdCp(args ...string) error { return err } - r := bytes.NewReader(data) if statusCode == 200 { + r := bytes.NewReader(data) if err := Untar(r, copyData.HostPath); err != nil { return err } diff --git a/container.go b/container.go index 7791d4b4af..6a60597edb 100644 --- a/container.go +++ b/container.go @@ -1094,5 +1094,19 @@ func (container *Container) Copy(resource string) (Archive, error) { if err := container.EnsureMounted(); err != nil { return nil, err } - return TarFilter(container.RootfsPath(), Uncompressed, []string{resource}) + var filter []string + basePath := path.Join(container.RootfsPath(), resource) + stat, err := os.Stat(basePath) + if err != nil { + return nil, err + } + if !stat.IsDir() { + d, f := path.Split(basePath) + basePath = d + filter = []string{f} + } else { + filter = []string{path.Base(basePath)} + basePath = path.Dir(basePath) + } + return TarFilter(basePath, Uncompressed, filter) } From 583f5868c989b9da574638ffdc532826e5070347 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 6 Aug 2013 16:01:09 +0000 Subject: [PATCH 111/242] Move copy command docs to api 1.4 document --- docs/sources/api/docker_remote_api_v1.3.rst | 33 +-------------------- docs/sources/api/docker_remote_api_v1.4.rst | 32 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index fe3d204d4d..8e5c7b2a3b 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -525,38 +525,6 @@ Remove a container :statuscode 500: server error -Copy files or folders from a container -************************************** - -.. http:post:: /containers/(id)/copy - - Copy files or folders of container ``id`` - - **Example request**: - - .. sourcecode:: http - - POST /containers/4fa6e0f0c678/copy HTTP/1.1 - Content-Type: application/json - - { - "Resource":"test.txt" - } - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/octet-stream - - {{ STREAM }} - - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error - - 2.2 Images ---------- @@ -1123,6 +1091,7 @@ Monitor Docker's events :statuscode 200: no error :statuscode 500: server error + 3. Going further ================ diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 31e5b5f766..fe73cb5405 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -528,6 +528,38 @@ Remove a container :statuscode 500: server error +Copy files or folders from a container +************************************** + +.. http:post:: /containers/(id)/copy + + Copy files or folders of container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + 2.2 Images ---------- From 1d654f615673aecb32011bd5a28c2ac43dbbc1fb Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Tue, 6 Aug 2013 11:40:16 -0700 Subject: [PATCH 112/242] changed the twitter handle --- docs/sources/faq.rst | 2 +- docs/theme/docker/layout.html | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index 12b9751338..3cc0086c5d 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -47,7 +47,7 @@ Most frequently asked questions. .. _IRC, docker on freenode: irc://chat.freenode.net#docker .. _Github: http://www.github.com/dotcloud/docker .. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker - .. _Join the conversation on Twitter: http://twitter.com/getdocker + .. _Join the conversation on Twitter: http://twitter.com/docker Looking for something else to read? Checkout the :ref:`hello_world` example. diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index ca26f44dc0..d6bfff79ba 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -130,7 +130,7 @@ From 65c8e9242cc73fd2f1ea949703cf7da34e8e9650 Mon Sep 17 00:00:00 2001 From: shin- Date: Tue, 6 Aug 2013 21:08:27 +0200 Subject: [PATCH 113/242] brew: added support for push on private registries. --- contrib/brew/brew/brew.py | 14 ++++++++++---- contrib/brew/docker-brew | 12 +++++++----- contrib/brew/requirements.txt | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index 91f7b01b94..73d59877fb 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -17,7 +17,7 @@ processed = {} def build_library(repository=None, branch=None, namespace=None, push=False, - debug=False, prefill=True): + debug=False, prefill=True, registry=None): dst_folder = None summary = Summary() if repository is None: @@ -75,7 +75,8 @@ def build_library(repository=None, branch=None, namespace=None, push=False, logger.debug('Pulling {0} from official repository (cache ' 'fill)'.format(buildfile)) client.pull(buildfile) - img = build_repo(url, ref, buildfile, tag, namespace, push) + img = build_repo(url, ref, buildfile, tag, namespace, push, + registry) summary.add_success(buildfile, (linecnt, line), img) processed['{0}@{1}'.format(url, ref)] = img except Exception as e: @@ -88,7 +89,7 @@ def build_library(repository=None, branch=None, namespace=None, push=False, summary.print_summary(logger) -def build_repo(repository, ref, docker_repo, docker_tag, namespace, push): +def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry): docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo) img_id = None if '{0}@{1}'.format(repository, ref) not in processed.keys(): @@ -105,7 +106,12 @@ def build_repo(repository, ref, docker_repo, docker_tag, namespace, push): docker_tag or 'latest')) client.tag(img_id, docker_repo, docker_tag) if push: - logger.info('Pushing result to the main registry') + logger.info('Pushing result to registry {0}'.format( + registry or "default")) + if registry is not None: + docker_repo = '{0}/{1}'.format(registry, docker_repo) + logger.info('Also tagging {0}'.format(docker_repo)) + client.tag(img_id, docker_repo, docker_tag) client.push(docker_repo) return img_id diff --git a/contrib/brew/docker-brew b/contrib/brew/docker-brew index 29052771c6..ead6b65075 100755 --- a/contrib/brew/docker-brew +++ b/contrib/brew/docker-brew @@ -8,20 +8,22 @@ import brew if __name__ == '__main__': parser = argparse.ArgumentParser('Build the docker standard library') parser.add_argument('--push', action='store_true', default=False, - help='push generated repositories to the official registry') + help='Push generated repositories') parser.add_argument('--debug', default=False, action='store_true', help='Enable debugging output') parser.add_argument('--noprefill', default=True, action='store_false', dest='prefill', help='Disable cache prefill') parser.add_argument('-n', metavar='NAMESPACE', default='library', - help='namespace used for generated repositories.' + help='Namespace used for generated repositories.' ' Default is library') parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH, - help='branch in the repository where the library definition' + help='Branch in the repository where the library definition' ' files will be fetched. Default is ' + brew.DEFAULT_BRANCH) parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY, nargs='?', help='git repository containing the library definition' ' files. Default is ' + brew.DEFAULT_REPOSITORY) + parser.add_argument('--reg', default=None, help='Registry address to' + ' push build results to. Also sets push to true.') args = parser.parse_args() - brew.build_library(args.repository, args.b, args.n, args.push, args.debug, - args.prefill) + brew.build_library(args.repository, args.b, args.n, + args.push or args.reg is not None, args.debug, args.prefill, args.reg) diff --git a/contrib/brew/requirements.txt b/contrib/brew/requirements.txt index 8006177ce6..78a574953d 100644 --- a/contrib/brew/requirements.txt +++ b/contrib/brew/requirements.txt @@ -1,2 +1,2 @@ dulwich==0.9.0 -docker-py==0.1.2 \ No newline at end of file +docker-py==0.1.3 \ No newline at end of file From e0a6f27d1bbf053baf8a94c87fddd3f9ed30964c Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 6 Aug 2013 13:56:29 -0700 Subject: [PATCH 114/242] Suggest installing linux-headers by default. Address #1187 and #1031 --- docs/sources/installation/ubuntulinux.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index cf41b79266..587c8fe31b 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -34,13 +34,20 @@ Dependencies **Linux kernel 3.8** -Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in. +Due to a bug in LXC, docker works best on the 3.8 kernel. Precise +comes with a 3.2 kernel, so we need to upgrade it. The kernel we +install comes with AUFS built in. We also include the generic headers +to enable packages that depend on them, like ZFS and the VirtualBox +guest additions. If you didn't install the headers for your "precise" +kernel, then you can skip these headers for the "raring" kernel. But +it is safer to include them if you're not sure. .. code-block:: bash # install the backported kernel - sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring + sudo apt-get update + sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring # reboot sudo reboot From 9f1c9686e0ee47289adeeedd6cf8d5296a058463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Tue, 6 Aug 2013 17:24:10 -0700 Subject: [PATCH 115/242] change network range to avoid conflict with EC2 DNS --- network.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network.go b/network.go index eefd36df3b..156ca3b47d 100644 --- a/network.go +++ b/network.go @@ -131,8 +131,8 @@ func CreateBridgeIface(ifaceName string) error { // In theory this shouldn't matter - in practice there's bound to be a few scripts relying // on the internal addressing or other stupid things like that. // The shouldn't, but hey, let's not break them unless we really have to. - "172.16.42.1/16", - "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive + "172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23 + "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive "10.1.42.1/16", "10.42.42.1/16", "172.16.42.1/24", From fea2d5f2fe81d983efbdd803cef2fd1c70f98ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Tue, 6 Aug 2013 10:00:45 -0700 Subject: [PATCH 116/242] Let userland proxy handle container-bound traffic --- network.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/network.go b/network.go index eefd36df3b..2916fc5070 100644 --- a/network.go +++ b/network.go @@ -253,6 +253,7 @@ func (mapper *PortMapper) setup() error { func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error { return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port), + "!", "-i", NetworkBridgeIface, "-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) } @@ -264,7 +265,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { return err } mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) - proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}, backendAddr) + proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) if err != nil { mapper.Unmap(port, "tcp") return err @@ -278,7 +279,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { return err } mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) - proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}, backendAddr) + proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) if err != nil { mapper.Unmap(port, "udp") return err From 84790aafd8b3050e502a16f03ee288c1bb9c21b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Tue, 6 Aug 2013 18:31:05 -0700 Subject: [PATCH 117/242] relax the lo interface test to allow iface index != 1 --- container_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/container_test.go b/container_test.go index 2f9da15f6e..1050bfb2ad 100644 --- a/container_test.go +++ b/container_test.go @@ -1306,10 +1306,10 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1) if len(interfaces) != 1 { - t.Fatalf("Wrong interface count in test container: expected [1: lo], got [%s]", interfaces) + t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) } - if interfaces[0] != "1: lo" { - t.Fatalf("Wrong interface in test container: expected [1: lo], got [%s]", interfaces) + if !strings.HasSuffix(interfaces[0], ": lo") { + t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) } } From 6bbe66d2e6414a127f0f543c0138f1709bd3b8b4 Mon Sep 17 00:00:00 2001 From: David Sissitka Date: Wed, 7 Aug 2013 05:33:03 -0400 Subject: [PATCH 118/242] Updated the Docker CLI to specify a value for the "Host" header. --- commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/commands.go b/commands.go index f0e1695b3f..e0d33d7d36 100644 --- a/commands.go +++ b/commands.go @@ -1433,6 +1433,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, return nil, -1, err } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + req.Host = cli.addr if data != nil { req.Header.Set("Content-Type", "application/json") } else if method == "POST" { @@ -1474,6 +1475,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e return err } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + req.Host = cli.addr if method == "POST" { req.Header.Set("Content-Type", "plain/text") } @@ -1536,6 +1538,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Header.Set("Content-Type", "plain/text") + req.Host = cli.addr dial, err := net.Dial(cli.proto, cli.addr) if err != nil { From 416d0986886cd322ecae1114c13565bbc27b305c Mon Sep 17 00:00:00 2001 From: David Sissitka Date: Wed, 7 Aug 2013 05:35:38 -0400 Subject: [PATCH 119/242] Updated my last commit to use tabs instead of spaces. --- commands.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index e0d33d7d36..a47de9f2ba 100644 --- a/commands.go +++ b/commands.go @@ -1433,7 +1433,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, return nil, -1, err } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) - req.Host = cli.addr + req.Host = cli.addr if data != nil { req.Header.Set("Content-Type", "application/json") } else if method == "POST" { @@ -1475,7 +1475,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e return err } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) - req.Host = cli.addr + req.Host = cli.addr if method == "POST" { req.Header.Set("Content-Type", "plain/text") } @@ -1538,7 +1538,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Header.Set("Content-Type", "plain/text") - req.Host = cli.addr + req.Host = cli.addr dial, err := net.Dial(cli.proto, cli.addr) if err != nil { From 6115348dd9e302e8323b857ae8ab1061159e69cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dra=C5=BEen=20Lu=C4=8Danin?= Date: Wed, 7 Aug 2013 13:57:31 +0200 Subject: [PATCH 120/242] doc: syntax to run a specific image tag --- docs/sources/commandline/command/run.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index db043c3b3f..c5b363d787 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -8,7 +8,7 @@ :: - Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] + Usage: docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] Run a command in a new container From 3104fc8d33ce4c26a446826639a7b2dba524264f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 6 Aug 2013 16:58:52 +0000 Subject: [PATCH 121/242] Forbid certain paths within docker build ADD --- buildfile.go | 3 +++ buildfile_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/buildfile.go b/buildfile.go index 00c747d6d0..3c4229d8bf 100644 --- a/buildfile.go +++ b/buildfile.go @@ -278,6 +278,9 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error { if strings.HasSuffix(dest, "/") { destPath = destPath + "/" } + if !strings.HasPrefix(origPath, b.context) { + return fmt.Errorf("Forbidden path: %s", origPath) + } fi, err := os.Stat(origPath) if err != nil { return err diff --git a/buildfile_test.go b/buildfile_test.go index 9d002f0665..d89c40d16c 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -423,3 +423,52 @@ func TestBuildImageWithoutCache(t *testing.T) { t.Fail() } } + +func TestForbiddenContextPath(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + + context := testContextTemplate{` + from {IMAGE} + maintainer dockerio + add ../../ test/ + `, + [][2]string{{"test.txt", "test1"}, {"other.txt", "other"}}, nil} + + httpServer, err := mkTestingFileServer(context.remoteFiles) + if err != nil { + t.Fatal(err) + } + defer httpServer.Close() + + idx := strings.LastIndex(httpServer.URL, ":") + if idx < 0 { + t.Fatalf("could not get port from test http server address %s", httpServer.URL) + } + port := httpServer.URL[idx+1:] + + ip := srv.runtime.networkManager.bridgeNetwork.IP + dockerfile := constructDockerfile(context.dockerfile, ip, port) + + buildfile := NewBuildFile(srv, ioutil.Discard, false, true) + _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t)) + + if err == nil { + t.Log("Error should not be nil") + t.Fail() + } + + if err.Error() != "Forbidden path: /" { + t.Logf("Error message is not expected: %s", err.Error()) + t.Fail() + } +} From ced93bcabdc0dd0f6dcfa52174be552adf6c5bd1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 7 Aug 2013 19:07:39 +0000 Subject: [PATCH 122/242] Modify test to accept error from mkContainer --- server_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server_test.go b/server_test.go index 58aa66bd20..911a4b1c7f 100644 --- a/server_test.go +++ b/server_test.go @@ -171,7 +171,10 @@ func TestContainerTop(t *testing.T) { srv := &Server{runtime: runtime} defer nuke(runtime) - c, hostConfig := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t) + c, hostConfig, err := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t) + if err != nil { + t.Fatal(err) + } defer runtime.Destroy(c) if err := c.Start(hostConfig); err != nil { t.Fatal(err) From 4860df1689f2f98d65aece0f4768b8ae98301d1e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 7 Aug 2013 21:24:41 +0000 Subject: [PATCH 123/242] Add Michael Crosby to core maintainers --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index dc0d1a19f6..55cb07b72b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,5 +1,6 @@ Solomon Hykes Guillaume Charmes Victor Vieux +Michael Crosby api.go: Victor Vieux Vagrantfile: Daniel Mizyrycki From 10190be5d74bc4b13a885f2ba27c2299c868fd19 Mon Sep 17 00:00:00 2001 From: Colin Rice Date: Sat, 3 Aug 2013 18:06:58 -0400 Subject: [PATCH 124/242] Add warning when net.ipv4.ip_forwarding = 0 Added warnings to api.go, container.go, commands.go, and runtime.go Also updated APIInfo to return whether IPv4Forwarding is enabled --- api.go | 5 +++++ api_params.go | 1 + commands.go | 3 +++ container.go | 4 ++++ docs/sources/api/docker_remote_api_v1.4.rst | 3 ++- runtime.go | 11 +++++++++-- server.go | 1 + 7 files changed, 25 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index 46fe8251e0..0223cdd673 100644 --- a/api.go +++ b/api.go @@ -522,6 +522,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.IPv4Forwarding { + 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 diff --git a/api_params.go b/api_params.go index 2737943f41..df879f63ee 100644 --- a/api_params.go +++ b/api_params.go @@ -24,6 +24,7 @@ type APIInfo struct { NGoroutines int `json:",omitempty"` MemoryLimit bool `json:",omitempty"` SwapLimit bool `json:",omitempty"` + IPv4Forwarding bool `json:",omitempty"` LXCVersion string `json:",omitempty"` NEventsListener int `json:",omitempty"` KernelVersion string `json:",omitempty"` diff --git a/commands.go b/commands.go index 39b8eb69af..0ddcfad18d 100644 --- a/commands.go +++ b/commands.go @@ -510,6 +510,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error { if !out.SwapLimit { fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") } + if !out.IPv4Forwarding { + fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") + } return nil } diff --git a/container.go b/container.go index 6a60597edb..8721d45a55 100644 --- a/container.go +++ b/container.go @@ -534,6 +534,10 @@ func (container *Container) Start(hostConfig *HostConfig) error { container.Config.MemorySwap = -1 } + if !container.runtime.capabilities.IPv4Forwarding { + log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") + } + // Create the requested bind mounts binds := make(map[string]BindMap) // Define illegal container destinations diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index fe73cb5405..06e8f46f99 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -1025,7 +1025,8 @@ Display system-wide information "NFd": 11, "NGoroutines":21, "MemoryLimit":true, - "SwapLimit":false + "SwapLimit":false, + "IPv4Forwarding":true } :statuscode 200: no error diff --git a/runtime.go b/runtime.go index f4c5b4d380..894028354e 100644 --- a/runtime.go +++ b/runtime.go @@ -15,8 +15,9 @@ import ( ) type Capabilities struct { - MemoryLimit bool - SwapLimit bool + MemoryLimit bool + SwapLimit bool + IPv4Forwarding bool } type Runtime struct { @@ -240,6 +241,12 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) { if !runtime.capabilities.SwapLimit && !quiet { log.Printf("WARNING: Your kernel does not support cgroup swap limit.") } + + content, err3 := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward") + runtime.capabilities.IPv4Forwarding = err3 == nil && len(content) > 0 && content[0] == '1' + if !runtime.capabilities.IPv4Forwarding && !quiet { + log.Printf("WARNING: IPv4 forwarding is disabled.") + } } } diff --git a/server.go b/server.go index 6d6ae5c934..8a4ce0ccf9 100644 --- a/server.go +++ b/server.go @@ -269,6 +269,7 @@ func (srv *Server) DockerInfo() *APIInfo { Images: imgcount, MemoryLimit: srv.runtime.capabilities.MemoryLimit, SwapLimit: srv.runtime.capabilities.SwapLimit, + IPv4Forwarding: srv.runtime.capabilities.IPv4Forwarding, Debug: os.Getenv("DEBUG") != "", NFd: utils.GetTotalUsedFds(), NGoroutines: runtime.NumGoroutine(), From ccffa6976616ddf5fc8c83f84d17e53ca8e0bca9 Mon Sep 17 00:00:00 2001 From: Colin Rice Date: Sat, 3 Aug 2013 18:15:24 -0400 Subject: [PATCH 125/242] Add Colin Rice to AUTHORS file --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 773ad6e4d3..6f6c597722 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,6 +22,7 @@ Caleb Spare Calen Pennington Charles Hooper Christopher Currie +Colin Rice Daniel Gasienica Daniel Mizyrycki Daniel Robinson From 3e491f8698860ed92b9f4cd6790eda82e083ae1e Mon Sep 17 00:00:00 2001 From: Colin Rice Date: Mon, 5 Aug 2013 18:26:06 -0400 Subject: [PATCH 126/242] Fix reversed IPv4Forwarding check in api.go --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index 0223cdd673..8eb892f259 100644 --- a/api.go +++ b/api.go @@ -522,7 +522,7 @@ 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.IPv4Forwarding { + if !srv.runtime.capabilities.IPv4Forwarding { log.Println("Warning: IPv4 forwarding is disabled.") out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.") } From 6a6a2ad8a4af85b543a24c5d22a2be5fdfc252d6 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 7 Aug 2013 17:23:49 -0700 Subject: [PATCH 127/242] Make sure ENV instruction within build perform a commit each time --- buildfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildfile.go b/buildfile.go index 3c4229d8bf..b969f758ec 100644 --- a/buildfile.go +++ b/buildfile.go @@ -172,9 +172,9 @@ func (b *buildFile) CmdEnv(args string) error { if envKey >= 0 { b.config.Env[envKey] = replacedVar - return nil + } else { + b.config.Env = append(b.config.Env, replacedVar) } - b.config.Env = append(b.config.Env, replacedVar) return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar)) } From 075d30dbced0e00843153a6975daa1291e90b339 Mon Sep 17 00:00:00 2001 From: Karan Lyons Date: Fri, 5 Jul 2013 16:16:58 -0700 Subject: [PATCH 128/242] Mount /dev/shm as a tmpfs Fixes #1122. --- lxc_template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxc_template.go b/lxc_template.go index d49d66b4d9..617e3d5e2d 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -81,7 +81,7 @@ lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0 #lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0 #lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0 -#lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 +lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 # Inject docker-init lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/.dockerinit none bind,ro 0 0 From c804a5f827f31c5300f34ec4614a5067a0c17456 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Aug 2013 14:56:37 +0000 Subject: [PATCH 129/242] rebase master --- server_test.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/server_test.go b/server_test.go index 0e683a4909..19923eb7dd 100644 --- a/server_test.go +++ b/server_test.go @@ -210,7 +210,7 @@ func TestContainerTop(t *testing.T) { srv := &Server{runtime: runtime} defer nuke(runtime) - c, hostConfig := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t) + c, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t) defer runtime.Destroy(c) if err := c.Start(hostConfig); err != nil { t.Fatal(err) @@ -222,21 +222,33 @@ func TestContainerTop(t *testing.T) { if !c.State.Running { t.Errorf("Container should be running") } - procs, err := srv.ContainerTop(c.ID) + procs, err := srv.ContainerTop(c.ID, "") if err != nil { t.Fatal(err) } - if len(procs) != 2 { - t.Fatalf("Expected 2 processes, found %d.", len(procs)) + if len(procs.Processes) != 2 { + t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes)) } - if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" { - t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd) + pos := -1 + for i := 0; i < len(procs.Titles); i++ { + if procs.Titles[i] == "CMD" { + pos = i + break + } } - if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" { - t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd) + if pos == -1 { + t.Fatalf("Expected CMD, not found.") + } + + if procs.Processes[0][pos] != "sh" && procs.Processes[0][pos] != "busybox" { + t.Fatalf("Expected `busybox` or `sh`, found %s.", procs.Processes[0][pos]) + } + + if procs.Processes[1][pos] != "sh" && procs.Processes[1][pos] != "busybox" { + t.Fatalf("Expected `busybox` or `sh`, found %s.", procs.Processes[1][pos]) } } From 18fc707fdf06aeb50fa5250f59f0ef4597d7cf73 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 8 Aug 2013 11:25:02 -0700 Subject: [PATCH 130/242] Make sure all needed mountpoint are present --- graph.go | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/graph.go b/graph.go index 1d2a9f807f..606a6833ee 100644 --- a/graph.go +++ b/graph.go @@ -194,15 +194,41 @@ func (graph *Graph) getDockerInitLayer() (string, error) { // For all other errors, abort. return "", err } - // FIXME: how the hell do I break down this line in a way - // that is idiomatic and not ugly as hell? - if f, err := os.OpenFile(path.Join(initLayer, ".dockerinit"), os.O_CREATE|os.O_TRUNC, 0700); err != nil && !os.IsExist(err) { - // If file already existed, keep going. - // For all other errors, abort. - return "", err - } else { - f.Close() + + for pth, typ := range map[string]string{ + "/dev/pts": "dir", + "/dev/shm": "dir", + "/proc": "dir", + "/sys": "dir", + "/.dockerinit": "file", + "/etc/resolv.conf": "file", + // "var/run": "dir", + // "var/lock": "dir", + } { + if _, err := os.Stat(path.Join(initLayer, pth)); err != nil { + if os.IsNotExist(err) { + switch typ { + case "dir": + if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil { + return "", err + } + case "file": + if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil { + return "", err + } + + if f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755); err != nil { + return "", err + } else { + f.Close() + } + } + } else { + return "", err + } + } } + // Layer is ready to use, if it wasn't before. return initLayer, nil } From ceb33818cdce434281889b75c278e43b85aec647 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 7 Aug 2013 16:35:38 -0700 Subject: [PATCH 131/242] Improve TestGetContainersTop so it does not rely on sleep --- api_test.go | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/api_test.go b/api_test.go index 928daf963c..9ab00cd759 100644 --- a/api_test.go +++ b/api_test.go @@ -461,26 +461,48 @@ func TestGetContainersTop(t *testing.T) { container, err := builder.Create( &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/sh", "-c", "sleep 2"}, + Image: GetTestImage(runtime).ID, + Cmd: []string{"/bin/sh", "-c", "cat"}, + OpenStdin: true, }, ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) + defer func() { + // Make sure the process dies before destorying runtime + container.stdin.Close() + container.WaitTimeout(2 * time.Second) + }() + hostConfig := &HostConfig{} if err := container.Start(hostConfig); err != nil { t.Fatal(err) } - // Give some time to the process to start - container.WaitTimeout(500 * time.Millisecond) + setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { + for { + if container.State.Running { + break + } + time.Sleep(10 * time.Millisecond) + } + }) if !container.State.Running { - t.Errorf("Container should be running") + t.Fatalf("Container should be running") } + // Make sure sh spawn up cat + setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { + in, _ := container.StdinPipe() + out, _ := container.StdoutPipe() + if err := assertPipe("hello\n", "hello", out, in, 15); err != nil { + t.Fatal(err) + } + }) + r := httptest.NewRecorder() req, err := http.NewRequest("GET", "/"+container.ID+"/top?ps_args=u", bytes.NewReader([]byte{})) if err != nil { @@ -504,11 +526,11 @@ func TestGetContainersTop(t *testing.T) { if len(procs.Processes) != 2 { t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes)) } - if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "sleep" { - t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[0][10]) + if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "cat" { + t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[0][10]) } - if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "sleep" { - t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[1][10]) + if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "cat" { + t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[1][10]) } } From 213365c2d27734bfbeeb1fc102fded7625550982 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Aug 2013 19:15:02 +0000 Subject: [PATCH 132/242] fix docker build and docker events output --- server.go | 9 +++++---- utils/utils.go | 12 ++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/server.go b/server.go index 8b15ecf1b0..f06b5ce68e 100644 --- a/server.go +++ b/server.go @@ -422,7 +422,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin // FIXME: Launch the getRemoteImage() in goroutines for _, id := range history { if !srv.runtime.graph.Exists(id) { - out.Write(sf.FormatStatus(utils.TruncateID(id), "Pulling metadata")) + out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "metadata")) imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token) if err != nil { // FIXME: Keep goging in case of error? @@ -434,7 +434,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin } // Get the layer - out.Write(sf.FormatStatus(utils.TruncateID(id), "Pulling fs layer")) + out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "fs layer")) layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token) if err != nil { return err @@ -500,7 +500,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName errors <- nil return } - out.Write(sf.FormatStatus(utils.TruncateID(img.ID), "Pulling image (%s) from %s", img.Tag, localName)) + out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Pulling", fmt.Sprintf("image (%s) from %s", img.Tag, localName))) success := false for _, ep := range repoData.Endpoints { if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { @@ -716,11 +716,12 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // Send the layer - if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "Pushing", "%8v/%v (%v)"), sf, true), ep, token, jsonRaw); err != nil { + if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "Pushing", "%8v/%v (%v)"), sf, false), ep, token, jsonRaw); err != nil { return "", err } else { imgData.Checksum = checksum } + out.Write(sf.FormatStatus("", "")) // Send the checksum if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil { diff --git a/utils/utils.go b/utils/utils.go index d52dcafe17..df21e75ae4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -79,7 +79,6 @@ type progressReader struct { func (r *progressReader) Read(p []byte) (n int, err error) { read, err := io.ReadCloser(r.reader).Read(p) r.readProgress += read - updateEvery := 1024 * 512 //512kB if r.readTotal > 0 { // Update progress for every 1% read if 1% < 512kB @@ -645,7 +644,6 @@ func (jm *JSONMessage) Display(out io.Writer) error { } return jm.Error } - fmt.Fprintf(out, "%c[2K", 27) if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } @@ -653,28 +651,26 @@ func (jm *JSONMessage) Display(out io.Writer) error { fmt.Fprintf(out, "%s: ", jm.ID) } if jm.Progress != "" { + fmt.Fprintf(out, "%c[2K", 27) fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) } else { - fmt.Fprintf(out, "%s\r", jm.Status) - } - if jm.ID == "" { - fmt.Fprintf(out, "\n") + fmt.Fprintf(out, "%s\r\n", jm.Status) } return nil } func DisplayJSONMessagesStream(in io.Reader, out io.Writer) error { dec := json.NewDecoder(in) - jm := JSONMessage{} ids := make(map[string]int) diff := 0 for { + jm := JSONMessage{} if err := dec.Decode(&jm); err == io.EOF { break } else if err != nil { return err } - if jm.ID != "" { + if jm.Progress != "" && jm.ID != "" { line, ok := ids[jm.ID] if !ok { line = len(ids) From 1ce9b3ca9c2ca2b2060e39a217d2e496c9a31826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Thu, 8 Aug 2013 17:02:59 -0700 Subject: [PATCH 133/242] switch from http to https --- contrib/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/install.sh b/contrib/install.sh index e58f054685..04340e2acb 100755 --- a/contrib/install.sh +++ b/contrib/install.sh @@ -3,7 +3,7 @@ # Original version by Jeff Lindsay # Revamped by Jerome Petazzoni # -# This script canonical location is http://get.docker.io/; to update it, run: +# This script canonical location is https://get.docker.io/; to update it, run: # s3cmd put -m text/x-shellscript -P install.sh s3://get.docker.io/index echo "Ensuring basic dependencies are installed..." @@ -36,7 +36,7 @@ else fi echo "Downloading docker binary and uncompressing into /usr/local/bin..." -curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz | +curl -s https://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz | tar -C /usr/local/bin --strip-components=1 -zxf- \ docker-latest/docker From 6178dc7f1be0d3c90ed02e8ccbafa4d7e103508d Mon Sep 17 00:00:00 2001 From: shin- Date: Fri, 9 Aug 2013 16:40:28 +0200 Subject: [PATCH 134/242] brew: added safeguards in script and changed default branch to 'master' --- contrib/brew/README.md | 2 +- contrib/brew/brew/brew.py | 10 ++++++++-- contrib/brew/docker-brew | 10 ++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/contrib/brew/README.md b/contrib/brew/README.md index d50e66f2a8..026758e876 100644 --- a/contrib/brew/README.md +++ b/contrib/brew/README.md @@ -8,7 +8,7 @@ docker-brew is a command-line tool used to build the docker standard library. 1. Install the easy_install tool (`sudo apt-get install python-setuptools` for Debian) 1. Install the python package manager, `pip` (`easy_install pip`) -1. Run the following command: `pip install -r requirements.txt` +1. Run the following command: `sudo pip install -r requirements.txt` 1. You should now be able to use the `docker-brew` script as such. ## Basics diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index 73d59877fb..8cbbaca06a 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -7,7 +7,7 @@ import docker import git DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker' -DEFAULT_BRANCH = 'library' +DEFAULT_BRANCH = 'master' logger = logging.getLogger(__name__) logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', @@ -36,7 +36,13 @@ def build_library(repository=None, branch=None, namespace=None, push=False, if not dst_folder: logger.info('Cloning docker repo from {0}, branch: {1}'.format( repository, branch)) - dst_folder = git.clone_branch(repository, branch) + try: + dst_folder = git.clone_branch(repository, branch) + except Exception as e: + logger.exception(e) + logger.error('Source repository could not be fetched. Check ' + 'that the address is correct and the branch exists.') + return for buildfile in os.listdir(os.path.join(dst_folder, 'library')): if buildfile == 'MAINTAINERS': continue diff --git a/contrib/brew/docker-brew b/contrib/brew/docker-brew index ead6b65075..c3fc147cf5 100755 --- a/contrib/brew/docker-brew +++ b/contrib/brew/docker-brew @@ -1,9 +1,15 @@ #!/usr/bin/env python import argparse +import sys -import brew - +try: + import brew +except ImportError as e: + print str(e) + print 'Please install the required dependencies first' + print 'sudo pip install -r requirements.txt' + sys.exit(1) if __name__ == '__main__': parser = argparse.ArgumentParser('Build the docker standard library') From 88cb9f3116e41b00b00fdccf6359a555e87061bd Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 9 Aug 2013 20:33:17 +0300 Subject: [PATCH 135/242] keep processing signals after the first one --- commands.go | 1 - 1 file changed, 1 deletion(-) diff --git a/commands.go b/commands.go index db8a126696..d045625c73 100644 --- a/commands.go +++ b/commands.go @@ -1403,7 +1403,6 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err := cli.CmdStop("-t", "4", runResult.ID); err != nil { fmt.Printf("failed to stop container:", err) } - return } } }() From db9d68c3e42a187a735082776b671d23651f4334 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 9 Aug 2013 10:50:58 -0700 Subject: [PATCH 136/242] Improve TestKillDifferentUser to prevent timeout on buildbot --- container_test.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/container_test.go b/container_test.go index 1050bfb2ad..c09c9f48f9 100644 --- a/container_test.go +++ b/container_test.go @@ -401,22 +401,24 @@ func TestOutput(t *testing.T) { func TestKillDifferentUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) + container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"tail", "-f", "/etc/resolv.conf"}, - User: "daemon", + Image: GetTestImage(runtime).ID, + Cmd: []string{"cat"}, + OpenStdin: true, + User: "daemon", }, ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) + defer container.stdin.Close() if container.State.Running { t.Errorf("Container shouldn't be running") } - hostConfig := &HostConfig{} - if err := container.Start(hostConfig); err != nil { + if err := container.Start(&HostConfig{}); err != nil { t.Fatal(err) } @@ -426,8 +428,13 @@ func TestKillDifferentUser(t *testing.T) { } }) - // Even if the state is running, lets give some time to lxc to spawn the process - container.WaitTimeout(500 * time.Millisecond) + setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { + out, _ := container.StdoutPipe() + in, _ := container.StdinPipe() + if err := assertPipe("hello\n", "hello", out, in, 15); err != nil { + t.Fatal(err) + } + }) if err := container.Kill(); err != nil { t.Fatal(err) From 2ba5c915473ce6fe769fb059db4120e2a21fb42e Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 9 Aug 2013 23:23:27 +0300 Subject: [PATCH 137/242] minor cleanup for signal handling --- commands.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index d045625c73..ffe4ce230e 100644 --- a/commands.go +++ b/commands.go @@ -1396,13 +1396,10 @@ func (cli *DockerCli) CmdRun(args ...string) error { signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) go func() { - for { - sig := <-signals - if sig == syscall.SIGINT || sig == syscall.SIGTERM { - fmt.Printf("\nReceived signal: %s; cleaning up\n", sig) - if err := cli.CmdStop("-t", "4", runResult.ID); err != nil { - fmt.Printf("failed to stop container:", err) - } + for sig := range signals { + fmt.Printf("\nReceived signal: %s; cleaning up\n", sig) + if err := cli.CmdStop("-t", "4", runResult.ID); err != nil { + fmt.Printf("failed to stop container:", err) } } }() From 641ddaeb03f8b8eee5c1ca11e3024976378ceb6d Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 9 Aug 2013 23:27:34 +0300 Subject: [PATCH 138/242] add formatting directive to failure to stop container error --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index ffe4ce230e..85b5c9abe9 100644 --- a/commands.go +++ b/commands.go @@ -1399,7 +1399,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { for sig := range signals { fmt.Printf("\nReceived signal: %s; cleaning up\n", sig) if err := cli.CmdStop("-t", "4", runResult.ID); err != nil { - fmt.Printf("failed to stop container:", err) + fmt.Printf("failed to stop container: %v", err) } } }() From 4ff649a4eaf5c653176d2fecbfdb6571f0d0d174 Mon Sep 17 00:00:00 2001 From: Kevin Clark Date: Fri, 9 Aug 2013 12:56:37 -0700 Subject: [PATCH 139/242] Only count known instructions as build steps stepN is only used in the log line, so if we only produce the log line when there's a message, it should do the right thing. If it's *not* a valid instruction, it gets a line as well, so there's no reason to double up. --- buildfile.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/buildfile.go b/buildfile.go index b969f758ec..a4f89091d1 100644 --- a/buildfile.go +++ b/buildfile.go @@ -488,15 +488,16 @@ func (b *buildFile) Build(context io.Reader) (string, error) { } instruction := strings.ToLower(strings.Trim(tmp[0], " ")) arguments := strings.Trim(tmp[1], " ") - stepN += 1 - // FIXME: only count known instructions as build steps - fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments) method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:])) if !exists { fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction)) continue } + + stepN += 1 + fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments) + ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface() if ret != nil { return "", ret.(error) From 722d4e916a727716f7f7b330c8fbea4bc089d62f Mon Sep 17 00:00:00 2001 From: Kevin Clark Date: Fri, 9 Aug 2013 14:39:03 -0700 Subject: [PATCH 140/242] Add myself to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 6f6c597722..5f2f0ec6a4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -62,6 +62,7 @@ Jérôme Petazzoni Karan Lyons Keli Hu Ken Cochrane +Kevin Clark Kevin J. Lynagh kim0 Kimbro Staken From 68b09cbe3d0a13bd1dbcaa39a63788fe938d0ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Fri, 9 Aug 2013 16:03:05 -0700 Subject: [PATCH 141/242] fix typo in TestBindMounts (runContainer called without image) --- container_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container_test.go b/container_test.go index 1050bfb2ad..aca53e5eb3 100644 --- a/container_test.go +++ b/container_test.go @@ -1174,7 +1174,7 @@ func TestBindMounts(t *testing.T) { readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist // test mounting to an illegal destination directory - if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil { + if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "_", "ls", "."}, nil); err == nil { t.Fatal("Container bind mounted illegal directory") } } From 3b23f02229fd3f4928934f317d41b8f497cec9a9 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 9 Aug 2013 19:52:05 -0400 Subject: [PATCH 142/242] Fix typo: fmt.Sprint -> fmt.Sprintf --- registry/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 5b8480d183..1e0e1815d2 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -391,7 +391,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return utils.NewHTTPRequestError(fmt.Sprint("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } var jsonBody map[string]string if err := json.Unmarshal(errBody, &jsonBody); err != nil { From 1408f08c40f73ccf0204334ceb44ac482c5641e9 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 9 Aug 2013 20:09:42 -0400 Subject: [PATCH 143/242] Update AUTHORS --- .mailmap | 3 ++- AUTHORS | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 11ff5357d8..adeac2d7ff 100644 --- a/.mailmap +++ b/.mailmap @@ -1,4 +1,4 @@ -# Generate AUTHORS: git log --all --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12 +# Generate AUTHORS: git log --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12 @@ -25,3 +25,4 @@ Walter Stanish Roberto Hashioka Konstantin Pelykh David Sissitka +Nolan Darilek diff --git a/AUTHORS b/AUTHORS index 773ad6e4d3..a9442d8637 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,6 +39,7 @@ Erno Hopearuoho Evan Wies ezbercih Fabrizio Regini +Fareed Dudhia Flavio Castelli Francisco Souza Frederick F. Kautz IV @@ -81,6 +82,7 @@ Nelson Chen Niall O'Higgins Nick Stenning Nick Stinemates +Nolan Darilek odk- Paul Bowsher Paul Hammond @@ -104,6 +106,7 @@ Thomas Hansen Tianon Gravi Tim Terhorst Tobias Bieniek +Tobias Schmidt Tobias Schwab Tom Hulihan unclejack From 7c50221de5ec1066f0f04d6db94ee9c2fef98efb Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 9 Aug 2013 20:42:20 -0400 Subject: [PATCH 144/242] Use ranged for loop on channels --- api.go | 3 +-- commands.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index 95b9c98de8..81d1e3aab1 100644 --- a/api.go +++ b/api.go @@ -223,8 +223,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 diff --git a/commands.go b/commands.go index a2099b9607..42fb8cdc48 100644 --- a/commands.go +++ b/commands.go @@ -1696,8 +1696,7 @@ func (cli *DockerCli) monitorTtySize(id string) error { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGWINCH) go func() { - for { - <-sigchan + for _ = range sigchan { cli.resizeTty(id) } }() From 4dc04d7690a4d634d9afd93821bb8e78620c5f9f Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 9 Aug 2013 21:16:44 -0400 Subject: [PATCH 145/242] Add GitHub usernames to MAINTAINERS --- MAINTAINERS | 12 ++++++------ docs/MAINTAINERS | 4 ++-- docs/sources/api/MAINTAINERS | 2 +- docs/theme/MAINTAINERS | 2 +- hack/dockerbuilder/MAINTAINERS | 2 +- hack/infrastructure/MAINTAINERS | 4 ++-- library/MAINTAINERS | 2 +- packaging/MAINTAINERS | 2 +- registry/MAINTAINERS | 6 +++--- term/MAINTAINERS | 4 ++-- testing/MAINTAINERS | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 55cb07b72b..96469c9814 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,6 +1,6 @@ -Solomon Hykes -Guillaume Charmes -Victor Vieux -Michael Crosby -api.go: Victor Vieux -Vagrantfile: Daniel Mizyrycki +Solomon Hykes (@shykes) +Guillaume Charmes (@creack) +Victor Vieux (@vieux) +Michael Crosby (@crosbymichael) +api.go: Victor Vieux (@vieux) +Vagrantfile: Daniel Mizyrycki (@mzdaniel) diff --git a/docs/MAINTAINERS b/docs/MAINTAINERS index f079e58481..a506ce11df 100644 --- a/docs/MAINTAINERS +++ b/docs/MAINTAINERS @@ -1,2 +1,2 @@ -Andy Rothfusz -Ken Cochrane +Andy Rothfusz (@metalivedev) +Ken Cochrane (@kencochrane) diff --git a/docs/sources/api/MAINTAINERS b/docs/sources/api/MAINTAINERS index e1c6f2ccfc..1887dfc232 100644 --- a/docs/sources/api/MAINTAINERS +++ b/docs/sources/api/MAINTAINERS @@ -1 +1 @@ -Solomon Hykes +Solomon Hykes (@shykes) diff --git a/docs/theme/MAINTAINERS b/docs/theme/MAINTAINERS index 606a1dd746..93231b1223 100644 --- a/docs/theme/MAINTAINERS +++ b/docs/theme/MAINTAINERS @@ -1 +1 @@ -Thatcher Peskens +Thatcher Peskens (@dhrp) diff --git a/hack/dockerbuilder/MAINTAINERS b/hack/dockerbuilder/MAINTAINERS index 228bd562e5..5dfc881420 100644 --- a/hack/dockerbuilder/MAINTAINERS +++ b/hack/dockerbuilder/MAINTAINERS @@ -1 +1 @@ -Daniel Mizyrycki +Daniel Mizyrycki (@mzdaniel) diff --git a/hack/infrastructure/MAINTAINERS b/hack/infrastructure/MAINTAINERS index 490b0c1ea2..bd089c55f4 100644 --- a/hack/infrastructure/MAINTAINERS +++ b/hack/infrastructure/MAINTAINERS @@ -1,2 +1,2 @@ -Ken Cochrane -Jerome Petazzoni +Ken Cochrane (@kencochrane) +Jerome Petazzoni (@jpetazzo) diff --git a/library/MAINTAINERS b/library/MAINTAINERS index b1c52939ef..6ac7d8cd79 100644 --- a/library/MAINTAINERS +++ b/library/MAINTAINERS @@ -1 +1 @@ -Joffrey Fuhrer +Joffrey Fuhrer (@shin-) diff --git a/packaging/MAINTAINERS b/packaging/MAINTAINERS index 228bd562e5..5dfc881420 100644 --- a/packaging/MAINTAINERS +++ b/packaging/MAINTAINERS @@ -1 +1 @@ -Daniel Mizyrycki +Daniel Mizyrycki (@mzdaniel) diff --git a/registry/MAINTAINERS b/registry/MAINTAINERS index b11dfc061b..bf3984f5f9 100644 --- a/registry/MAINTAINERS +++ b/registry/MAINTAINERS @@ -1,3 +1,3 @@ -Sam Alba -Joffrey Fuhrer -Ken Cochrane +Sam Alba (@samalba) +Joffrey Fuhrer (@shin-) +Ken Cochrane (@kencochrane) diff --git a/term/MAINTAINERS b/term/MAINTAINERS index 8df21a4601..48d4d91b2a 100644 --- a/term/MAINTAINERS +++ b/term/MAINTAINERS @@ -1,2 +1,2 @@ -Guillaume Charmes -Solomon Hykes +Guillaume Charmes (@creack) +Solomon Hykes (@shykes) diff --git a/testing/MAINTAINERS b/testing/MAINTAINERS index 228bd562e5..5dfc881420 100644 --- a/testing/MAINTAINERS +++ b/testing/MAINTAINERS @@ -1 +1 @@ -Daniel Mizyrycki +Daniel Mizyrycki (@mzdaniel) From d6e5c2c276de458f36034d46f7b7f0f8efd24491 Mon Sep 17 00:00:00 2001 From: Kawsar Saiyeed Date: Sat, 10 Aug 2013 02:38:11 +0100 Subject: [PATCH 146/242] Add initial bash completion script The script can be used to auto-complete commands, image names and container ids from within a bash prompt. This partially resolves #336. --- contrib/docker.bash | 434 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 contrib/docker.bash diff --git a/contrib/docker.bash b/contrib/docker.bash new file mode 100644 index 0000000000..dcabdf252c --- /dev/null +++ b/contrib/docker.bash @@ -0,0 +1,434 @@ +#!bash + +have docker && { +__docker_containers() +{ + local containers + containers="$( docker ps -a -q )" + COMPREPLY=( $( compgen -W "$containers" -- "$cur" ) ) +} + +__docker_image_repos() +{ + local repos + repos="$( docker images | awk 'NR>1{print $1}' )" + COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) ) +} + +__docker_images() +{ + local images + images="$( docker images | awk 'NR>1{print $1":"$2}' )" + COMPREPLY=( $( compgen -W "$images" -- "$cur" ) ) + __ltrim_colon_completions "$cur" +} + +__docker_image_repos_and_tags() +{ + local repos images + repos="$( docker images | awk 'NR>1{print $1}' )" + images="$( docker images | awk 'NR>1{print $1":"$2}' )" + COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) ) + __ltrim_colon_completions "$cur" +} + +__docker_containers_and_images() +{ + local containers images + containers="$( docker ps -a -q )" + images="$( docker images | awk 'NR>1{print $1":"$2}' )" + COMPREPLY=( $( compgen -W "$images $containers" -- "$cur" ) ) + __ltrim_colon_completions "$cur" +} + +_docker_docker() +{ + case "$prev" in + -H) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-H" -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W "$commands help" -- "$cur" ) ) + ;; + esac +} + +_docker_attach() +{ + if [ $cpos -eq $cword ]; then + __docker_containers + fi +} + +_docker_build() +{ + case "$prev" in + -t) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-t -q" -- "$cur" ) ) + ;; + *) + _filedir + ;; + esac +} + +_docker_commit() +{ + case "$prev" in + -author|-m|-run) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-author -m -run" -- "$cur" ) ) + ;; + *) + __docker_containers + ;; + esac +} + +_docker_diff() +{ + if [ $cpos -eq $cword ]; then + __docker_containers + fi +} + +_docker_events() +{ + COMPREPLY=( $( compgen -W "-since" -- "$cur" ) ) +} + +_docker_export() +{ + if [ $cpos -eq $cword ]; then + __docker_containers + fi +} + +_docker_help() +{ + if [ $cpos -eq $cword ]; then + COMPREPLY=( $( compgen -W "$commands" -- "$cur" ) ) + fi +} + +_docker_history() +{ + if [ $cpos -eq $cword ]; then + __docker_image_repos_and_tags + fi +} + +_docker_images() +{ + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) ) + ;; + *) + local counter=$cpos + while [ $counter -le $cword ]; do + case "${words[$counter]}" in + -*) + ;; + *) + break + ;; + esac + (( counter++ )) + done + + if [ $counter -eq $cword ]; then + __docker_image_repos + fi + ;; + esac +} + +_docker_import() +{ + return +} + +_docker_info() +{ + return +} + +_docker_insert() +{ + if [ $cpos -eq $cword ]; then + __docker_image_repos_and_tags + fi +} + +_docker_inspect() +{ + __docker_containers_and_images +} + +_docker_kill() +{ + __docker_containers +} + +_docker_login() +{ + COMPREPLY=( $( compgen -W "-e -p -u" -- "$cur" ) ) +} + +_docker_logs() +{ + if [ $cpos -eq $cword ]; then + __docker_containers + fi +} + +_docker_port() +{ + if [ $cpos -eq $cword ]; then + __docker_containers + fi +} + +_docker_ps() +{ + COMPREPLY=( $( compgen -W "-a -beforeId -l -n -notrunc -q -s -sinceId" -- "$cur" ) ) +} + +_docker_pull() +{ + COMPREPLY=( $( compgen -W "-t" -- "$cur" ) ) +} + +_docker_push() +{ + return +} + +_docker_restart() +{ + case "$prev" in + -t) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-t" -- "$cur" ) ) + ;; + *) + __docker_containers + ;; + esac +} + +_docker_rm() +{ + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-v" -- "$cur" ) ) + ;; + *) + __docker_containers + ;; + esac +} + +_docker_rmi() +{ + __docker_image_repos_and_tags +} + +_docker_run() +{ + case "$prev" in + -cidfile) + _filedir + ;; + -volumes-from) + __docker_containers + ;; + -a|-c|-dns|-e|-h|-m|-p|-u|-v) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-a -c -cidfile -d -dns -e -entrypoint -h -i -m -n -p -t -u -v -volumes-from" -- "$cur" ) ) + ;; + *) + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) ) + ;; + *) + local counter=$cpos + while [ $counter -le $cword ]; do + case "${words[$counter]}" in + -a|-c|-cidfile|-dns|-e|-h|-m|-p|-u|-v|-volumes-from) + (( counter++ )) + ;; + -*) + ;; + *) + break + ;; + esac + (( counter++ )) + done + + if [ $counter -eq $cword ]; then + __docker_image_repos_and_tags + fi + ;; + esac + ;; + esac +} + +_docker_search() +{ + COMPREPLY=( $( compgen -W "-notrunc" -- "$cur" ) ) +} + +_docker_start() +{ + __docker_containers +} + +_docker_stop() +{ + case "$prev" in + -t) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-t" -- "$cur" ) ) + ;; + *) + __docker_containers + ;; + esac +} + +_docker_tag() +{ + COMPREPLY=( $( compgen -W "-f" -- "$cur" ) ) +} + +_docker_top() +{ + if [ $cpos -eq $cword ]; then + __docker_containers + fi +} + +_docker_version() +{ + return +} + +_docker_wait() +{ + __docker_containers +} + +_docker() +{ + local cur prev words cword command="docker" counter=1 word cpos + local commands=" + attach + build + commit + diff + events + export + history + images + import + info + insert + inspect + kill + login + logs + port + ps + pull + push + restart + rm + rmi + run + search + start + stop + tag + top + version + wait + " + + COMPREPLY=() + _get_comp_words_by_ref -n : cur prev words cword + + while [ $counter -lt $cword ]; do + word="${words[$counter]}" + case "$word" in + -H) + (( counter++ )) + ;; + -*) + ;; + *) + command="$word" + cpos=$counter + (( cpos++ )) + break + ;; + esac + (( counter++ )) + done + + local completions_func=_docker_${command} + declare -F $completions_func >/dev/null && $completions_func + + return 0 +} + +complete -F _docker docker +} \ No newline at end of file From e3acbff2edd04c8a7211ea078b24745749021efc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sat, 10 Aug 2013 03:06:08 +0000 Subject: [PATCH 147/242] Revert "docker.upstart: avoid spawning a `sh` process" This reverts commit 24dd50490a027f01ea086eb90663d53348fa770e. --- packaging/ubuntu/docker.upstart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packaging/ubuntu/docker.upstart b/packaging/ubuntu/docker.upstart index f4d2fbe922..143be03402 100644 --- a/packaging/ubuntu/docker.upstart +++ b/packaging/ubuntu/docker.upstart @@ -5,4 +5,6 @@ stop on runlevel [!2345] respawn -exec /usr/bin/docker -d +script + /usr/bin/docker -d +end script From 3bd73a96333e011738136f6a9eda23642cc204ab Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Sat, 10 Aug 2013 04:55:23 +0000 Subject: [PATCH 148/242] Apply volumes-from before creating volumes Copies the volumes from the container specified in `Config.VolumesFrom` before creating volumes from `Config.Volumes`. Skips any preexisting volumes when processing `Config.Volumes`. Fixes #1351 --- container.go | 60 ++++++++++++++++++++++++----------------------- container_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/container.go b/container.go index 8721d45a55..44797e6724 100644 --- a/container.go +++ b/container.go @@ -574,40 +574,12 @@ func (container *Container) Start(hostConfig *HostConfig) error { binds[path.Clean(dst)] = bindMap } - // FIXME: evaluate volumes-from before individual volumes, so that the latter can override the former. - // Create the requested volumes volumes if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) container.VolumesRW = make(map[string]bool) - - for volPath := range container.Config.Volumes { - volPath = path.Clean(volPath) - // If an external bind is defined for this volume, use that as a source - if bindMap, exists := binds[volPath]; exists { - container.Volumes[volPath] = bindMap.SrcPath - if strings.ToLower(bindMap.Mode) == "rw" { - container.VolumesRW[volPath] = true - } - // Otherwise create an directory in $ROOT/volumes/ and use that - } else { - c, err := container.runtime.volumes.Create(nil, container, "", "", nil) - if err != nil { - return err - } - srcPath, err := c.layer() - if err != nil { - return err - } - container.Volumes[volPath] = srcPath - container.VolumesRW[volPath] = true // RW by default - } - // Create the mountpoint - if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { - return nil - } - } } + // Apply volumes from another container if requested if container.Config.VolumesFrom != "" { c := container.runtime.Get(container.Config.VolumesFrom) if c == nil { @@ -627,6 +599,36 @@ func (container *Container) Start(hostConfig *HostConfig) error { } } + // Create the requested volumes if they don't exist + for volPath := range container.Config.Volumes { + volPath = path.Clean(volPath) + // If an external bind is defined for this volume, use that as a source + if _, exists := container.Volumes[volPath]; exists { + // Skip existing mounts + } else if bindMap, exists := binds[volPath]; exists { + container.Volumes[volPath] = bindMap.SrcPath + if strings.ToLower(bindMap.Mode) == "rw" { + container.VolumesRW[volPath] = true + } + // Otherwise create an directory in $ROOT/volumes/ and use that + } else { + c, err := container.runtime.volumes.Create(nil, container, "", "", nil) + if err != nil { + return err + } + srcPath, err := c.layer() + if err != nil { + return err + } + container.Volumes[volPath] = srcPath + container.VolumesRW[volPath] = true // RW by default + } + // Create the mountpoint + if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { + return nil + } + } + if err := container.generateLXCConfig(); err != nil { return err } diff --git a/container_test.go b/container_test.go index aca53e5eb3..c4f2193733 100644 --- a/container_test.go +++ b/container_test.go @@ -1276,6 +1276,65 @@ func TestRestartWithVolumes(t *testing.T) { } } +// Test for #1351 +func TestVolumesFromWithVolumes(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, + Volumes: map[string]struct{}{"/test": {}}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + for key := range container.Config.Volumes { + if key != "/test" { + t.Fail() + } + } + + _, err = container.Output() + if err != nil { + t.Fatal(err) + } + + expected := container.Volumes["/test"] + if expected == "" { + t.Fail() + } + + container2, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"cat", "/test/foo"}, + VolumesFrom: container.ID, + Volumes: map[string]struct{}{"/test": {}}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container2) + + output, err := container2.Output() + if err != nil { + t.Fatal(err) + } + + if string(output) != "bar" { + t.Fail() + } + + if container.Volumes["/test"] != container2.Volumes["/test"] { + t.Fail() + } +} + func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) From 57b49efc98d2f4605c95d5579a6cd952dfd6f124 Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Sat, 10 Aug 2013 06:37:57 +0000 Subject: [PATCH 149/242] Skip existing volumes in volumes-from Removes the error when a container already has a volume that would otherwise be created by `Config.VolumesFrom`. Allows restarting containers with a `Config.VolumesFrom` set. --- container.go | 10 ++++++---- container_test.go | 6 ++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/container.go b/container.go index 44797e6724..326c0c55fe 100644 --- a/container.go +++ b/container.go @@ -587,7 +587,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } for volPath, id := range c.Volumes { if _, exists := container.Volumes[volPath]; exists { - return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.ID) + continue } if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { return nil @@ -602,10 +602,12 @@ func (container *Container) Start(hostConfig *HostConfig) error { // Create the requested volumes if they don't exist for volPath := range container.Config.Volumes { volPath = path.Clean(volPath) - // If an external bind is defined for this volume, use that as a source + // Skip existing volumes if _, exists := container.Volumes[volPath]; exists { - // Skip existing mounts - } else if bindMap, exists := binds[volPath]; exists { + continue + } + // If an external bind is defined for this volume, use that as a source + if bindMap, exists := binds[volPath]; exists { container.Volumes[volPath] = bindMap.SrcPath if strings.ToLower(bindMap.Mode) == "rw" { container.VolumesRW[volPath] = true diff --git a/container_test.go b/container_test.go index c4f2193733..644e1c058c 100644 --- a/container_test.go +++ b/container_test.go @@ -1333,6 +1333,12 @@ func TestVolumesFromWithVolumes(t *testing.T) { if container.Volumes["/test"] != container2.Volumes["/test"] { t.Fail() } + + // Ensure it restarts successfully + _, err = container2.Output() + if err != nil { + t.Fatal(err) + } } func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { From 91ae1358966211826818ed452f13e3e2e27ed6cc Mon Sep 17 00:00:00 2001 From: Kawsar Saiyeed Date: Sat, 10 Aug 2013 13:05:31 +0100 Subject: [PATCH 150/242] Add description and usage information --- contrib/docker.bash | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/contrib/docker.bash b/contrib/docker.bash index dcabdf252c..5a4a8e30a9 100644 --- a/contrib/docker.bash +++ b/contrib/docker.bash @@ -1,4 +1,23 @@ #!bash +# +# bash completion file for core docker commands +# +# This script provides supports completion of: +# - commands and their options +# - container ids +# - image repos and tags +# - filepaths +# +# To enable the completions either: +# - place this file in /etc/bash_completion.d +# or +# - copy this file and add the line below to your .bashrc after +# bash completion features are loaded +# . docker.bash +# +# Note: +# If the docker daemon is using a unix socket for communication your user +# must have access to the socket for the completions to function correctly have docker && { __docker_containers() From a43bae4c0b6f3ec632636be544a6816f5f62dcb0 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Sat, 10 Aug 2013 13:48:24 -0400 Subject: [PATCH 151/242] gitignore all test files --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 512e4f2cd9..ea62e34d19 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,7 @@ docker/docker a.out *.orig build_src -command-line-arguments.test .flymake* -docker.test -auth/auth.test .idea .DS_Store docs/_build @@ -16,3 +13,4 @@ docs/_static docs/_templates .gopath/ .dotcloud +*.test From d75282eb147218bb7c6f4032e75b95a1ddd299cf Mon Sep 17 00:00:00 2001 From: Kawsar Saiyeed Date: Sun, 11 Aug 2013 00:57:18 +0100 Subject: [PATCH 152/242] Missed -entrypoint from 'docker run' options --- contrib/docker.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker.bash b/contrib/docker.bash index 5a4a8e30a9..a94ae2360f 100644 --- a/contrib/docker.bash +++ b/contrib/docker.bash @@ -293,7 +293,7 @@ _docker_run() -volumes-from) __docker_containers ;; - -a|-c|-dns|-e|-h|-m|-p|-u|-v) + -a|-c|-dns|-e|-entrypoint|-h|-m|-p|-u|-v) return ;; *) From ae1909b4823e500798f224a3ea70f7faf644a2bc Mon Sep 17 00:00:00 2001 From: Kawsar Saiyeed Date: Sun, 11 Aug 2013 01:08:47 +0100 Subject: [PATCH 153/242] Clarified bash completion limitations --- contrib/docker.bash | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/docker.bash b/contrib/docker.bash index a94ae2360f..1edabe1fe1 100644 --- a/contrib/docker.bash +++ b/contrib/docker.bash @@ -16,6 +16,8 @@ # . docker.bash # # Note: +# Currently, the completions will not work if the docker daemon is not +# bound to the default communication port/socket # If the docker daemon is using a unix socket for communication your user # must have access to the socket for the completions to function correctly From e737856a7f2c92f6cb582a12cc6f0b9fb4720d5a Mon Sep 17 00:00:00 2001 From: Kawsar Saiyeed Date: Sun, 11 Aug 2013 01:29:22 +0100 Subject: [PATCH 154/242] Still missed -entrypoint from 'docker run' --- contrib/docker.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker.bash b/contrib/docker.bash index 1edabe1fe1..b9fb2ec6e0 100644 --- a/contrib/docker.bash +++ b/contrib/docker.bash @@ -315,7 +315,7 @@ _docker_run() local counter=$cpos while [ $counter -le $cword ]; do case "${words[$counter]}" in - -a|-c|-cidfile|-dns|-e|-h|-m|-p|-u|-v|-volumes-from) + -a|-c|-cidfile|-dns|-e|-entrypoint|-h|-m|-p|-u|-v|-volumes-from) (( counter++ )) ;; -*) From a2fb870ce30d87aa7a08cca4c2e45c4d021c99c4 Mon Sep 17 00:00:00 2001 From: Kawsar Saiyeed Date: Sun, 11 Aug 2013 02:04:04 +0100 Subject: [PATCH 155/242] Fix typo in docs for docker run -dns --- docs/sources/commandline/command/run.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index c5b363d787..1aa6884a07 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -24,7 +24,7 @@ -p=[]: Map a network port to the container -t=false: Allocate a pseudo-tty -u="": Username or UID - -d=[]: Set custom dns servers for the container + -dns=[]: Set custom dns servers for the container -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume. -volumes-from="": Mount all volumes from the given container. -entrypoint="": Overwrite the default entrypoint set by the image. From 025c759e443cc4eb43fc20b1f7da5520956b3b30 Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Sun, 11 Aug 2013 00:37:16 -0700 Subject: [PATCH 156/242] Fix Graph ByParent() to generate list of child images per parent image. --- graph.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph.go b/graph.go index 606a6833ee..c54725fdb4 100644 --- a/graph.go +++ b/graph.go @@ -323,9 +323,9 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) { return } if children, exists := byParent[parent.ID]; exists { - byParent[parent.ID] = []*Image{image} - } else { byParent[parent.ID] = append(children, image) + } else { + byParent[parent.ID] = []*Image{image} } }) return byParent, err From 02b8d14bdd1837aad5b8fb667d1f4e7eace59687 Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Sun, 11 Aug 2013 01:24:21 -0700 Subject: [PATCH 157/242] Add test case for Graph ByParent(). --- graph_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/graph_test.go b/graph_test.go index 2898fccf99..32fb0ef441 100644 --- a/graph_test.go +++ b/graph_test.go @@ -234,6 +234,45 @@ func TestDelete(t *testing.T) { assertNImages(graph, t, 1) } +func TestByParent(t *testing.T) { + archive1, _ := fakeTar() + archive2, _ := fakeTar() + archive3, _ := fakeTar() + + graph := tempGraph(t) + defer os.RemoveAll(graph.Root) + parentImage := &Image{ + ID: GenerateID(), + Comment: "parent", + Created: time.Now(), + Parent: "", + } + childImage1 := &Image{ + ID: GenerateID(), + Comment: "child1", + Created: time.Now(), + Parent: parentImage.ID, + } + childImage2 := &Image{ + ID: GenerateID(), + Comment: "child2", + Created: time.Now(), + Parent: parentImage.ID, + } + _ = graph.Register(nil, archive1, parentImage) + _ = graph.Register(nil, archive2, childImage1) + _ = graph.Register(nil, archive3, childImage2) + + byParent, err := graph.ByParent() + if err != nil { + t.Fatal(err) + } + numChildren := len(byParent[parentImage.ID]) + if numChildren != 2 { + t.Fatalf("Expected 2 children, found %d", numChildren) + } +} + func assertNImages(graph *Graph, t *testing.T, n int) { if images, err := graph.All(); err != nil { t.Fatal(err) From 64b817a5c12545252d0cbb3d4ce1f23996bb266b Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Sun, 11 Aug 2013 11:52:16 +0200 Subject: [PATCH 158/242] runtime: correctly detect IPv4 forwarding When memory cgroup is absent, there was not attempt to detect if IPv4 forwarding was enabled and therefore, docker was printing a warning for each command spawning a new container. The test for IPv4 forwarding was guarded by the test for memory cgroup. --- runtime.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/runtime.go b/runtime.go index 894028354e..0f97c01cba 100644 --- a/runtime.go +++ b/runtime.go @@ -241,12 +241,12 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) { if !runtime.capabilities.SwapLimit && !quiet { log.Printf("WARNING: Your kernel does not support cgroup swap limit.") } + } - content, err3 := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward") - runtime.capabilities.IPv4Forwarding = err3 == nil && len(content) > 0 && content[0] == '1' - if !runtime.capabilities.IPv4Forwarding && !quiet { - log.Printf("WARNING: IPv4 forwarding is disabled.") - } + content, err3 := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward") + runtime.capabilities.IPv4Forwarding = err3 == nil && len(content) > 0 && content[0] == '1' + if !runtime.capabilities.IPv4Forwarding && !quiet { + log.Printf("WARNING: IPv4 forwarding is disabled.") } } From 529ee848da1f1dd4b628c3396289b4bcffdbd540 Mon Sep 17 00:00:00 2001 From: Laurie Voss Date: Sun, 11 Aug 2013 17:27:47 -0700 Subject: [PATCH 159/242] Adding a reference to ps -a It was confusing to me as a first-time user that my docker attach command failed; I was expecting the container to run continuously, and when I couldn't attach to it, I assumed something was wrong. ps -a shows that the container still exists, which gives the user confidence to go to the next step. --- docs/sources/examples/python_web_app.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index ed4a490c72..dae46d66d8 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -36,7 +36,13 @@ Inside of the "shykes/pybuilder" image there is a command called buildapp, we ar docker attach $BUILD_JOB [...] -We attach to the new container to see what is going on. Ctrl-C to disconnect +While this container is running, we can attach to the new container to see what is going on. Ctrl-C to disconnect. + +.. code-block:: bash + + docker ps -a + +List all docker containers. If this container has already finished running, it will still be listed here. .. code-block:: bash From def9598ed968eac934699db1b8717f852652b1ef Mon Sep 17 00:00:00 2001 From: Kawsar Saiyeed Date: Mon, 12 Aug 2013 05:22:33 +0100 Subject: [PATCH 160/242] Install websocket library before building docker --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 46f9b585c8..7430ec6c0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,9 @@ run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt run apt-get update run apt-get install -y lxc run apt-get install -y aufs-tools +# Docker requires code.google.com/p/go.net/websocket +run apt-get install -y -q mercurial +run PKG=code.google.com/p/go.net REV=78ad7f42aa2e; hg clone https://$PKG /go/src/$PKG && cd /go/src/$PKG && hg checkout -r $REV # Upload docker source add . /go/src/github.com/dotcloud/docker # Build the binary From d52c1490753cb924aa2aeeefc92fbe7a985f4cd3 Mon Sep 17 00:00:00 2001 From: Zaiste! Date: Mon, 12 Aug 2013 12:03:43 +0200 Subject: [PATCH 161/242] docs/postgresql: PostgreSQL service on Docker example --- docs/sources/examples/index.rst | 3 +- docs/sources/examples/postgresql_service.rst | 149 +++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 docs/sources/examples/postgresql_service.rst diff --git a/docs/sources/examples/index.rst b/docs/sources/examples/index.rst index d017f641b1..58da18e344 100644 --- a/docs/sources/examples/index.rst +++ b/docs/sources/examples/index.rst @@ -1,6 +1,6 @@ :title: Docker Examples :description: Examples on how to use Docker -:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples +:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql @@ -20,3 +20,4 @@ Contents: running_redis_service running_ssh_service couchdb_data_volumes + postgresql_service diff --git a/docs/sources/examples/postgresql_service.rst b/docs/sources/examples/postgresql_service.rst new file mode 100644 index 0000000000..c829a53a86 --- /dev/null +++ b/docs/sources/examples/postgresql_service.rst @@ -0,0 +1,149 @@ +:title: PostgreSQL service How-To +:description: Running and installing a PostgreSQL service +:keywords: docker, example, package installation, postgresql + +.. _postgresql_service: + +PostgreSQL Service +================== + +.. note:: + + A shorter version of `this blog post`_. + +.. _this blog post: http://zaiste.net/2013/08/docker_postgresql_how_to/ + +Installing PostgreSQL on Docker +------------------------------- + +For clarity I won't be showing commands output. + +Run an interactive shell in Docker container. + +.. code-block:: bash + + docker run -i -t base /bin/bash + +Update its dependencies. + +.. code-block:: bash + + apt-get update + +Install ``python-software-properies``. + +.. code-block:: bash + + apt-get install python-software-properties + apt-get install software-properties-common + +Add Pitti's PostgreSQL repository. It contains the most recent stable release +of PostgreSQL i.e. ``9.2``. + +.. code-block:: bash + + add-apt-repository ppa:pitti/postgresql + apt-get update + +Finally, install PostgreSQL 9.2 + +.. code-block:: bash + + apt-get -y install postgresql-9.2 postgresql-client-9.2 postgresql-contrib-9.2 + +Now, create a PostgreSQL superuser role that can create databases and other roles. +Following Vagrant's convention the role will be named `docker` with `docker` +password assigned to it. + +.. code-block:: bash + + sudo -u postgres createuser -P -d -r -s docker + +Create a test database also named ``docker`` owned by previously created ``docker`` +role. + +.. code-block:: bash + + sudo -u postgres createdb -O docker docker + +Adjust PostgreSQL configuration so that remote connections to the database are +possible. Make sure that inside ``/etc/postgresql/9.2/main/pg_hba.conf`` you have +following line: + +.. code-block:: bash + + host all all 0.0.0.0/0 md5 + +Additionaly, inside ``/etc/postgresql/9.2/main/postgresql.conf`` uncomment +``listen_address`` so it is as follows: + +.. code-block:: bash + + listen_address='*' + +*Note:* this PostgreSQL setup is for development only purposes. Refer to +PostgreSQL documentation how to fine-tune these settings so that it is enough +secure. + +Create an image and assign it a name. ```` is in the Bash prompt; +you can also locate it using ``docker ps -a``. + +.. code-block:: bash + + docker commit /postgresql + +Finally, run PostgreSQL server via ``docker``. + +.. code-block:: bash + + CONTAINER=$(docker run -d -p 5432 \ + -t /postgresql \ + /bin/su postgres -c '/usr/lib/postgresql/9.2/bin/postgres \ + -D /var/lib/postgresql/9.2/main \ + -c config_file=/etc/postgresql/9.2/main/postgresql.conf') + +Connect the PostgreSQL server using ``psql``. + +.. code-block:: bash + + CONTAINER_IP=$(docker inspect $CONTAINER | grep IPAddress | awk '{ print $2 }' | tr -d ',"') + psql -h $CONTAINER_IP -p 5432 -d docker -U docker -W + +As before, create roles or databases if needed. + +.. code-block:: bash + + psql (9.2.4) + Type "help" for help. + + docker=# CREATE DATABASE foo OWNER=docker; + CREATE DATABASE + +Additionally, publish there your newly created image on Docker Index. + +.. code-block:: bash + + docker login + Username: + [...] + +.. code-block:: bash + + docker push /postgresql + +PostgreSQL service auto-launch +------------------------------ + +Running our image seems complicated. We have to specify the whole command with +``docker run``. Let's simplify it so the service starts automatically when the +container starts. + +.. code-block:: bash + + docker commit /postgresql -run='{"Cmd": \ + ["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.2/bin/postgres -D \ + /var/lib/postgresql/9.2/main -c \ + config_file=/etc/postgresql/9.2/main/postgresql.conf"], PortSpecs": ["5432"]} + +From now on, just type ``docker run /postgresql`` and PostgreSQL +should automatically start. From 703905d7ece5b4a71ae1faf2743341ace98c4fbb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 12 Aug 2013 11:50:03 +0000 Subject: [PATCH 162/242] ensure the use oh IDs and add image's name in /events --- container.go | 2 +- server.go | 22 +++++++++++----------- utils/utils.go | 4 ++++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/container.go b/container.go index 8721d45a55..18c56c8349 100644 --- a/container.go +++ b/container.go @@ -813,7 +813,7 @@ func (container *Container) monitor() { } utils.Debugf("Process finished") if container.runtime != nil && container.runtime.srv != nil { - container.runtime.srv.LogEvent("die", container.ShortID()) + container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image)) } exitCode := -1 if container.cmd != nil { diff --git a/server.go b/server.go index f06b5ce68e..663b9683b8 100644 --- a/server.go +++ b/server.go @@ -76,7 +76,7 @@ func (srv *Server) ContainerKill(name string) error { if err := container.Kill(); err != nil { return fmt.Errorf("Error killing container %s: %s", name, err) } - srv.LogEvent("kill", name) + srv.LogEvent("kill", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) } else { return fmt.Errorf("No such container: %s", name) } @@ -95,7 +95,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { if _, err := io.Copy(out, data); err != nil { return err } - srv.LogEvent("export", name) + srv.LogEvent("export", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) return nil } return fmt.Errorf("No such container: %s", name) @@ -832,7 +832,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { } return "", err } - srv.LogEvent("create", container.ShortID()) + srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) return container.ShortID(), nil } @@ -841,7 +841,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { if err := container.Restart(t); err != nil { return fmt.Errorf("Error restarting container %s: %s", name, err) } - srv.LogEvent("restart", name) + srv.LogEvent("restart", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) } else { return fmt.Errorf("No such container: %s", name) } @@ -861,7 +861,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { if err := srv.runtime.Destroy(container); err != nil { return fmt.Errorf("Error destroying container %s: %s", name, err) } - srv.LogEvent("destroy", name) + srv.LogEvent("destroy", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) if removeVolume { // Retrieve all volumes from all remaining containers @@ -928,7 +928,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { return err } *imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)}) - srv.LogEvent("delete", utils.TruncateID(id)) + srv.LogEvent("delete", utils.TruncateID(id), "") return nil } return nil @@ -975,7 +975,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro } if tagDeleted { imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) - srv.LogEvent("untag", img.ShortID()) + srv.LogEvent("untag", img.ShortID(), "") } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { @@ -1042,7 +1042,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if err := container.Start(hostConfig); err != nil { return fmt.Errorf("Error starting container %s: %s", name, err) } - srv.LogEvent("start", name) + srv.LogEvent("start", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) } else { return fmt.Errorf("No such container: %s", name) } @@ -1054,7 +1054,7 @@ func (srv *Server) ContainerStop(name string, t int) error { if err := container.Stop(t); err != nil { return fmt.Errorf("Error stopping container %s: %s", name, err) } - srv.LogEvent("stop", name) + srv.LogEvent("stop", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) } else { return fmt.Errorf("No such container: %s", name) } @@ -1222,9 +1222,9 @@ func (srv *Server) HTTPRequestFactory() *utils.HTTPRequestFactory { return srv.reqFactory } -func (srv *Server) LogEvent(action, id string) { +func (srv *Server) LogEvent(action, id, from string) { now := time.Now().Unix() - jm := utils.JSONMessage{Status: action, ID: id, Time: now} + jm := utils.JSONMessage{Status: action, ID: id, From: from, Time: now} srv.events = append(srv.events, jm) for _, c := range srv.listeners { select { // non blocking channel diff --git a/utils/utils.go b/utils/utils.go index df21e75ae4..497d7f4e42 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -622,6 +622,7 @@ type JSONMessage struct { Progress string `json:"progress,omitempty"` ErrorMessage string `json:"error,omitempty"` //deprecated ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` Time int64 `json:"time,omitempty"` Error *JSONError `json:"errorDetail,omitempty"` } @@ -650,6 +651,9 @@ func (jm *JSONMessage) Display(out io.Writer) error { if jm.ID != "" { fmt.Fprintf(out, "%s: ", jm.ID) } + if jm.From != "" { + fmt.Fprintf(out, "(from %s) ", jm.From) + } if jm.Progress != "" { fmt.Fprintf(out, "%c[2K", 27) fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) From 123c80467bb6e7bef22827b55640a4789e42558d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 12 Aug 2013 11:55:23 +0000 Subject: [PATCH 163/242] Added docs --- docs/sources/api/docker_remote_api.rst | 4 +++ docs/sources/api/docker_remote_api_v1.4.rst | 31 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 7e4b674348..ec19fed48e 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -48,6 +48,10 @@ What's new **New!** You can now use ps args with docker top, like `docker top aux` +.. http:get:: /events: + + **New!** Image's name added in the events + :doc:`docker_remote_api_v1.3` ***************************** diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 06e8f46f99..1073ddcd6e 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -1095,6 +1095,37 @@ Create a new image from a container's changes :statuscode 404: no such container :statuscode 500: server error + +Monitor Docker's events +*********************** + +.. http:get:: /events + + Get events from docker, either in real time via streaming, or via polling (using `since`) + + **Example request**: + + .. sourcecode:: http + + POST /events?since=1374067924 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + + 3. Going further ================ From 3af60bf37565de35643c95eae9fd4ddc863c21fc Mon Sep 17 00:00:00 2001 From: Zaiste! Date: Mon, 12 Aug 2013 15:30:52 +0200 Subject: [PATCH 164/242] fix/docs: ubuntu instead of base, note about root-only --- docs/sources/examples/postgresql_service.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/sources/examples/postgresql_service.rst b/docs/sources/examples/postgresql_service.rst index c829a53a86..4ed1ca4bed 100644 --- a/docs/sources/examples/postgresql_service.rst +++ b/docs/sources/examples/postgresql_service.rst @@ -11,18 +11,27 @@ PostgreSQL Service A shorter version of `this blog post`_. +.. note:: + + As of version 0.5.2, docker requires root privileges to run. + You have to either manually adjust your system configuration (permissions on + /var/run/docker.sock or sudo config), or prefix `docker` with `sudo`. Check + `this thread`_ for details. + .. _this blog post: http://zaiste.net/2013/08/docker_postgresql_how_to/ +.. _this thread: https://groups.google.com/forum/?fromgroups#!topic/docker-club/P3xDLqmLp0E Installing PostgreSQL on Docker ------------------------------- For clarity I won't be showing commands output. + Run an interactive shell in Docker container. .. code-block:: bash - docker run -i -t base /bin/bash + docker run -i -t ubuntu /bin/bash Update its dependencies. From 9b2a5964fc2067014f24e26bd2f99873751ebdc3 Mon Sep 17 00:00:00 2001 From: Pascal Borreli Date: Mon, 12 Aug 2013 18:53:06 +0100 Subject: [PATCH 165/242] Fixed typos --- api_test.go | 10 +++++----- archive.go | 2 +- buildfile.go | 2 +- commands.go | 2 +- commands_test.go | 2 +- container_test.go | 8 ++++---- docker/docker.go | 2 +- docs/README.md | 2 +- docs/sources/api/docker_remote_api.rst | 4 ++-- docs/sources/api/docker_remote_api_v1.1.rst | 4 ++-- docs/sources/api/docker_remote_api_v1.2.rst | 4 ++-- docs/sources/api/docker_remote_api_v1.3.rst | 4 ++-- docs/sources/api/docker_remote_api_v1.4.rst | 4 ++-- docs/sources/api/registry_index_spec.rst | 2 +- docs/sources/conf.py | 2 +- docs/sources/contributing/contributing.rst | 2 +- docs/sources/examples/running_ssh_service.rst | 4 ++-- docs/sources/index.rst | 2 +- docs/sources/installation/rackspace.rst | 2 +- docs/sources/terms/layer.rst | 2 +- docs/sources/use/workingwithrepository.rst | 4 ++-- network.go | 4 ++-- server.go | 10 +++++----- server_test.go | 8 ++++---- utils/utils_test.go | 2 +- 25 files changed, 47 insertions(+), 47 deletions(-) diff --git a/api_test.go b/api_test.go index 9ab00cd759..fb065bb27e 100644 --- a/api_test.go +++ b/api_test.go @@ -471,7 +471,7 @@ func TestGetContainersTop(t *testing.T) { } defer runtime.Destroy(container) defer func() { - // Make sure the process dies before destorying runtime + // Make sure the process dies before destroying runtime container.stdin.Close() container.WaitTimeout(2 * time.Second) }() @@ -563,7 +563,7 @@ func TestGetContainersByName(t *testing.T) { t.Fatal(err) } if outContainer.ID != container.ID { - t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.ID, outContainer.ID) + t.Fatalf("Wrong containers retrieved. Expected %s, received %s", container.ID, outContainer.ID) } } @@ -802,7 +802,7 @@ func TestPostContainersStart(t *testing.T) { r = httptest.NewRecorder() if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil { - t.Fatalf("A running containter should be able to be started") + t.Fatalf("A running container should be able to be started") } if err := container.Kill(); err != nil { @@ -926,7 +926,7 @@ func TestPostContainersAttach(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - // Try to avoid the timeoout in destroy. Best effort, don't check error + // Try to avoid the timeout in destroy. Best effort, don't check error defer func() { closeWrap(stdin, stdinPipe, stdout, stdoutPipe) container.Kill() @@ -982,7 +982,7 @@ func TestPostContainersAttach(t *testing.T) { t.Fatalf("/bin/cat is not running after closing stdin") } - // Try to avoid the timeoout in destroy. Best effort, don't check error + // Try to avoid the timeout in destroy. Best effort, don't check error cStdin, _ := container.StdinPipe() cStdin.Close() container.Wait() diff --git a/archive.go b/archive.go index bb79cd34d4..bb019fb033 100644 --- a/archive.go +++ b/archive.go @@ -98,7 +98,7 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader // Untar reads a stream of bytes from `archive`, parses it as a tar archive, // and unpacks it into the directory at `path`. -// The archive may be compressed with one of the following algorithgms: +// The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. // FIXME: specify behavior when target path exists vs. doesn't exist. func Untar(archive io.Reader, path string) error { diff --git a/buildfile.go b/buildfile.go index a4f89091d1..5a2662646e 100644 --- a/buildfile.go +++ b/buildfile.go @@ -509,7 +509,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image)) return b.image, nil } - return "", fmt.Errorf("An error occured during the build\n") + return "", fmt.Errorf("An error occurred during the build\n") } func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache bool) BuildFile { diff --git a/commands.go b/commands.go index c9f669b782..9b9dd51e75 100644 --- a/commands.go +++ b/commands.go @@ -194,7 +194,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } var body io.Reader // Setup an upload progress bar - // FIXME: ProgressReader shouldn't be this annoyning to use + // FIXME: ProgressReader shouldn't be this annoying to use if context != nil { sf := utils.NewStreamFormatter(false) body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("", "Uploading context", "%v bytes%0.0s%0.0s"), sf, true) diff --git a/commands_test.go b/commands_test.go index ddbc7f6b40..ac30cc73f9 100644 --- a/commands_test.go +++ b/commands_test.go @@ -373,7 +373,7 @@ func TestAttachDisconnect(t *testing.T) { t.Fatalf("/bin/cat is not running after closing stdin") } - // Try to avoid the timeoout in destroy. Best effort, don't check error + // Try to avoid the timeout in destroy. Best effort, don't check error cStdin, _ := container.StdinPipe() cStdin.Close() container.Wait() diff --git a/container_test.go b/container_test.go index aca53e5eb3..6aa4b94f54 100644 --- a/container_test.go +++ b/container_test.go @@ -186,7 +186,7 @@ func TestDiff(t *testing.T) { } } - // Create a new containere + // Create a new container container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t) defer runtime.Destroy(container3) @@ -351,10 +351,10 @@ func TestStart(t *testing.T) { t.Errorf("Container should be running") } if err := container.Start(hostConfig); err == nil { - t.Fatalf("A running containter should be able to be started") + t.Fatalf("A running container should be able to be started") } - // Try to avoid the timeoout in destroy. Best effort, don't check error + // Try to avoid the timeout in destroy. Best effort, don't check error cStdin.Close() container.WaitTimeout(2 * time.Second) } @@ -764,7 +764,7 @@ func TestUser(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, - User: "unkownuser", + User: "unknownuser", }, ) if err != nil { diff --git a/docker/docker.go b/docker/docker.go index 9874d756d5..a48865bfa3 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -37,7 +37,7 @@ func main() { flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() if len(flHosts) > 1 { - flHosts = flHosts[1:] //trick to display a nice defaul value in the usage + flHosts = flHosts[1:] //trick to display a nice default value in the usage } for i, flHost := range flHosts { flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost) diff --git a/docs/README.md b/docs/README.md index 366b7ed8f2..d53b8675b9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,7 +28,7 @@ Usage Working using GitHub's file editor ---------------------------------- Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows -you to preview your changes right online. Just be carefull not to create many commits. +you to preview your changes right online. Just be careful not to create many commits. Images ------ diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 7e4b674348..9113d8e155 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -26,7 +26,7 @@ Docker Remote API 2. Versions =========== -The current verson of the API is 1.4 +The current version of the API is 1.4 Calling /images//insert is the same as calling /v1.4/images//insert @@ -107,7 +107,7 @@ The client should send it's authConfig as POST on each call of Only checks the configuration but doesn't store it on the server Deleting an image is now improved, will only untag the image if it - has chidren and remove all the untagged parents if has any. + has children and remove all the untagged parents if has any. .. http:post:: /images//delete diff --git a/docs/sources/api/docker_remote_api_v1.1.rst b/docs/sources/api/docker_remote_api_v1.1.rst index 3e906ed50f..7fecdbfddd 100644 --- a/docs/sources/api/docker_remote_api_v1.1.rst +++ b/docs/sources/api/docker_remote_api_v1.1.rst @@ -305,8 +305,8 @@ Start a container :statuscode 500: server error -Stop a contaier -*************** +Stop a container +**************** .. http:post:: /containers/(id)/stop diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index b956d1dfe6..387b5a5dda 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -317,8 +317,8 @@ Start a container :statuscode 500: server error -Stop a contaier -*************** +Stop a container +**************** .. http:post:: /containers/(id)/stop diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 8e5c7b2a3b..f1d743dd63 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -365,8 +365,8 @@ Start a container :statuscode 500: server error -Stop a contaier -*************** +Stop a container +**************** .. http:post:: /containers/(id)/stop diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 06e8f46f99..88ba3a60af 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -368,8 +368,8 @@ Start a container :statuscode 500: server error -Stop a contaier -*************** +Stop a container +**************** .. http:post:: /containers/(id)/stop diff --git a/docs/sources/api/registry_index_spec.rst b/docs/sources/api/registry_index_spec.rst index 3ae39e37d9..a41523e813 100644 --- a/docs/sources/api/registry_index_spec.rst +++ b/docs/sources/api/registry_index_spec.rst @@ -154,7 +154,7 @@ API (pulling repository foo/bar): .. note:: - **It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed. + **It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authenticated and the security is not guaranteed. .. note:: diff --git a/docs/sources/conf.py b/docs/sources/conf.py index b4c23f0c58..8168deb241 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -51,7 +51,7 @@ source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' -#disable the parmalinks on headers, I find them really annoying +#disable the permalinks on headers, I find them really annoying html_add_permalinks = None diff --git a/docs/sources/contributing/contributing.rst b/docs/sources/contributing/contributing.rst index 1913cec30d..301977e5e1 100644 --- a/docs/sources/contributing/contributing.rst +++ b/docs/sources/contributing/contributing.rst @@ -1,5 +1,5 @@ :title: Contribution Guidelines -:description: Contribution guidelines: create issues, convetions, pull requests +:description: Contribution guidelines: create issues, conventions, pull requests :keywords: contributing, docker, documentation, help, guideline Contributing to Docker diff --git a/docs/sources/examples/running_ssh_service.rst b/docs/sources/examples/running_ssh_service.rst index 54ca4cd200..821ff84659 100644 --- a/docs/sources/examples/running_ssh_service.rst +++ b/docs/sources/examples/running_ssh_service.rst @@ -33,7 +33,7 @@ The password is 'screencast' .. code-block:: bash - # Hello! We are going to try and install openssh on a container and run it as a servic + # Hello! We are going to try and install openssh on a container and run it as a service # let's pull ubuntu to get a base ubuntu image. $ docker pull ubuntu # I had it so it was quick @@ -46,7 +46,7 @@ The password is 'screencast' $ apt-get install openssh-server # ok. lets see if we can run it. $ which sshd - # we need to create priviledge separation directory + # we need to create privilege separation directory $ mkdir /var/run/sshd $ /usr/sbin/sshd $ exit diff --git a/docs/sources/index.rst b/docs/sources/index.rst index ba8f60c3fa..8dfffa718b 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -23,7 +23,7 @@ dependencies. commit``). Each use of ``docker`` is documented here. The features of Docker are -currently in active development, so this documention will change +currently in active development, so this documentation will change frequently. For an overview of Docker, please see the `Introduction diff --git a/docs/sources/installation/rackspace.rst b/docs/sources/installation/rackspace.rst index 7482404683..7f360682e2 100644 --- a/docs/sources/installation/rackspace.rst +++ b/docs/sources/installation/rackspace.rst @@ -10,7 +10,7 @@ Rackspace Cloud :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. -Installing Docker on Ubuntu proviced by Rackspace is pretty straightforward, and you should mostly be able to follow the +Installing Docker on Ubuntu provided by Rackspace is pretty straightforward, and you should mostly be able to follow the :ref:`ubuntu_linux` installation guide. **However, there is one caveat:** diff --git a/docs/sources/terms/layer.rst b/docs/sources/terms/layer.rst index 981194b6a3..509dbe5cba 100644 --- a/docs/sources/terms/layer.rst +++ b/docs/sources/terms/layer.rst @@ -14,7 +14,7 @@ switches the whole rootfs volume to read-write mode. Layer ..... -When Docker mounts the rootfs, it starts read-only, as in a tradtional +When Docker mounts the rootfs, it starts read-only, as in a traditional Linux boot, but then, instead of changing the file system to read-write mode, it takes advantage of a `union mount `_ to add a read-write file diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst index 4a2e39aea1..b1ed6fc633 100644 --- a/docs/sources/use/workingwithrepository.rst +++ b/docs/sources/use/workingwithrepository.rst @@ -1,6 +1,6 @@ :title: Working With Repositories :description: Repositories allow users to share images. -:keywords: repo, repositiores, usage, pull image, push image, image, documentation +:keywords: repo, repositories, usage, pull image, push image, image, documentation .. _working_with_the_repository: @@ -71,7 +71,7 @@ function completely independently from the Central Index. Find public images available on the Central Index ------------------------------------------------- -Seach by name, namespace or description +Search by name, namespace or description .. code-block:: bash diff --git a/network.go b/network.go index b289934295..dd0fe2dd41 100644 --- a/network.go +++ b/network.go @@ -332,7 +332,7 @@ func newPortMapper() (*PortMapper, error) { return mapper, nil } -// Port allocator: Atomatically allocate and release networking ports +// Port allocator: Automatically allocate and release networking ports type PortAllocator struct { sync.Mutex inUse map[int]struct{} @@ -385,7 +385,7 @@ func newPortAllocator() (*PortAllocator, error) { return allocator, nil } -// IP allocator: Atomatically allocate and release networking ports +// IP allocator: Automatically allocate and release networking ports type IPAllocator struct { network *net.IPNet queueAlloc chan allocatedIP diff --git a/server.go b/server.go index f06b5ce68e..16b59dc83d 100644 --- a/server.go +++ b/server.go @@ -425,7 +425,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "metadata")) imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token) if err != nil { - // FIXME: Keep goging in case of error? + // FIXME: Keep going in case of error? return err } img, err := NewImgJSON(imgJSON) @@ -565,7 +565,7 @@ func (srv *Server) poolAdd(kind, key string) error { srv.pushingPool[key] = struct{}{} break default: - return fmt.Errorf("Unkown pool type") + return fmt.Errorf("Unknown pool type") } return nil } @@ -579,7 +579,7 @@ func (srv *Server) poolRemove(kind, key string) error { delete(srv.pushingPool, key) break default: - return fmt.Errorf("Unkown pool type") + return fmt.Errorf("Unknown pool type") } return nil } @@ -693,7 +693,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, out = utils.NewWriteFlusher(out) jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgID, "json")) if err != nil { - return "", fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err) + return "", fmt.Errorf("Error while retrieving the path for {%s}: %s", imgID, err) } out.Write(sf.FormatStatus("", "Pushing %s", imgID)) @@ -731,7 +731,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, return imgData.Checksum, nil } -// FIXME: Allow to interupt current push when new push of same image is done. +// FIXME: Allow to interrupt current push when new push of same image is done. func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { if err := srv.poolAdd("push", localName); err != nil { return err diff --git a/server_test.go b/server_test.go index b66c44427e..b7ffbb7e6f 100644 --- a/server_test.go +++ b/server_test.go @@ -283,8 +283,8 @@ func TestPools(t *testing.T) { t.Fatalf("Expected `pull test1 is already in progress`") } err = srv.poolAdd("wait", "test3") - if err == nil || err.Error() != "Unkown pool type" { - t.Fatalf("Expected `Unkown pool type`") + if err == nil || err.Error() != "Unknown pool type" { + t.Fatalf("Expected `Unknown pool type`") } err = srv.poolRemove("pull", "test2") @@ -304,8 +304,8 @@ func TestPools(t *testing.T) { t.Fatal(err) } err = srv.poolRemove("wait", "test3") - if err == nil || err.Error() != "Unkown pool type" { - t.Fatalf("Expected `Unkown pool type`") + if err == nil || err.Error() != "Unknown pool type" { + t.Fatalf("Expected `Unknown pool type`") } } diff --git a/utils/utils_test.go b/utils/utils_test.go index d8ba7f1c31..1030b2902a 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -219,7 +219,7 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) { if r := CompareKernelVersion(a, b); r != result { - t.Fatalf("Unepected kernel version comparaison result. Found %d, expected %d", r, result) + t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result) } } From 999a8d72492c7937aa9f826406ae40158017f766 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Mon, 12 Aug 2013 15:15:52 -0700 Subject: [PATCH 166/242] API, issue 1471: Allow users belonging to the docker group to use the docker client --- api.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/api.go b/api.go index c9ac15bc53..18f42c446d 100644 --- a/api.go +++ b/api.go @@ -15,6 +15,7 @@ import ( "net/http" "os" "os/exec" + "regexp" "strconv" "strings" ) @@ -1086,7 +1087,25 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error { return e } if proto == "unix" { - os.Chmod(addr, 0700) + if err := os.Chmod(addr, 0660); err != nil { + return err + } + + groups, err := ioutil.ReadFile("/etc/group") + if err != nil { + return err + } + re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)") + if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil { + gid, err := strconv.Atoi(gidMatch[2]) + if err != nil { + return err + } + utils.Debugf("docker group found. gid: %d", gid) + if err := os.Chown(addr, 0, gid); err != nil { + return err + } + } } httpSrv := http.Server{Addr: addr, Handler: r} return httpSrv.Serve(l) From ec61c46bf73b8c727fe8de1982d86a1417a8a0c4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 12 Aug 2013 22:42:29 +0000 Subject: [PATCH 167/242] Add import for dotcloud/tar to replace std tar pkg --- Dockerfile | 1 + utils/tarsum.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 46f9b585c8..2e4953fb1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ run cd /tmp && echo 'package main' > t.go && go test -a -i -v run PKG=github.com/kr/pty REV=27435c699; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV run PKG=github.com/gorilla/context/ REV=708054d61e5; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV run PKG=github.com/gorilla/mux/ REV=9b36453141c; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV +run PKG=github.com/dotcloud/tar/ REV=d06045a6d9; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV # Run dependencies run apt-get install -y iptables # lxc requires updating ubuntu sources diff --git a/utils/tarsum.go b/utils/tarsum.go index d3e1db61f1..290be241a9 100644 --- a/utils/tarsum.go +++ b/utils/tarsum.go @@ -1,11 +1,11 @@ package utils import ( - "archive/tar" "bytes" "compress/gzip" "crypto/sha256" "encoding/hex" + "github.com/dotcloud/tar" "hash" "io" "sort" From 5ee3c58d2549c25fa4464963831a28a99407b6cd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 13 Aug 2013 12:02:17 +0000 Subject: [PATCH 168/242] Add USER instruction --- buildfile.go | 5 +++++ buildfile_test.go | 11 +++++++++++ docs/sources/use/builder.rst | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/buildfile.go b/buildfile.go index 5a2662646e..06ca331a40 100644 --- a/buildfile.go +++ b/buildfile.go @@ -197,6 +197,11 @@ func (b *buildFile) CmdExpose(args string) error { return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports)) } +func (b *buildFile) CmdUser(args string) error { + b.config.User = args + return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args)) +} + func (b *buildFile) CmdInsert(args string) error { return fmt.Errorf("INSERT has been deprecated. Please use ADD instead") } diff --git a/buildfile_test.go b/buildfile_test.go index d89c40d16c..14986161d8 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -270,6 +270,17 @@ func TestBuildMaintainer(t *testing.T) { } } +func TestBuildUser(t *testing.T) { + img := buildImage(testContextTemplate{` + from {IMAGE} + user dockerio + `, nil, nil}, t, nil, true) + + if img.Config.User != "dockerio" { + t.Fail() + } +} + func TestBuildEnv(t *testing.T) { img := buildImage(testContextTemplate{` from {IMAGE} diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index d111e335ab..98c6beaccb 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -203,6 +203,14 @@ to the entrypoint. The ``VOLUME`` instruction will add one or more new volumes to any container created from the image. +3.10 USER +--------- + + ``USER daemon`` + +The ``USER`` instruction sets the username or UID to use when running +the image. + 4. Dockerfile Examples ====================== From 6cb908bb823409661bfedab806da924d232bf200 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 13 Aug 2013 13:35:34 +0000 Subject: [PATCH 169/242] fix merge issue --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 9b9dd51e75..d9d4f1b620 100644 --- a/commands.go +++ b/commands.go @@ -857,7 +857,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { } if err := push(); err != nil { - if err == fmt.Errorf("Authentication is required.") { + if err.Error() == "Authentication is required." { if err = cli.checkIfLogged("push"); err == nil { return push() } From 2ba1300773857273585288c79aa65f011b045b4c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 13 Aug 2013 13:51:49 +0000 Subject: [PATCH 170/242] remove checkIfLogged --- commands.go | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/commands.go b/commands.go index d9d4f1b620..8cefe3408f 100644 --- a/commands.go +++ b/commands.go @@ -858,9 +858,11 @@ func (cli *DockerCli) CmdPush(args ...string) error { if err := push(); err != nil { if err.Error() == "Authentication is required." { - if err = cli.checkIfLogged("push"); err == nil { - return push() + fmt.Fprintln(cli.out, "\nPlease login prior to push:") + if err := cli.CmdLogin(""); err != nil { + return err } + return push() } return err } @@ -1512,19 +1514,6 @@ func (cli *DockerCli) CmdCp(args ...string) error { return nil } -func (cli *DockerCli) checkIfLogged(action string) error { - // If condition AND the login failed - if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { - if err := cli.CmdLogin(""); err != nil { - return err - } - if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { - return fmt.Errorf("Please login prior to %s. ('docker login')", action) - } - } - return nil -} - func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) { var params io.Reader if data != nil { From e09863fedb1b2fec4672d2d1ebad29ecdb8eed1a Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 13 Aug 2013 19:48:30 +0300 Subject: [PATCH 171/242] use Go 1.1.2 for dockerbuilder --- hack/dockerbuilder/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile index 60cd93b17a..496ee45e7a 100644 --- a/hack/dockerbuilder/Dockerfile +++ b/hack/dockerbuilder/Dockerfile @@ -23,7 +23,7 @@ run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu run apt-get update # Packages required to checkout, build and upload docker run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl -run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz +run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz run tar -C /usr/local -xzf /go.tar.gz run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile From fb7c4214ced3b0533316e3eebd90ac07fe7b2933 Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 12 Aug 2013 19:45:12 +0200 Subject: [PATCH 172/242] brew: Reuse repositories when possible --- contrib/brew/brew/brew.py | 12 ++++++++++-- contrib/brew/brew/git.py | 17 ++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index 8cbbaca06a..352d20c779 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -14,6 +14,7 @@ logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level='INFO') client = docker.Client() processed = {} +processed_folders = [] def build_library(repository=None, branch=None, namespace=None, push=False, @@ -92,20 +93,27 @@ def build_library(repository=None, branch=None, namespace=None, push=False, f.close() if dst_folder != repository: rmtree(dst_folder, True) + for d in processed_folders: + rmtree(d, True) summary.print_summary(logger) def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry): docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo) img_id = None + dst_folder = None if '{0}@{1}'.format(repository, ref) not in processed.keys(): logger.info('Cloning {0} (ref: {1})'.format(repository, ref)) - dst_folder = git.clone(repository, ref) + if repository not in processed: + rep, dst_folder = git.clone(repository, ref) + processed[repository] = rep + processed_folders.append(dst_folder) + else: + dst_folder = git.checkout(processed[repository], ref) if not 'Dockerfile' in os.listdir(dst_folder): raise RuntimeError('Dockerfile not found in cloned repository') logger.info('Building using dockerfile...') img_id, logs = client.build(path=dst_folder, quiet=True) - rmtree(dst_folder, True) else: img_id = processed['{0}@{1}'.format(repository, ref)] logger.info('Committing to {0}:{1}'.format(docker_repo, diff --git a/contrib/brew/brew/git.py b/contrib/brew/brew/git.py index 40cae8753b..e45e995458 100644 --- a/contrib/brew/brew/git.py +++ b/contrib/brew/brew/git.py @@ -16,6 +16,21 @@ def clone_tag(repo_url, tag, folder=None): return clone(repo_url, 'refs/tags/' + tag, folder) +def checkout(rep, ref=None): + is_commit = False + if ref is None: + ref = 'refs/heads/master' + elif not ref.startswith('refs/'): + is_commit = True + if is_commit: + rep['HEAD'] = rep.commit(ref) + else: + rep['HEAD'] = rep.refs[ref] + indexfile = rep.index_path() + tree = rep["HEAD"].tree + index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree) + return rep.path + def clone(repo_url, ref=None, folder=None): is_commit = False if ref is None: @@ -45,4 +60,4 @@ def clone(repo_url, ref=None, folder=None): tree = rep["HEAD"].tree index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree) logger.debug("done") - return folder + return rep, folder From 79fc90b6463d9b20391b4edd1540bc0a8e84da6f Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 12 Aug 2013 19:52:09 +0200 Subject: [PATCH 173/242] brew: Don't build if docker daemon can't be reached --- contrib/brew/brew/brew.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index 352d20c779..e07fdfdbbb 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -32,6 +32,15 @@ def build_library(repository=None, branch=None, namespace=None, push=False, logger.info('Repository provided assumed to be a local path') dst_folder = repository + try: + client.version() + except Exception as e: + logger.error('Could not reach the docker daemon. Please make sure it ' + 'is running.') + logger.warning('Also make sure you have access to the docker UNIX ' + 'socket (use sudo)') + return + #FIXME: set destination folder and only pull latest changes instead of # cloning the whole repo everytime if not dst_folder: From e5f1b6b9a4b934eab9c42d6534fe52672c018405 Mon Sep 17 00:00:00 2001 From: shin- Date: Tue, 13 Aug 2013 20:12:44 +0200 Subject: [PATCH 174/242] brew: Updated requirements --- contrib/brew/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/brew/requirements.txt b/contrib/brew/requirements.txt index 78a574953d..6100b01d01 100644 --- a/contrib/brew/requirements.txt +++ b/contrib/brew/requirements.txt @@ -1,2 +1,2 @@ dulwich==0.9.0 -docker-py==0.1.3 \ No newline at end of file +docker-py==0.1.4 \ No newline at end of file From 2cebe09924c9afea47bb1f2444ba1cd8fc423669 Mon Sep 17 00:00:00 2001 From: shin- Date: Tue, 13 Aug 2013 20:28:06 +0200 Subject: [PATCH 175/242] brew: Display a clear error message when the path is invalid --- contrib/brew/brew/brew.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/brew/brew/brew.py b/contrib/brew/brew/brew.py index e07fdfdbbb..22fe5b7b40 100644 --- a/contrib/brew/brew/brew.py +++ b/contrib/brew/brew/brew.py @@ -53,7 +53,13 @@ def build_library(repository=None, branch=None, namespace=None, push=False, logger.error('Source repository could not be fetched. Check ' 'that the address is correct and the branch exists.') return - for buildfile in os.listdir(os.path.join(dst_folder, 'library')): + try: + dirlist = os.listdir(os.path.join(dst_folder, 'library')) + except OSError as e: + logger.error('The path provided ({0}) could not be found or didn\'t' + 'contain a library/ folder.'.format(dst_folder)) + return + for buildfile in dirlist: if buildfile == 'MAINTAINERS': continue f = open(os.path.join(dst_folder, 'library', buildfile)) From e4f35dd4cf81a7f2d19a61cc8b1084c3adcc5253 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 13 Aug 2013 12:02:20 -0700 Subject: [PATCH 176/242] Update docs for docker group --- docs/sources/api/docker_remote_api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 9113d8e155..aa11ba50a2 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -16,6 +16,7 @@ Docker Remote API - The Remote API is replacing rcli - By default the Docker daemon listens on unix:///var/run/docker.sock and the client must have root access to interact with the daemon +- If a group named *docker* exists on your system, docker will apply ownership of the socket to the group - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr From e2409ad3376baeb36d1011732f7d7b1a239320ae Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 13 Aug 2013 13:45:07 -0700 Subject: [PATCH 177/242] Added information about Docker's high level tools over LXC. Formatting cleanup. Mailing list cleanup. --- docs/sources/api/registry_index_spec.rst | 3 +- docs/sources/faq.rst | 124 ++++++++++++++++++++--- docs/sources/use/builder.rst | 2 + 3 files changed, 116 insertions(+), 13 deletions(-) diff --git a/docs/sources/api/registry_index_spec.rst b/docs/sources/api/registry_index_spec.rst index a41523e813..4ea0c687db 100644 --- a/docs/sources/api/registry_index_spec.rst +++ b/docs/sources/api/registry_index_spec.rst @@ -2,9 +2,10 @@ :description: Documentation for docker Registry and Registry API :keywords: docker, registry, api, index +.. _registryindexspec: ===================== -Registry & index Spec +Registry & Index Spec ===================== .. contents:: Table of Contents diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index 3cc0086c5d..dd5fd11fd5 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -9,40 +9,140 @@ FAQ Most frequently asked questions. -------------------------------- -1. **How much does Docker cost?** +How much does Docker cost? +.......................... Docker is 100% free, it is open source, so you can use it without paying. -2. **What open source license are you using?** +What open source license are you using? +....................................... - We are using the Apache License Version 2.0, see it here: https://github.com/dotcloud/docker/blob/master/LICENSE + We are using the Apache License Version 2.0, see it here: + https://github.com/dotcloud/docker/blob/master/LICENSE -3. **Does Docker run on Mac OS X or Windows?** +Does Docker run on Mac OS X or Windows? +....................................... - Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a - virtual machine on your box, and get the best of both worlds. Check out the :ref:`install_using_vagrant` and :ref:`windows` installation guides. + Not at this time, Docker currently only runs on Linux, but you can + use VirtualBox to run Docker in a virtual machine on your box, and + get the best of both worlds. Check out the + :ref:`install_using_vagrant` and :ref:`windows` installation + guides. -4. **How do containers compare to virtual machines?** +How do containers compare to virtual machines? +.............................................. - They are complementary. VMs are best used to allocate chunks of hardware resources. Containers operate at the process level, which makes them very lightweight and perfect as a unit of software delivery. + They are complementary. VMs are best used to allocate chunks of + hardware resources. Containers operate at the process level, which + makes them very lightweight and perfect as a unit of software + delivery. -5. **Can I help by adding some questions and answers?** +What does Docker add to just plain LXC? +....................................... + + Docker is not a replacement for LXC. "LXC" refers to capabilities + of the Linux kernel (specifically namespaces and control groups) + which allow sandboxing processes from one another, and controlling + their resource allocations. On top of this low-level foundation of + kernel features, Docker offers a high-level tool with several + powerful functionalities: + + * *Portable deployment across machines.* + Docker defines a format for bundling an application and all its + dependencies into a single object which can be transferred to + any Docker-enabled machine, and executed there with the + guarantee that the execution environment exposed to the + application will be the same. LXC implements process sandboxing, + which is an important pre-requisite for portable deployment, but + that alone is not enough for portable deployment. If you sent me + a copy of your application installed in a custom LXC + configuration, it would almost certainly not run on my machine + the way it does on yours, because it is tied to your machine's + specific configuration: networking, storage, logging, distro, + etc. Docker defines an abstraction for these machine-specific + settings, so that the exact same Docker container can run - + unchanged - on many different machines, with many different + configurations. + + * *Application-centric.* + Docker is optimized for the deployment of applications, as + opposed to machines. This is reflected in its API, user + interface, design philosophy and documentation. By contrast, the + ``lxc`` helper scripts focus on containers as lightweight + machines - basically servers that boot faster and need less + RAM. We think there's more to containers than just that. + + * *Automatic build.* + Docker includes :ref:`a tool for developers to automatically + assemble a container from their source code `, + with full control over application dependencies, build tools, + packaging etc. They are free to use ``make, maven, chef, puppet, + salt,`` Debian packages, RPMs, source tarballs, or any + combination of the above, regardless of the configuration of the + machines. + + * *Versioning.* + Docker includes git-like capabilities for tracking successive + versions of a container, inspecting the diff between versions, + committing new versions, rolling back etc. The history also + includes how a container was assembled and by whom, so you get + full traceability from the production server all the way back to + the upstream developer. Docker also implements incremental + uploads and downloads, similar to ``git pull``, so new versions + of a container can be transferred by only sending diffs. + + * *Component re-use.* + Any container can be used as a :ref:`"base image" + ` to create more specialized components. This + can be done manually or as part of an automated build. For + example you can prepare the ideal Python environment, and use it + as a base for 10 different applications. Your ideal Postgresql + setup can be re-used for all your future projects. And so on. + + * *Sharing.* + Docker has access to a `public registry + `_ where thousands of people have + uploaded useful containers: anything from Redis, CouchDB, + Postgres to IRC bouncers to Rails app servers to Hadoop to base + images for various Linux distros. The :ref:`registry + ` also includes an official "standard + library" of useful containers maintained by the Docker team. The + registry itself is open-source, so anyone can deploy their own + registry to store and transfer private containers, for internal + server deployments for example. + + * *Tool ecosystem.* + Docker defines an API for automating and customizing the + creation and deployment of containers. There are a huge number + of tools integrating with Docker to extend its + capabilities. PaaS-like deployment (Dokku, Deis, Flynn), + multi-node orchestration (Maestro, Salt, Mesos, Openstack Nova), + management dashboards (docker-ui, Openstack Horizon, Shipyard), + configuration management (Chef, Puppet), continuous integration + (Jenkins, Strider, Travis), etc. Docker is rapidly establishing + itself as the standard for container-based tooling. + +Can I help by adding some questions and answers? +................................................ Definitely! You can fork `the repo`_ and edit the documentation sources. -42. **Where can I find more answers?** +Where can I find more answers? +.............................. You can find more answers on: - * `Docker club mailinglist`_ + * `Docker user mailinglist`_ + * `Docker developer mailinglist`_ * `IRC, docker on freenode`_ * `Github`_ * `Ask questions on Stackoverflow`_ * `Join the conversation on Twitter`_ - .. _Docker club mailinglist: https://groups.google.com/d/forum/docker-club + .. _Docker user mailinglist: https://groups.google.com/d/forum/docker-user + .. _Docker developer mailinglist: https://groups.google.com/d/forum/docker-dev .. _the repo: http://www.github.com/dotcloud/docker .. _IRC, docker on freenode: irc://chat.freenode.net#docker .. _Github: http://www.github.com/dotcloud/docker diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index d111e335ab..293ad32063 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -2,6 +2,8 @@ :description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. :keywords: builder, docker, Dockerfile, automation, image creation +.. _dockerbuilder: + ================== Dockerfile Builder ================== From f14db4934605235bd77e9d1dc22377ca710e4c7b Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Tue, 13 Aug 2013 16:18:32 -0700 Subject: [PATCH 178/242] [docs] Some user-friendly changes to the documentation. - Added parmalinks (closes #1527) - Changed the 'fork us on github' button to 'Edit this page on github', so people can edit quickly (closes #1532) - Changed the favicon --- docs/sources/conf.py | 5 ++--- docs/theme/docker/layout.html | 4 ++-- docs/theme/docker/static/css/main.css | 16 ++++++++++++++++ docs/theme/docker/static/css/main.less | 18 ++++++++++++++++++ docs/theme/docker/static/favicon.png | Bin 404 -> 1475 bytes 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/sources/conf.py b/docs/sources/conf.py index b4c23f0c58..9342ab503a 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -18,7 +18,7 @@ import sys, os # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) -# -- General configuration ----------------------------------------------------- +# -- General configuratiofn ----------------------------------------------------- @@ -52,8 +52,7 @@ source_suffix = '.rst' #source_encoding = 'utf-8-sig' #disable the parmalinks on headers, I find them really annoying -html_add_permalinks = None - +html_add_permalinks = u'¶' # The master toctree document. master_doc = 'toctree' diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index d6bfff79ba..2b7796628f 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -70,8 +70,8 @@