diff --git a/api.go b/api.go index 8984d00cd9..4cfb0aac74 100644 --- a/api.go +++ b/api.go @@ -283,23 +283,17 @@ func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars src := r.Form.Get("fromSrc") image := r.Form.Get("fromImage") - repo := r.Form.Get("repo") tag := r.Form.Get("tag") + repo := r.Form.Get("repo") - in, out, err := hijackServer(w) - if err != nil { - return err - } - defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if image != "" { //pull registry := r.Form.Get("registry") - if err := srv.ImagePull(image, tag, registry, out); err != nil { - fmt.Fprintf(out, "Error: %s\n", err) + if err := srv.ImagePull(image, tag, registry, w); err != nil { + return err } } else { //import - if err := srv.ImageImport(src, repo, tag, in, out); err != nil { - fmt.Fprintf(out, "Error: %s\n", err) + if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil { + return err } } return nil @@ -335,15 +329,9 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars } name := vars["name"] - in, out, err := hijackServer(w) - if err != nil { + if err := srv.ImageInsert(name, url, path, w); err != nil { return err } - defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if err := srv.ImageInsert(name, url, path, out); err != nil { - fmt.Fprintf(out, "Error: %s\n", err) - } return nil } @@ -358,28 +346,16 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars ma } name := vars["name"] - in, out, err := hijackServer(w) - if err != nil { + if err := srv.ImagePush(name, registry, w); err != nil { return err } - defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if err := srv.ImagePush(name, registry, out); err != nil { - fmt.Fprintf(out, "Error: %s\n", err) - } return nil } func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - in, out, err := hijackServer(w) - if err != nil { + if err := srv.ImageCreateFromFile(r.Body, w); err != nil { return err } - defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if err := srv.ImageCreateFromFile(in, out); err != nil { - fmt.Fprintf(out, "Error: %s\n", err) - } return nil } diff --git a/api_test.go b/api_test.go index f77d39e3cc..700d2c4b2c 100644 --- a/api_test.go +++ b/api_test.go @@ -14,6 +14,7 @@ import ( "net/http/httptest" "os" "path" + "strings" "testing" "time" ) @@ -587,45 +588,29 @@ func TestPostBuild(t *testing.T) { srv := &Server{runtime: runtime} - stdin, stdinPipe := io.Pipe() - stdout, stdoutPipe := io.Pipe() + imgs, err := runtime.graph.All() + if err != nil { + t.Fatal(err) + } + beginCount := len(imgs) - c1 := make(chan struct{}) - go func() { - defer close(c1) - r := &hijackTester{ - ResponseRecorder: httptest.NewRecorder(), - in: stdin, - out: stdoutPipe, - } - - if err := postBuild(srv, r, nil, nil); err != nil { - t.Fatal(err) - } - }() - - // Acknowledge hijack - setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { - stdout.Read([]byte{}) - stdout.Read(make([]byte, 4096)) - }) - - setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("from docker-ut\n", "FROM docker-ut", stdout, stdinPipe, 15); err != nil { - t.Fatal(err) - } - }) - - // Close pipes (client disconnects) - if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + req, err := http.NewRequest("POST", "/build", strings.NewReader(Dockerfile)) + if err != nil { t.Fatal(err) } - // Wait for build to finish, the client disconnected, therefore, Build finished his job - setTimeout(t, "Waiting for CmdBuild timed out", 2*time.Second, func() { - <-c1 - }) + r := httptest.NewRecorder() + if err := postBuild(srv, r, req, nil); err != nil { + t.Fatal(err) + } + imgs, err = runtime.graph.All() + if err != nil { + t.Fatal(err) + } + if len(imgs) != beginCount+3 { + t.Fatalf("Expected %d images, %d found", beginCount+3, len(imgs)) + } } func TestPostImagesCreate(t *testing.T) { diff --git a/commands.go b/commands.go index 33ba8125de..e4aa768d68 100644 --- a/commands.go +++ b/commands.go @@ -104,7 +104,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error { v.Set("url", cmd.Arg(1)) v.Set("path", cmd.Arg(2)) - err := cli.hijack("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), false) + err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, os.Stdout) if err != nil { return err } @@ -117,7 +117,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { return nil } - err := cli.hijack("POST", "/build", false) + err := cli.stream("POST", "/build", os.Stdin, os.Stdout) if err != nil { return err } @@ -571,7 +571,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { v.Set("tag", tag) v.Set("fromSrc", src) - err := cli.hijack("POST", "/images/create?"+v.Encode(), false) + err := cli.stream("POST", "/images/create?"+v.Encode(), os.Stdin, os.Stdout) if err != nil { return err } @@ -628,7 +628,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { v := url.Values{} v.Set("registry", *registry) - if err := cli.hijack("POST", "/images/"+name+"/push?"+v.Encode(), false); err != nil { + if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, os.Stdout); err != nil { return err } return nil @@ -659,7 +659,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { v.Set("tag", *tag) v.Set("registry", *registry) - if err := cli.hijack("POST", "/images/create?"+v.Encode(), false); err != nil { + if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, os.Stdout); err != nil { return err } @@ -864,7 +864,7 @@ func (cli *DockerCli) CmdExport(args ...string) error { return nil } - if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export"); err != nil { + if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, os.Stdout); err != nil { return err } return nil @@ -1086,7 +1086,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if statusCode == 404 { v := url.Values{} v.Set("fromImage", config.Image) - err = cli.hijack("POST", "/images/create?"+v.Encode(), false) + err = cli.stream("POST", "/images/create?"+v.Encode(), nil, os.Stderr) if err != nil { return err } @@ -1179,8 +1179,11 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, return body, resp.StatusCode, nil } -func (cli *DockerCli) stream(method, path string) error { - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, path), nil) +func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) error { + if (method == "POST" || method == "PUT") && in == nil { + in = bytes.NewReader([]byte{}) + } + req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, path), in) if err != nil { return err } @@ -1204,7 +1207,7 @@ func (cli *DockerCli) stream(method, path string) error { return fmt.Errorf("error: %s", body) } - if _, err := io.Copy(os.Stdout, resp.Body); err != nil { + if _, err := io.Copy(out, resp.Body); err != nil { return err } return nil diff --git a/registry/registry.go b/registry/registry.go index e2ffb292c5..ce9b4b4ac7 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -175,7 +175,6 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ } func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { - utils.Debugf("Pulling repository %s from %s\r\n", remote, auth.IndexServerAddress()) repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images" req, err := http.NewRequest("GET", repositoryTarget, nil) @@ -327,10 +326,11 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat if err != nil { return nil, err } - - utils.Debugf("json sent: %s\n", imgListJson) - - req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/", bytes.NewReader(imgListJson)) + var suffix string + if validate { + suffix = "images" + } + req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJson)) if err != nil { return nil, err } @@ -362,29 +362,28 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat defer res.Body.Close() } - if res.StatusCode != 200 && res.StatusCode != 201 { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err + var tokens, endpoints []string + if !validate { + if res.StatusCode != 200 && res.StatusCode != 201 { + errBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody) + } + if res.Header.Get("X-Docker-Token") != "" { + tokens = res.Header["X-Docker-Token"] + utils.Debugf("Auth token: %v", tokens) + } else { + return nil, fmt.Errorf("Index response didn't contain an access token") } - return nil, fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody) - } - var tokens []string - if res.Header.Get("X-Docker-Token") != "" { - tokens = res.Header["X-Docker-Token"] - utils.Debugf("Auth token: %v", tokens) - } else { - return nil, fmt.Errorf("Index response didn't contain an access token") + if res.Header.Get("X-Docker-Endpoints") != "" { + endpoints = res.Header["X-Docker-Endpoints"] + } else { + return nil, fmt.Errorf("Index response didn't contain any endpoints") + } } - - var endpoints []string - if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints = res.Header["X-Docker-Endpoints"] - } else { - return nil, fmt.Errorf("Index response didn't contain any endpoints") - } - if validate { if res.StatusCode != 204 { if errBody, err := ioutil.ReadAll(res.Body); err != nil { diff --git a/server.go b/server.go index dafa44ec84..d336248271 100644 --- a/server.go +++ b/server.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" @@ -67,6 +68,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) { } func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error { + out = utils.NewWriteFlusher(out) img, err := srv.runtime.repositories.LookupImage(name) if err != nil { return err @@ -288,6 +290,7 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error { } func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []string) error { + out = utils.NewWriteFlusher(out) history, err := srv.registry.GetRemoteHistory(imgId, registry, token) if err != nil { return err @@ -322,8 +325,9 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri return nil } -func (srv *Server) pullRepository(stdout io.Writer, remote, askedTag string) error { - utils.Debugf("Retrieving repository data") +func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error { + out = utils.NewWriteFlusher(out) + fmt.Fprintf(out, "Pulling repository %s from %s\r\n", remote, auth.IndexServerAddress()) repoData, err := srv.registry.GetRepositoryData(remote) if err != nil { return err @@ -360,11 +364,11 @@ func (srv *Server) pullRepository(stdout io.Writer, remote, askedTag string) err utils.Debugf("%s does not match %s, skipping", img.Tag, askedTag) continue } - fmt.Fprintf(stdout, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote) + fmt.Fprintf(out, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote) success := false for _, ep := range repoData.Endpoints { - if err := srv.pullImage(stdout, img.Id, "https://"+ep+"/v1", repoData.Tokens); err != nil { - fmt.Fprintf(stdout, "Error while retrieving image for tag: %s (%s); checking next endpoint\n", askedTag, err) + if err := srv.pullImage(out, img.Id, "https://"+ep+"/v1", repoData.Tokens); err != nil { + fmt.Fprintf(out, "Error while retrieving image for tag: %s (%s); checking next endpoint\n", askedTag, err) continue } if err := srv.runtime.repositories.Set(remote, img.Tag, img.Id, true); err != nil { @@ -476,6 +480,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat } func (srv *Server) pushRepository(out io.Writer, name string, localRepo map[string]string) error { + out = utils.NewWriteFlusher(out) fmt.Fprintf(out, "Processing checksums\n") imgList, err := srv.getImageList(localRepo) if err != nil { @@ -515,6 +520,7 @@ func (srv *Server) pushRepository(out io.Writer, name string, localRepo map[stri } func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []string) 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) @@ -574,6 +580,7 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st } func (srv *Server) ImagePush(name, registry string, out io.Writer) error { + out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(name) if err != nil { fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name])) @@ -610,7 +617,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write u.Host = src u.Path = "" } - fmt.Fprintln(out, "Downloading from", u) + fmt.Fprintf(out, "Downloading from %s\n", u) // Download with curl (pretty progress bar) // If curl is not available, fallback to http.Get() resp, err = utils.Download(u.String(), out) @@ -629,7 +636,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write return err } } - fmt.Fprintln(out, img.ShortId()) + fmt.Fprintf(out, "%s\n", img.ShortId()) return nil } diff --git a/utils/utils.go b/utils/utils.go index 88d0c87f5c..150eae8570 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -104,7 +104,7 @@ func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string if template == "" { template = "%v/%v (%v)" } - return &progressReader{r, output, size, 0, 0, template} + return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template} } // HumanDuration returns a human-readable approximation of a duration @@ -530,3 +530,28 @@ func GetKernelVersion() (*KernelVersionInfo, error) { Flavor: flavor, }, nil } + +type NopFlusher struct{} + +func (f *NopFlusher) Flush() {} + +type WriteFlusher struct { + w io.Writer + flusher http.Flusher +} + +func (wf *WriteFlusher) Write(b []byte) (n int, err error) { + n, err = wf.w.Write(b) + wf.flusher.Flush() + return n, err +} + +func NewWriteFlusher(w io.Writer) *WriteFlusher { + var flusher http.Flusher + if f, ok := w.(http.Flusher); ok { + flusher = f + } else { + flusher = &NopFlusher{} + } + return &WriteFlusher{w: w, flusher: flusher} +}