From 0f135ad7f31df2952352feb9ef00863d61577467 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 22 May 2013 20:07:26 -0700 Subject: [PATCH 01/15] 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 29103fac1..5812743df 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 ceeab002c..e0a55ae6c 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 5e459a1d9..4291b1d8d 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 564b1c812..06f947d89 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 { From d42c10aa094e39d8c1184b61c98777d8c59ae900 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 23 May 2013 18:32:56 -0700 Subject: [PATCH 02/15] Implement Context within docker build (not yet in use) --- api.go | 23 +++++----------- builder_client.go | 2 +- commands.go | 70 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 59 insertions(+), 36 deletions(-) diff --git a/api.go b/api.go index 5812743df..0a65543b7 100644 --- a/api.go +++ b/api.go @@ -1,7 +1,6 @@ package docker import ( - "bytes" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -631,32 +630,24 @@ func Upload(w http.ResponseWriter, req *http.Request) { } 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) + if err := r.ParseMultipartForm(4096); err != nil { return err } - mpr, err := r.MultipartReader() + file, _, err := r.FormFile("Dockerfile") if err != nil { return err } - p, err := mpr.NextPart() + context, _, err := r.FormFile("Context") if err != nil { - return err + if err != http.ErrMissingFile { + 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 { + if _, err := b.Build(file, context); err != nil { return err } return nil diff --git a/builder_client.go b/builder_client.go index e0a55ae6c..0b511ee21 100644 --- a/builder_client.go +++ b/builder_client.go @@ -255,7 +255,7 @@ func (b *builderClient) commit(id string) error { return nil } -func (b *builderClient) Build(dockerfile io.Reader) (string, error) { +func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) { defer b.clearTmp(b.tmpContainers, b.tmpImages) file := bufio.NewReader(dockerfile) for { diff --git a/commands.go b/commands.go index 4291b1d8d..f5c658e6b 100644 --- a/commands.go +++ b/commands.go @@ -57,7 +57,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n" for cmd, description := range map[string]string{ "attach": "Attach to a running container", - "build": "Build a container from Dockerfile or via stdin", + "build": "Build a container from a Dockerfile", "commit": "Create a new image from a container's changes", "diff": "Inspect changes on a container's filesystem", "export": "Stream the contents of a container as a tar archive", @@ -112,36 +112,67 @@ func (cli *DockerCli) CmdInsert(args ...string) error { } func (cli *DockerCli) CmdBuild(args ...string) error { + cmd := Subcmd("build", "[OPTIONS]", "Build an image from a Dockerfile") + fileName := cmd.String("f", "Dockerfile", "Use file as Dockerfile. Can be '-' for stdin") + contextPath := cmd.String("c", "", "Use the specified directory as context for the build") + if err := cmd.Parse(args); err != nil { + return nil + } + var ( + file io.ReadCloser + multipartBody io.Reader + err error + ) + + // Init the needed component for the Multipart buff := bytes.NewBuffer([]byte{}) - + multipartBody = buff w := multipart.NewWriter(buff) + boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n") - dockerfile, err := w.CreateFormFile("Dockerfile", "Dockerfile") + // Create a FormFile multipart for the Dockerfile + if *fileName == "-" { + file = os.Stdin + } else { + file, err = os.Open(*fileName) + if err != nil { + return err + } + defer file.Close() + } + if _, err := w.CreateFormFile("Dockerfile", *fileName); err != nil { + return err + } + multipartBody = io.MultiReader(multipartBody, file) + + // Create a FormFile multipart for the context if needed + if *contextPath != "" { + // FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage? + context, err := Tar(*contextPath, Bzip2) + if err != nil { + return err + } + if _, err := w.CreateFormFile("Context", *contextPath+".tar.bz2"); err != nil { + return err + } + multipartBody = io.MultiReader(multipartBody, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Uploading Context %v/%v (%v)")) + } + + // Send the multipart request with correct content-type + req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), io.MultiReader(multipartBody, boundary)) 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.Header.Set("Content-Type", w.FormDataContentType()) - // 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) + resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() + // Check for errors if resp.StatusCode < 200 || resp.StatusCode >= 400 { body, err := ioutil.ReadAll(resp.Body) if err != nil { @@ -150,6 +181,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { return fmt.Errorf("error: %s", body) } + // Output the result if _, err := io.Copy(os.Stdout, resp.Body); err != nil { return err } @@ -180,7 +212,7 @@ func (cli *DockerCli) CmdBuildClient(args ...string) error { return err } } - if _, err := NewBuilderClient("0.0.0.0", 4243).Build(file); err != nil { + if _, err := NewBuilderClient("0.0.0.0", 4243).Build(file, nil); err != nil { return err } return nil From e3f04298597195a95557c4224c05df752dc08695 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 23 May 2013 18:33:31 -0700 Subject: [PATCH 03/15] Add missing buildfile.go --- buildfile.go | 311 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 buildfile.go diff --git a/buildfile.go b/buildfile.go new file mode 100644 index 000000000..0784f5432 --- /dev/null +++ b/buildfile.go @@ -0,0 +1,311 @@ +package docker + +import ( + "bufio" + "encoding/json" + "fmt" + "github.com/dotcloud/docker/utils" + "io" + "os" + "reflect" + "strings" +) + +type BuildFile interface { + Build(io.Reader, io.Reader) (string, error) + CmdFrom(string) error + CmdRun(string) error +} + +type buildFile struct { + runtime *Runtime + builder *Builder + srv *Server + + image string + maintainer string + config *Config + + tmpContainers map[string]struct{} + tmpImages map[string]struct{} + + needCommit bool + + out io.Writer +} + +func (b *buildFile) clearTmp(containers, images map[string]struct{}) { + for c := range containers { + tmp := b.runtime.Get(c) + b.runtime.Destroy(tmp) + utils.Debugf("Removing container %s", c) + } + for i := range images { + b.runtime.graph.Delete(i) + utils.Debugf("Removing image %s", i) + } +} + +func (b *buildFile) CmdFrom(name string) error { + image, err := b.runtime.repositories.LookupImage(name) + if err != nil { + if b.runtime.graph.IsNotExist(err) { + + var tag, remote string + if strings.Contains(name, ":") { + remoteParts := strings.Split(name, ":") + tag = remoteParts[1] + remote = remoteParts[0] + } else { + remote = name + } + + if err := b.srv.ImagePull(remote, tag, "", b.out); err != nil { + return err + } + + image, err = b.runtime.repositories.LookupImage(name) + if err != nil { + return err + } + } else { + return err + } + } + b.image = image.Id + b.config = &Config{} + return nil +} + +func (b *buildFile) CmdMaintainer(name string) error { + b.needCommit = true + b.maintainer = name + return nil +} + +func (b *buildFile) CmdRun(args string) error { + if b.image == "" { + return fmt.Errorf("Please provide a source image with `from` prior to run") + } + config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil) + if err != nil { + return err + } + + cmd, env := b.config.Cmd, b.config.Env + b.config.Cmd = nil + MergeConfig(b.config, config) + + if cache, err := b.srv.ImageGetCached(b.image, config); err != nil { + return err + } else if cache != nil { + utils.Debugf("Use cached version") + b.image = cache.Id + return nil + } + + cid, err := b.run() + if err != nil { + return err + } + b.config.Cmd, b.config.Env = cmd, env + return b.commit(cid) +} + +func (b *buildFile) CmdEnv(args string) error { + b.needCommit = true + tmp := strings.SplitN(args, " ", 2) + if len(tmp) != 2 { + return fmt.Errorf("Invalid ENV format") + } + key := strings.Trim(tmp[0], " ") + value := strings.Trim(tmp[1], " ") + + for i, elem := range b.config.Env { + if strings.HasPrefix(elem, key+"=") { + b.config.Env[i] = key + "=" + value + return nil + } + } + b.config.Env = append(b.config.Env, key+"="+value) + return nil +} + +func (b *buildFile) CmdCmd(args string) error { + b.needCommit = true + var cmd []string + if err := json.Unmarshal([]byte(args), &cmd); err != nil { + utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err) + b.config.Cmd = []string{"/bin/sh", "-c", args} + } else { + b.config.Cmd = cmd + } + return nil +} + +func (b *buildFile) CmdExpose(args string) error { + ports := strings.Split(args, " ") + b.config.PortSpecs = append(ports, b.config.PortSpecs...) + return nil +} + +func (b *buildFile) CmdInsert(args string) error { + if b.image == "" { + return fmt.Errorf("Please provide a source image with `from` prior to insert") + } + tmp := strings.SplitN(args, " ", 2) + if len(tmp) != 2 { + return fmt.Errorf("Invalid INSERT format") + } + sourceUrl := strings.Trim(tmp[0], " ") + destPath := strings.Trim(tmp[1], " ") + + file, err := utils.Download(sourceUrl, b.out) + if err != nil { + return err + } + defer file.Body.Close() + + cid, err := b.run() + if err != nil { + return err + } + + container := b.runtime.Get(cid) + if container == nil { + return fmt.Errorf("An error occured while creating the container") + } + + if err := container.Inject(file.Body, destPath); err != nil { + return err + } + + return b.commit(cid) +} + +func (b *buildFile) run() (string, error) { + if b.image == "" { + return "", fmt.Errorf("Please provide a source image with `from` prior to run") + } + b.config.Image = b.image + + // Create the container and start it + c, err := b.builder.Create(b.config) + if err != nil { + return "", err + } + b.tmpContainers[c.Id] = struct{}{} + + //start the container + if err := c.Start(); err != nil { + return "", err + } + + // Wait for it to finish + if ret := c.Wait(); ret != 0 { + return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret) + } + + return c.Id, nil +} + +func (b *buildFile) commit(id string) error { + if b.image == "" { + return fmt.Errorf("Please provide a source image with `from` prior to commit") + } + b.config.Image = b.image + + if id == "" { + cmd := b.config.Cmd + b.config.Cmd = []string{"true"} + if cid, err := b.run(); err != nil { + return err + } else { + id = cid + } + b.config.Cmd = cmd + } + + container := b.runtime.Get(id) + if container == nil { + return fmt.Errorf("An error occured while creating the container") + } + + // Commit the container + image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil) + if err != nil { + return err + } + b.tmpImages[image.Id] = struct{}{} + b.image = image.Id + b.needCommit = false + return nil +} + +func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { + b.out = os.Stdout + + defer b.clearTmp(b.tmpContainers, b.tmpImages) + file := bufio.NewReader(dockerfile) + for { + line, err := file.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + return "", err + } + line = strings.Replace(strings.TrimSpace(line), " ", " ", 1) + // Skip comments and empty line + if len(line) == 0 || line[0] == '#' { + continue + } + tmp := strings.SplitN(line, " ", 2) + if len(tmp) != 2 { + return "", fmt.Errorf("Invalid Dockerfile format") + } + instruction := strings.ToLower(strings.Trim(tmp[0], " ")) + arguments := strings.Trim(tmp[1], " ") + + fmt.Fprintf(b.out, "%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.Fprintf(b.out, "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.Fprintf(b.out, "===> %v\n", b.image) + } + if b.needCommit { + if err := b.commit(""); err != nil { + return "", err + } + } + if b.image != "" { + // The build is successful, keep the temporary containers and images + for i := range b.tmpImages { + delete(b.tmpImages, i) + } + fmt.Fprintf(b.out, "Build finished. image id: %s\n", b.image) + return b.image, nil + } + for i := range b.tmpContainers { + delete(b.tmpContainers, i) + } + return "", fmt.Errorf("An error occured during the build\n") +} + +func NewBuildFile(srv *Server, out io.Writer) BuildFile { + return &buildFile{ + builder: NewBuilder(srv.runtime), + runtime: srv.runtime, + srv: srv, + config: &Config{}, + tmpContainers: make(map[string]struct{}), + tmpImages: make(map[string]struct{}), + } +} From 2cd00a47a5a8f405eb6a7b3f34edaf38c89e9b1c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 23 May 2013 18:34:38 -0700 Subject: [PATCH 04/15] remove unused function --- api.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/api.go b/api.go index 0a65543b7..d3eb9101b 100644 --- a/api.go +++ b/api.go @@ -599,36 +599,6 @@ 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 { if err := r.ParseMultipartForm(4096); err != nil { return err From 54db18625aa7154c9dd230907444676fa3079b99 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 13:37:49 -0700 Subject: [PATCH 05/15] Add Extension() method to Compresison type --- api.go | 1 - archive.go | 17 ++++++++++++++++- buildfile.go | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index d3eb9101b..a1a694962 100644 --- a/api.go +++ b/api.go @@ -9,7 +9,6 @@ import ( "io" "log" "net/http" - "os" "strconv" "strings" ) diff --git a/archive.go b/archive.go index 8a011eb6e..4120a52c1 100644 --- a/archive.go +++ b/archive.go @@ -2,6 +2,7 @@ package docker import ( "errors" + "fmt" "io" "io/ioutil" "os" @@ -31,6 +32,20 @@ func (compression *Compression) Flag() string { return "" } +func (compression *Compression) Extension() string { + switch *compression { + case Uncompressed: + return "tar" + case Bzip2: + return "tar.bz2" + case Gzip: + return "tar.gz" + case Xz: + return "tar.xz" + } + return "" +} + func Tar(path string, compression Compression) (io.Reader, error) { cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".") return CmdStream(cmd) @@ -41,7 +56,7 @@ func Untar(archive io.Reader, path string) error { cmd.Stdin = archive output, err := cmd.CombinedOutput() if err != nil { - return errors.New(err.Error() + ": " + string(output)) + return fmt.Errorf("%s: %s", err, output) } return nil } diff --git a/buildfile.go b/buildfile.go index 0784f5432..d0f0b6e7d 100644 --- a/buildfile.go +++ b/buildfile.go @@ -6,7 +6,9 @@ import ( "fmt" "github.com/dotcloud/docker/utils" "io" + "io/ioutil" "os" + "path" "reflect" "strings" ) From 6ae3800151025ff73a97c40af578ee714164003b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 13:38:26 -0700 Subject: [PATCH 06/15] Implement the CmdAdd instruction --- buildfile.go | 48 +++++++++++++++++++++++++++++++++++++++++++++--- commands.go | 29 ----------------------------- runtime_test.go | 11 ++--------- utils/utils.go | 7 +++++++ 4 files changed, 54 insertions(+), 41 deletions(-) diff --git a/buildfile.go b/buildfile.go index d0f0b6e7d..0cd8aa182 100644 --- a/buildfile.go +++ b/buildfile.go @@ -27,6 +27,7 @@ type buildFile struct { image string maintainer string config *Config + context string tmpContainers map[string]struct{} tmpImages map[string]struct{} @@ -168,6 +169,7 @@ func (b *buildFile) CmdInsert(args string) error { } defer file.Body.Close() + b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath} cid, err := b.run() if err != nil { return err @@ -185,6 +187,36 @@ func (b *buildFile) CmdInsert(args string) error { return b.commit(cid) } +func (b *buildFile) CmdAdd(args string) error { + tmp := strings.SplitN(args, " ", 2) + if len(tmp) != 2 { + return fmt.Errorf("Invalid INSERT format") + } + orig := strings.Trim(tmp[0], " ") + dest := strings.Trim(tmp[1], " ") + + b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest} + cid, err := b.run() + if err != nil { + return err + } + + container := b.runtime.Get(cid) + if container == nil { + return fmt.Errorf("Error while creating the container (CmdAdd)") + } + + if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil { + return err + } + + if err := utils.CopyDirectory(path.Join(b.context, orig), path.Join(container.rwPath(), dest)); err != nil { + return err + } + + return b.commit(cid) +} + func (b *buildFile) run() (string, error) { if b.image == "" { return "", fmt.Errorf("Please provide a source image with `from` prior to run") @@ -216,7 +248,6 @@ func (b *buildFile) commit(id string) error { return fmt.Errorf("Please provide a source image with `from` prior to commit") } b.config.Image = b.image - if id == "" { cmd := b.config.Cmd b.config.Cmd = []string{"true"} @@ -245,9 +276,19 @@ func (b *buildFile) commit(id string) error { } func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { - b.out = os.Stdout - defer b.clearTmp(b.tmpContainers, b.tmpImages) + + if context != nil { + name, err := ioutil.TempDir("/tmp", "docker-build") + if err != nil { + return "", err + } + if err := Untar(context, name); err != nil { + return "", err + } + defer os.RemoveAll(name) + b.context = name + } file := bufio.NewReader(dockerfile) for { line, err := file.ReadString('\n') @@ -307,6 +348,7 @@ func NewBuildFile(srv *Server, out io.Writer) BuildFile { runtime: srv.runtime, srv: srv, config: &Config{}, + out: out, tmpContainers: make(map[string]struct{}), tmpImages: make(map[string]struct{}), } diff --git a/commands.go b/commands.go index f5c658e6b..79fdafe7d 100644 --- a/commands.go +++ b/commands.go @@ -189,35 +189,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error { 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 - } - var ( - file io.ReadCloser - err error - ) - - if cmd.NArg() == 0 { - file, err = os.Open("Dockerfile") - if err != nil { - return err - } - } else if cmd.Arg(0) == "-" { - file = os.Stdin - } else { - file, err = os.Open(cmd.Arg(0)) - if err != nil { - return err - } - } - if _, err := NewBuilderClient("0.0.0.0", 4243).Build(file, nil); err != nil { - return err - } - return nil -} - // 'docker login': login / register a user to registry service. func (cli *DockerCli) CmdLogin(args ...string) error { var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { diff --git a/runtime_test.go b/runtime_test.go index 01bd2a012..9ca280495 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -32,13 +32,6 @@ func nuke(runtime *Runtime) error { return os.RemoveAll(runtime.root) } -func CopyDirectory(source, dest string) error { - if _, err := exec.Command("cp", "-ra", source, dest).Output(); err != nil { - return err - } - return nil -} - func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) @@ -88,7 +81,7 @@ func newTestRuntime() (*Runtime, error) { if err := os.Remove(root); err != nil { return nil, err } - if err := CopyDirectory(unitTestStoreBase, root); err != nil { + if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil { return nil, err } @@ -345,7 +338,7 @@ func TestRestore(t *testing.T) { if err := os.Remove(root); err != nil { t.Fatal(err) } - if err := CopyDirectory(unitTestStoreBase, root); err != nil { + if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil { t.Fatal(err) } diff --git a/utils/utils.go b/utils/utils.go index 150eae857..90ef30625 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -531,6 +531,13 @@ func GetKernelVersion() (*KernelVersionInfo, error) { }, nil } +func CopyDirectory(source, dest string) error { + if _, err := exec.Command("cp", "-ra", source, dest).Output(); err != nil { + return err + } + return nil +} + type NopFlusher struct{} func (f *NopFlusher) Flush() {} From 90ffcda05547332020ec6f2b98179380f7d0e56f Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 13:38:40 -0700 Subject: [PATCH 07/15] Update the UI for docker build --- commands.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/commands.go b/commands.go index 79fdafe7d..cfe6c38a4 100644 --- a/commands.go +++ b/commands.go @@ -112,9 +112,8 @@ func (cli *DockerCli) CmdInsert(args ...string) error { } func (cli *DockerCli) CmdBuild(args ...string) error { - cmd := Subcmd("build", "[OPTIONS]", "Build an image from a Dockerfile") - fileName := cmd.String("f", "Dockerfile", "Use file as Dockerfile. Can be '-' for stdin") - contextPath := cmd.String("c", "", "Use the specified directory as context for the build") + cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile") + fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin") if err := cmd.Parse(args); err != nil { return nil } @@ -146,14 +145,16 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } multipartBody = io.MultiReader(multipartBody, file) + compression := Bzip2 + // Create a FormFile multipart for the context if needed - if *contextPath != "" { + if cmd.Arg(0) != "" { // FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage? - context, err := Tar(*contextPath, Bzip2) + context, err := Tar(cmd.Arg(0), compression) if err != nil { return err } - if _, err := w.CreateFormFile("Context", *contextPath+".tar.bz2"); err != nil { + if _, err := w.CreateFormFile("Context", cmd.Arg(0)+"."+compression.Extension()); err != nil { return err } multipartBody = io.MultiReader(multipartBody, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Uploading Context %v/%v (%v)")) @@ -165,6 +166,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { return err } req.Header.Set("Content-Type", w.FormDataContentType()) + if cmd.Arg(0) != "" { + req.Header.Set("X-Docker-Context-Compression", compression.Flag()) + } resp, err := http.DefaultClient.Do(req) if err != nil { From a48799016a43e6badae72e855f9a90592b6cdd98 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 13:46:52 -0700 Subject: [PATCH 08/15] Fix merge issue --- api.go | 2 +- buildfile.go | 2 +- commands.go | 2 +- server.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index e004aa4a3..7b82135ef 100644 --- a/api.go +++ b/api.go @@ -626,7 +626,7 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r * return nil } -func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := r.ParseMultipartForm(4096); err != nil { return err } diff --git a/buildfile.go b/buildfile.go index 0cd8aa182..f1e08b20f 100644 --- a/buildfile.go +++ b/buildfile.go @@ -63,7 +63,7 @@ func (b *buildFile) CmdFrom(name string) error { remote = name } - if err := b.srv.ImagePull(remote, tag, "", b.out); err != nil { + if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil { return err } diff --git a/commands.go b/commands.go index 952a2bb6c..a79a24bff 100644 --- a/commands.go +++ b/commands.go @@ -175,7 +175,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if _, err := w.CreateFormFile("Context", cmd.Arg(0)+"."+compression.Extension()); err != nil { return err } - multipartBody = io.MultiReader(multipartBody, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Uploading Context %v/%v (%v)")) + multipartBody = io.MultiReader(multipartBody, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Uploading Context %v/%v (%v)", false)) } // Send the multipart request with correct content-type diff --git a/server.go b/server.go index 0714cba54..245558780 100644 --- a/server.go +++ b/server.go @@ -92,7 +92,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e } if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil { - return err + return "", err } // FIXME: Handle custom repo, tag comment, author img, err = b.Commit(c, "", "", img.Comment, img.Author, nil) From 582a9e0a67598672db35ef26a18027dd6fb222ca Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 13:47:04 -0700 Subject: [PATCH 09/15] Make docker build flush output each line --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index 7b82135ef..ca65c8e11 100644 --- a/api.go +++ b/api.go @@ -643,7 +643,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } } - b := NewBuildFile(srv, w) + b := NewBuildFile(srv, utils.NewWriteFlusher(w)) if _, err := b.Build(file, context); err != nil { return err } From cfb8cbe5214c58690ca99cd44b25bd202c4dbcf7 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 13:51:21 -0700 Subject: [PATCH 10/15] Small fix --- buildfile.go | 3 +++ commands.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index f1e08b20f..88dcadbf9 100644 --- a/buildfile.go +++ b/buildfile.go @@ -188,6 +188,9 @@ func (b *buildFile) CmdInsert(args string) error { } func (b *buildFile) CmdAdd(args string) error { + if b.context == "" { + return fmt.Errorf("No context given. Impossible to use ADD") + } tmp := strings.SplitN(args, " ", 2) if len(tmp) != 2 { return fmt.Errorf("Invalid INSERT format") diff --git a/commands.go b/commands.go index a79a24bff..5d65daeb5 100644 --- a/commands.go +++ b/commands.go @@ -175,7 +175,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if _, err := w.CreateFormFile("Context", cmd.Arg(0)+"."+compression.Extension()); err != nil { return err } - multipartBody = io.MultiReader(multipartBody, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Uploading Context %v/%v (%v)", false)) + multipartBody = io.MultiReader(multipartBody, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Uploading Context %v/%v (%v)\r", false)) } // Send the multipart request with correct content-type From fe0c0c208c0e816419b668a6fd6567520698c2d2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 15:21:06 -0700 Subject: [PATCH 11/15] Send error without headers when using chunks --- api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index ca65c8e11..12abb3da2 100644 --- a/api.go +++ b/api.go @@ -631,7 +631,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ return err } - file, _, err := r.FormFile("Dockerfile") + dockerfile, _, err := r.FormFile("Dockerfile") if err != nil { return err } @@ -644,8 +644,8 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } b := NewBuildFile(srv, utils.NewWriteFlusher(w)) - if _, err := b.Build(file, context); err != nil { - return err + if _, err := b.Build(dockerfile, context); err != nil { + fmt.Fprintf(w, "Error build: %s\n", err) } return nil } From 2897cb04760d7e4e7e52ccc20e94c94e3743667e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 15:22:01 -0700 Subject: [PATCH 12/15] Add directory contents instead of while directory for docker build --- buildfile.go | 21 ++++++++++++++++++++- utils/utils.go | 10 ++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/buildfile.go b/buildfile.go index 88dcadbf9..15577b98e 100644 --- a/buildfile.go +++ b/buildfile.go @@ -213,9 +213,28 @@ func (b *buildFile) CmdAdd(args string) error { return err } - if err := utils.CopyDirectory(path.Join(b.context, orig), path.Join(container.rwPath(), dest)); err != nil { + origPath := path.Join(b.context, orig) + destPath := path.Join(container.rwPath(), dest) + + fi, err := os.Stat(origPath) + if err != nil { return err } + if fi.IsDir() { + files, err := ioutil.ReadDir(path.Join(b.context, orig)) + if err != nil { + return err + } + for _, fi := range files { + if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil { + return err + } + } + } else { + if err := utils.CopyDirectory(origPath, destPath); err != nil { + return err + } + } return b.commit(cid) } diff --git a/utils/utils.go b/utils/utils.go index ac0a142ae..97bdea9e9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -69,7 +69,7 @@ type progressReader struct { readProgress int // How much has been read so far (bytes) lastUpdate int // How many bytes read at least update template string // Template to print. Default "%v/%v (%v)" - json bool + json bool } func (r *progressReader) Read(p []byte) (n int, err error) { @@ -102,7 +102,7 @@ func (r *progressReader) Close() error { return io.ReadCloser(r.reader).Close() } func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader { - if template == "" { + if template == "" { template = "%v/%v (%v)\r" } return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json} @@ -533,8 +533,8 @@ func GetKernelVersion() (*KernelVersionInfo, error) { } func CopyDirectory(source, dest string) error { - if _, err := exec.Command("cp", "-ra", source, dest).Output(); err != nil { - return err + if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil { + return fmt.Errorf("Error copy: %s (%s)", err, output) } return nil } @@ -577,5 +577,3 @@ func FormatProgress(str string, json bool) string { } return "Downloading " + str + "\r" } - - From 2127f8d6ad091d699d3462715305a0f64340eca1 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 15:22:34 -0700 Subject: [PATCH 13/15] Fill the multipart writer directly instead of using reader --- commands.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index 5d65daeb5..bca825925 100644 --- a/commands.go +++ b/commands.go @@ -158,10 +158,12 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } defer file.Close() } - if _, err := w.CreateFormFile("Dockerfile", *fileName); err != nil { + if wField, err := w.CreateFormFile("Dockerfile", *fileName); err != nil { return err + } else { + io.Copy(wField, file) } - multipartBody = io.MultiReader(multipartBody, file) + multipartBody = io.MultiReader(multipartBody, boundary) compression := Bzip2 @@ -172,20 +174,30 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if err != nil { return err } - if _, err := w.CreateFormFile("Context", cmd.Arg(0)+"."+compression.Extension()); err != nil { + // NOTE: Do this in case '.' or '..' is input + absPath, err := filepath.Abs(cmd.Arg(0)) + if err != nil { return err } - multipartBody = io.MultiReader(multipartBody, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Uploading Context %v/%v (%v)\r", false)) + if wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension()); err != nil { + return err + } else { + // FIXME: Find a way to have a progressbar for the upload too + io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false)) + } + + multipartBody = io.MultiReader(multipartBody, boundary) } // Send the multipart request with correct content-type - req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), io.MultiReader(multipartBody, boundary)) + req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), multipartBody) if err != nil { return err } req.Header.Set("Content-Type", w.FormDataContentType()) if cmd.Arg(0) != "" { req.Header.Set("X-Docker-Context-Compression", compression.Flag()) + fmt.Println("Uploading Context...") } resp, err := http.DefaultClient.Do(req) From 5b33b2463a27aa3356a4b6200cf4b81dd83c26a0 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 15:31:06 -0700 Subject: [PATCH 14/15] Readd build tests --- buildfile_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++ runtime_test.go | 1 - 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 buildfile_test.go diff --git a/buildfile_test.go b/buildfile_test.go new file mode 100644 index 000000000..b6f4e62ae --- /dev/null +++ b/buildfile_test.go @@ -0,0 +1,72 @@ +package docker + +import ( + "github.com/dotcloud/docker/utils" + "strings" + "testing" +) + +const Dockerfile = ` +# VERSION 0.1 +# DOCKER-VERSION 0.2 + +from ` + unitTestImageName + ` +run sh -c 'echo root:testpass > /tmp/passwd' +run mkdir -p /var/run/sshd +` + +func TestBuild(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + buildfile := NewBuildFile(srv, &utils.NopWriter{}) + + imgId, err := buildfile.Build(strings.NewReader(Dockerfile), nil) + if err != nil { + t.Fatal(err) + } + + builder := NewBuilder(runtime) + container, err := builder.Create( + &Config{ + Image: imgId, + Cmd: []string{"cat", "/tmp/passwd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + output, err := container.Output() + if err != nil { + t.Fatal(err) + } + if string(output) != "root:testpass\n" { + t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n") + } + + container2, err := builder.Create( + &Config{ + Image: imgId, + Cmd: []string{"ls", "-d", "/var/run/sshd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container2) + + output, err = container2.Output() + if err != nil { + t.Fatal(err) + } + if string(output) != "/var/run/sshd\n" { + t.Fatal("/var/run/sshd has not been created") + } +} diff --git a/runtime_test.go b/runtime_test.go index 27db53a88..55671e12b 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -8,7 +8,6 @@ import ( "io/ioutil" "net" "os" - "os/exec" "os/user" "sync" "testing" From 54af0536232ca71d37f7c6440f1bb17b3ed112db Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 28 May 2013 15:40:22 -0700 Subject: [PATCH 15/15] Make sure the last line of docker build is the image id --- buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 15577b98e..23f2f4717 100644 --- a/buildfile.go +++ b/buildfile.go @@ -355,7 +355,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { for i := range b.tmpImages { delete(b.tmpImages, i) } - fmt.Fprintf(b.out, "Build finished. image id: %s\n", b.image) + fmt.Fprintf(b.out, "Build success.\n Image id:\n%s\n", b.image) return b.image, nil } for i := range b.tmpContainers {