diff --git a/api.go b/api.go index 3c440d7331..621d9c82e1 100644 --- a/api.go +++ b/api.go @@ -650,6 +650,13 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ if err := r.ParseMultipartForm(4096); err != nil { return err } + remote := r.FormValue("t") + tag := "" + if strings.Contains(remote, ":") { + remoteParts := strings.Split(remote, ":") + tag = remoteParts[1] + remote = remoteParts[0] + } dockerfile, _, err := r.FormFile("Dockerfile") if err != nil { @@ -664,8 +671,10 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } b := NewBuildFile(srv, utils.NewWriteFlusher(w)) - if _, err := b.Build(dockerfile, context); err != nil { + if id, err := b.Build(dockerfile, context); err != nil { fmt.Fprintf(w, "Error build: %s\n", err) + } else if remote != "" { + srv.runtime.repositories.Set(remote, tag, id, false) } return nil } diff --git a/buildfile.go b/buildfile.go index 23f2f47172..d9a4b5eff6 100644 --- a/buildfile.go +++ b/buildfile.go @@ -32,8 +32,6 @@ type buildFile struct { tmpContainers map[string]struct{} tmpImages map[string]struct{} - needCommit bool - out io.Writer } @@ -81,9 +79,8 @@ func (b *buildFile) CmdFrom(name string) error { } func (b *buildFile) CmdMaintainer(name string) error { - b.needCommit = true b.maintainer = name - return nil + return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name)) } func (b *buildFile) CmdRun(args string) error { @@ -95,28 +92,34 @@ func (b *buildFile) CmdRun(args string) error { return err } - cmd, env := b.config.Cmd, b.config.Env + cmd := b.config.Cmd b.config.Cmd = nil MergeConfig(b.config, config) - if cache, err := b.srv.ImageGetCached(b.image, config); err != nil { + 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 { - utils.Debugf("Use cached version") + utils.Debugf("[BUILDER] Use cached version") b.image = cache.Id return nil + } else { + utils.Debugf("[BUILDER] Cache miss") } cid, err := b.run() if err != nil { return err } - b.config.Cmd, b.config.Env = cmd, env - return b.commit(cid) + if err := b.commit(cid, cmd, "run"); err != nil { + return err + } + b.config.Cmd = cmd + return nil } 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") @@ -131,60 +134,34 @@ func (b *buildFile) CmdEnv(args string) error { } } b.config.Env = append(b.config.Env, key+"="+value) - return nil + return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value)) } 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 + cmd = []string{"/bin/sh", "-c", args} } + if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil { + return err + } + 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 + return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports)) } 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], " ") + return fmt.Errorf("INSERT has been deprecated. Please use ADD instead") +} - file, err := utils.Download(sourceUrl, b.out) - if err != nil { - return err - } - defer file.Body.Close() - - b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath} - 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) CmdCopy(args string) error { + return fmt.Errorf("COPY has been deprecated. Please use ADD instead") } func (b *buildFile) CmdAdd(args string) error { @@ -193,12 +170,13 @@ func (b *buildFile) CmdAdd(args string) error { } tmp := strings.SplitN(args, " ", 2) if len(tmp) != 2 { - return fmt.Errorf("Invalid INSERT format") + return fmt.Errorf("Invalid ADD format") } orig := strings.Trim(tmp[0], " ") dest := strings.Trim(tmp[1], " ") - b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest} + cmd := b.config.Cmd + b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} cid, err := b.run() if err != nil { return err @@ -208,19 +186,23 @@ func (b *buildFile) CmdAdd(args string) error { if container == nil { return fmt.Errorf("Error while creating the container (CmdAdd)") } - - if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil { + if err := container.EnsureMounted(); err != nil { return err } + defer container.Unmount() origPath := path.Join(b.context, orig) - destPath := path.Join(container.rwPath(), dest) + destPath := path.Join(container.RootfsPath(), dest) fi, err := os.Stat(origPath) if err != nil { return err } if fi.IsDir() { + if err := os.MkdirAll(destPath, 0700); err != nil { + return err + } + files, err := ioutil.ReadDir(path.Join(b.context, orig)) if err != nil { return err @@ -231,12 +213,18 @@ func (b *buildFile) CmdAdd(args string) error { } } } else { + if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil { + return err + } if err := utils.CopyDirectory(origPath, destPath); err != nil { return err } } - - return b.commit(cid) + if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { + return err + } + b.config.Cmd = cmd + return nil } func (b *buildFile) run() (string, error) { @@ -265,20 +253,30 @@ func (b *buildFile) run() (string, error) { return c.Id, nil } -func (b *buildFile) commit(id string) error { +// Commit the container with the autorun command +func (b *buildFile) commit(id string, autoCmd []string, comment 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"} + b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} + + if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { + return err + } else if cache != nil { + utils.Debugf("[BUILDER] Use cached version") + b.image = cache.Id + return nil + } else { + utils.Debugf("[BUILDER] Cache miss") + } + if cid, err := b.run(); err != nil { return err } else { id = cid } - b.config.Cmd = cmd } container := b.runtime.Get(id) @@ -286,20 +284,20 @@ func (b *buildFile) commit(id string) error { return fmt.Errorf("An error occured while creating the container") } + // Note: Actually copy the struct + autoConfig := *b.config + autoConfig.Cmd = autoCmd // Commit the container - image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil) + image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig) 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) { - defer b.clearTmp(b.tmpContainers, b.tmpImages) - if context != nil { name, err := ioutil.TempDir("/tmp", "docker-build") if err != nil { @@ -337,6 +335,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { 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 } ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface() if ret != nil { @@ -345,22 +344,10 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, 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 success.\n Image id:\n%s\n", b.image) + fmt.Fprintf(b.out, "Build successful.\n===> %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") } diff --git a/commands.go b/commands.go index f5c00ec698..38deb1a561 100644 --- a/commands.go +++ b/commands.go @@ -17,6 +17,7 @@ import ( "net/url" "os" "os/signal" + "path" "path/filepath" "reflect" "strconv" @@ -130,16 +131,20 @@ func (cli *DockerCli) CmdInsert(args ...string) error { } func (cli *DockerCli) CmdBuild(args ...string) error { - cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile") - fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin") + cmd := Subcmd("build", "[OPTIONS] PATH | -", "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") if err := cmd.Parse(args); err != nil { return nil } + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } var ( - file io.ReadCloser multipartBody io.Reader - err error + file io.ReadCloser + contextPath string ) // Init the needed component for the Multipart @@ -148,27 +153,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error { w := multipart.NewWriter(buff) boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n") - // 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 wField, err := w.CreateFormFile("Dockerfile", *fileName); err != nil { - return err - } else { - io.Copy(wField, file) - } - multipartBody = io.MultiReader(multipartBody, boundary) - compression := Bzip2 - // Create a FormFile multipart for the context if needed - if cmd.Arg(0) != "" { + if cmd.Arg(0) == "-" { + file = os.Stdin + } else { + // Send Dockerfile from arg/Dockerfile (deprecate later) + if f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")); err != nil { + return err + } else { + file = f + } + // Send context from arg + // Create a FormFile multipart for the context if needed // FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage? context, err := Tar(cmd.Arg(0), compression) if err != nil { @@ -185,17 +182,25 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // 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) } + // Create a FormFile multipart for the Dockerfile + if wField, err := w.CreateFormFile("Dockerfile", "Dockerfile"); err != nil { + return err + } else { + io.Copy(wField, file) + } + multipartBody = io.MultiReader(multipartBody, boundary) + v := &url.Values{} + v.Set("t", *tag) // Send the multipart request with correct content-type - req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), multipartBody) + req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody) if err != nil { return err } req.Header.Set("Content-Type", w.FormDataContentType()) - if cmd.Arg(0) != "" { + if contextPath != "" { req.Header.Set("X-Docker-Context-Compression", compression.Flag()) fmt.Println("Uploading Context...") } diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index a8d2093a5b..81120b22d2 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -2,12 +2,27 @@ :description: Build a new image from the Dockerfile passed via stdin :keywords: build, docker, container, documentation -======================================================== -``build`` -- Build a container from Dockerfile via stdin -======================================================== +================================================ +``build`` -- Build a container from a Dockerfile +================================================ :: - Usage: docker build - - Example: cat Dockerfile | docker build - - Build a new image from the Dockerfile passed via stdin + Usage: docker build [OPTIONS] PATH | - + Build a new container image from the source code at PATH + -t="": Tag to be applied to the resulting image in case of success. + +Examples +-------- + +.. code-block:: bash + + docker build . + +This will take the local Dockerfile + +.. code-block:: bash + + docker build - + +This will read a Dockerfile form Stdin without context diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 4e53ed4a79..abd5b9ecb1 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -125,8 +125,14 @@ curl was installed within the image. .. note:: The path must include the file name. -.. note:: - This instruction has temporarily disabled +2.8 ADD +------- + + ``ADD `` + +The `ADD` instruction will insert the files from the `` path of the context into `` path +of the container. +The context must be set in order to use this instruction. (see examples) 3. Dockerfile Examples ======================