From 0f135ad7f31df2952352feb9ef00863d61577467 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 22 May 2013 20:07:26 -0700 Subject: [PATCH] Start moving the docker builder into the server --- api.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++- builder_client.go | 35 +++++++++++++-------- commands.go | 50 +++++++++++++++++++++++++++-- server.go | 16 +++++----- 4 files changed, 157 insertions(+), 24 deletions(-) diff --git a/api.go b/api.go index 29103fac10..5812743dfe 100644 --- a/api.go +++ b/api.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -9,6 +10,7 @@ import ( "io" "log" "net/http" + "os" "strconv" "strings" ) @@ -31,6 +33,13 @@ func parseForm(r *http.Request) error { return nil } +func parseMultipartForm(r *http.Request) error { + if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") { + return err + } + return nil +} + func httpError(w http.ResponseWriter, err error) { if strings.HasPrefix(err.Error(), "No such") { http.Error(w, err.Error(), http.StatusNotFound) @@ -329,9 +338,15 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars } name := vars["name"] - if err := srv.ImageInsert(name, url, path, w); err != nil { + imgId, err := srv.ImageInsert(name, url, path, w) + if err != nil { return err } + b, err := json.Marshal(&ApiId{Id: imgId}) + if err != nil { + return err + } + writeJson(w, b) return nil } @@ -585,6 +600,68 @@ func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, var return nil } +func Upload(w http.ResponseWriter, req *http.Request) { + + mr, err := req.MultipartReader() + if err != nil { + return + } + length := req.ContentLength + for { + + part, err := mr.NextPart() + if err == io.EOF { + break + } + var read int64 + var p float32 + for { + buffer := make([]byte, 100000) + cBytes, err := part.Read(buffer) + if err == io.EOF { + break + } + read = read + int64(cBytes) + //fmt.Printf("read: %v \n",read ) + p = float32(read) / float32(length) * 100 + fmt.Printf("progress: %v \n", p) + os.Stdout.Write(buffer) + } + } +} + +func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + + Upload(w, r) + + // io.Copy(os.Stderr, r.Body) + + if err := r.ParseMultipartForm(409699); err != nil { + utils.Debugf("----- %s\n", err) + return err + } + + mpr, err := r.MultipartReader() + if err != nil { + return err + } + + p, err := mpr.NextPart() + if err != nil { + return err + } + + dockerfile := make([]byte, 4096) + p.Read(dockerfile) + + utils.Debugf("Dockerfile >>>%s<<<\n", dockerfile) + b := NewBuildFile(srv, w) + if _, err := b.Build(bytes.NewReader(dockerfile)); err != nil { + return err + } + return nil +} + func ListenAndServe(addr string, srv *Server, logging bool) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) @@ -607,6 +684,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { "POST": { "/auth": postAuth, "/commit": postCommit, + "/build": postBuild, "/images/create": postImagesCreate, "/images/{name:.*}/insert": postImagesInsert, "/images/{name:.*}/push": postImagesPush, diff --git a/builder_client.go b/builder_client.go index ceeab002c9..e0a55ae6c9 100644 --- a/builder_client.go +++ b/builder_client.go @@ -12,12 +12,6 @@ import ( "strings" ) -type BuilderClient interface { - Build(io.Reader) (string, error) - CmdFrom(string) error - CmdRun(string) error -} - type builderClient struct { cli *DockerCli @@ -164,8 +158,23 @@ func (b *builderClient) CmdExpose(args string) error { } func (b *builderClient) CmdInsert(args string) error { - // FIXME: Reimplement this once the remove_hijack branch gets merged. - // We need to retrieve the resulting Id + // tmp := strings.SplitN(args, "\t ", 2) + // sourceUrl, destPath := tmp[0], tmp[1] + + // v := url.Values{} + // v.Set("url", sourceUrl) + // v.Set("path", destPath) + // body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil) + // if err != nil { + // return err + // } + + // apiId := &ApiId{} + // if err := json.Unmarshal(body, apiId); err != nil { + // return err + // } + + // FIXME: Reimplement this, we need to retrieve the resulting Id return fmt.Errorf("INSERT not implemented") } @@ -269,18 +278,18 @@ func (b *builderClient) Build(dockerfile io.Reader) (string, error) { instruction := strings.ToLower(strings.Trim(tmp[0], " ")) arguments := strings.Trim(tmp[1], " ") - fmt.Printf("%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image) + fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image) method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:])) if !exists { - fmt.Printf("Skipping unknown instruction %s\n", strings.ToUpper(instruction)) + fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction)) } ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface() if ret != nil { return "", ret.(error) } - fmt.Printf("===> %v\n", b.image) + fmt.Fprintf(os.Stderr, "===> %v\n", b.image) } if b.needCommit { if err := b.commit(""); err != nil { @@ -295,13 +304,13 @@ func (b *builderClient) Build(dockerfile io.Reader) (string, error) { for i := range b.tmpContainers { delete(b.tmpContainers, i) } - fmt.Printf("Build finished. image id: %s\n", b.image) + fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image) return b.image, nil } return "", fmt.Errorf("An error occured during the build\n") } -func NewBuilderClient(addr string, port int) BuilderClient { +func NewBuilderClient(addr string, port int) BuildFile { return &builderClient{ cli: NewDockerCli(addr, port), config: &Config{}, diff --git a/commands.go b/commands.go index 5e459a1d94..4291b1d8de 100644 --- a/commands.go +++ b/commands.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" + "mime/multipart" "net" "net/http" "net/http/httputil" @@ -104,14 +105,59 @@ func (cli *DockerCli) CmdInsert(args ...string) error { v.Set("url", cmd.Arg(1)) v.Set("path", cmd.Arg(2)) - err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, os.Stdout) - if err != nil { + if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, os.Stdout); err != nil { return err } return nil } func (cli *DockerCli) CmdBuild(args ...string) error { + + buff := bytes.NewBuffer([]byte{}) + + w := multipart.NewWriter(buff) + + dockerfile, err := w.CreateFormFile("Dockerfile", "Dockerfile") + if err != nil { + return err + } + file, err := os.Open("Dockerfile") + if err != nil { + return err + } + dockerfile.Write([]byte(w.Boundary() + "\r\n")) + if _, err := io.Copy(dockerfile, file); err != nil { + return err + } + dockerfile.Write([]byte("\r\n" + w.Boundary())) + + // req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), buff) + // if err != nil { + // return err + // } + // req.Header.Set("Content-Type", w.FormDataContentType()) + resp, err := http.Post(fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), w.FormDataContentType(), buff) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("error: %s", body) + } + + if _, err := io.Copy(os.Stdout, resp.Body); err != nil { + return err + } + + return nil +} + +func (cli *DockerCli) CmdBuildClient(args ...string) error { cmd := Subcmd("build", "-|Dockerfile", "Build an image from Dockerfile or via stdin") if err := cmd.Parse(args); err != nil { return nil diff --git a/server.go b/server.go index 564b1c812d..06f947d89f 100644 --- a/server.go +++ b/server.go @@ -67,40 +67,40 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) { return outs, nil } -func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error { +func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) { out = utils.NewWriteFlusher(out) img, err := srv.runtime.repositories.LookupImage(name) if err != nil { - return err + return "", err } file, err := utils.Download(url, out) if err != nil { - return err + return "", err } defer file.Body.Close() config, _, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, srv.runtime.capabilities) if err != nil { - return err + return "", err } b := NewBuilder(srv.runtime) c, err := b.Create(config) if err != nil { - return err + return "", err } if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil { - return err + return "", err } // FIXME: Handle custom repo, tag comment, author img, err = b.Commit(c, "", "", img.Comment, img.Author, nil) if err != nil { - return err + return "", err } fmt.Fprintf(out, "%s\n", img.Id) - return nil + return img.ShortId(), nil } func (srv *Server) ImagesViz(out io.Writer) error {