From 31d2b258c15a28402c3fc5df6440c5b8644a6d76 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Jun 2013 15:40:46 -0700 Subject: [PATCH 01/30] Allow remote url to be passed to the ADD instruction within the builder --- buildfile.go | 72 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/buildfile.go b/buildfile.go index 5ea5607fc7..bf67c5a213 100644 --- a/buildfile.go +++ b/buildfile.go @@ -164,33 +164,21 @@ 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 { - 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 ADD format") - } - orig := strings.Trim(tmp[0], " ") - dest := strings.Trim(tmp[1], " ") +func (b *buildFile) isUrl(str string) bool { + return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://") +} - cmd := b.config.Cmd - b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} - cid, err := b.run() +func (b *buildFile) addRemote(container *Container, orig, dest string) error { + file, err := utils.Download(orig, ioutil.Discard) if err != nil { return err } + defer file.Body.Close() - container := b.runtime.Get(cid) - if container == nil { - return fmt.Errorf("Error while creating the container (CmdAdd)") - } - if err := container.EnsureMounted(); err != nil { - return err - } - defer container.Unmount() + return container.Inject(file.Body, dest) +} +func (b *buildFile) addContext(container *Container, orig, dest string) error { origPath := path.Join(b.context, orig) destPath := path.Join(container.RootfsPath(), dest) @@ -220,6 +208,46 @@ func (b *buildFile) CmdAdd(args string) error { return err } } + return nil +} + +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 ADD format") + } + orig := strings.Trim(tmp[0], " ") + dest := strings.Trim(tmp[1], " ") + + 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 + } + + container := b.runtime.Get(cid) + if container == nil { + return fmt.Errorf("Error while creating the container (CmdAdd)") + } + if err := container.EnsureMounted(); err != nil { + return err + } + defer container.Unmount() + + if b.isUrl(orig) { + if err := b.addRemote(container, orig, dest); err != nil { + return err + } + } else { + if err := b.addContext(container, orig, dest); err != nil { + return err + } + } + if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { return err } @@ -318,7 +346,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { } return "", err } - line = strings.Replace(strings.TrimSpace(line), " ", " ", 1) + line = strings.TrimSpace(strings.Replace(line, " ", " ", -1)) // Skip comments and empty line if len(line) == 0 || line[0] == '#' { continue From eaa2183d77435bd5d65f36287e96a341696cb79c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Jun 2013 15:48:12 -0700 Subject: [PATCH 02/30] Fix issue EXPOSE override CMD within builder --- buildfile.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildfile.go b/buildfile.go index bf67c5a213..a91d779ff5 100644 --- a/buildfile.go +++ b/buildfile.go @@ -288,7 +288,9 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { } b.config.Image = b.image if id == "" { + cmd := b.config.Cmd 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 From f4a4cfd2cc748859d4f28b90274c81098afa8d5d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Jun 2013 15:50:09 -0700 Subject: [PATCH 03/30] Move isUrl to utils.IsURL --- buildfile.go | 6 +----- utils/utils.go | 18 +++++++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/buildfile.go b/buildfile.go index a91d779ff5..6ed0276bec 100644 --- a/buildfile.go +++ b/buildfile.go @@ -164,10 +164,6 @@ func (b *buildFile) CmdCopy(args string) error { return fmt.Errorf("COPY has been deprecated. Please use ADD instead") } -func (b *buildFile) isUrl(str string) bool { - return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://") -} - func (b *buildFile) addRemote(container *Container, orig, dest string) error { file, err := utils.Download(orig, ioutil.Discard) if err != nil { @@ -238,7 +234,7 @@ func (b *buildFile) CmdAdd(args string) error { } defer container.Unmount() - if b.isUrl(orig) { + if utils.IsURL(orig) { if err := b.addRemote(container, orig, dest); err != nil { return err } diff --git a/utils/utils.go b/utils/utils.go index c3f9e571d3..752cc03f32 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -70,7 +70,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)" - sf *StreamFormatter + sf *StreamFormatter } func (r *progressReader) Read(p []byte) (n int, err error) { @@ -103,7 +103,7 @@ 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) + tpl := string(template) if tpl == "" { tpl = string(sf.FormatProgress("", "%v/%v (%v)")) } @@ -585,7 +585,7 @@ func (sf *StreamFormatter) FormatStatus(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{Status: str}) if err != nil { return sf.FormatError(err) } @@ -597,7 +597,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: err.Error()}); err == nil { return b } return []byte("{\"error\":\"format error\"}") @@ -608,10 +608,10 @@ func (sf *StreamFormatter) FormatError(err error) []byte { func (sf *StreamFormatter) FormatProgress(action, str 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}) if err != nil { - return nil - } + return nil + } return b } return []byte(action + " " + str + "\r") @@ -620,3 +620,7 @@ func (sf *StreamFormatter) FormatProgress(action, str string) []byte { func (sf *StreamFormatter) Used() bool { return sf.used } + +func IsURL(str string) bool { + return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://") +} From 01f446e908305efb86f9cbfbbe3302366a6ccd45 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Jun 2013 15:56:09 -0700 Subject: [PATCH 04/30] Allow to docker build URL --- commands.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index 50aaac8fb1..50cca552c0 100644 --- a/commands.go +++ b/commands.go @@ -157,6 +157,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if cmd.Arg(0) == "-" { file = os.Stdin + } else if utils.IsURL(cmd.Arg(0)) { + f, err := utils.Download(cmd.Arg(0), ioutil.Discard) + if err != nil { + return err + } + defer f.Body.Close() + file = f.Body } else { // Send Dockerfile from arg/Dockerfile (deprecate later) f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")) @@ -289,15 +296,12 @@ func (cli *DockerCli) CmdLogin(args ...string) error { return err } - var out auth.AuthConfig - err = json.Unmarshal(body, &out) - if err != nil { + out := &auth.AuthConfig{} + if err := json.Unmarshal(body, out); err != nil { return err } - var username string - var password string - var email string + var username, password, email string fmt.Print("Username (", out.Username, "): ") username = readAndEchoString(os.Stdin, os.Stdout) @@ -330,9 +334,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error { return err } - var out2 APIAuth - err = json.Unmarshal(body, &out2) - if err != nil { + out2 := &APIAuth{} + if err := json.Unmarshal(body, &out2); err != nil { return err } if out2.Status != "" { From a11e61677c7b368628975e2a0ead639b5bfcabb9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Jun 2013 16:09:46 -0700 Subject: [PATCH 05/30] Move the docker build URL form client to server, prepare for GIT support --- api.go | 47 ++++++++++++++++++++++++++++++++++------------- commands.go | 29 +++++++++++++++-------------- utils/utils.go | 4 ++++ 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/api.go b/api.go index 5a9bdbbe23..94dd94c404 100644 --- a/api.go +++ b/api.go @@ -7,6 +7,7 @@ import ( "github.com/dotcloud/docker/utils" "github.com/gorilla/mux" "io" + "io/ioutil" "log" "net/http" "strconv" @@ -674,31 +675,51 @@ 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") + remoteURL := r.FormValue("remote") + repoName := r.FormValue("t") tag := "" - if strings.Contains(remote, ":") { - remoteParts := strings.Split(remote, ":") + if strings.Contains(repoName, ":") { + remoteParts := strings.Split(repoName, ":") tag = remoteParts[1] - remote = remoteParts[0] + repoName = remoteParts[0] } - dockerfile, _, err := r.FormFile("Dockerfile") - if err != nil { - return err - } + var dockerfile, context io.ReadCloser - context, _, err := r.FormFile("Context") - if err != nil { - if err != http.ErrMissingFile { + if remoteURL == "" { + d, _, err := r.FormFile("Dockerfile") + if err != nil { return err + } else { + dockerfile = d + } + c, _, err := r.FormFile("Context") + if err != nil { + if err != http.ErrMissingFile { + return err + } + } else { + context = c + } + } else { + if utils.IsGIT(remoteURL) { + return fmt.Errorf("Builder from git is not yet supported") + } else if utils.IsURL(remoteURL) { + f, err := utils.Download(remoteURL, ioutil.Discard) + if err != nil { + return err + } else { + dockerfile = f.Body + } + defer f.Body.Close() } } b := NewBuildFile(srv, utils.NewWriteFlusher(w)) 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) + } else if repoName != "" { + srv.runtime.repositories.Set(repoName, tag, id, false) } return nil } diff --git a/commands.go b/commands.go index 50cca552c0..de0e6432f2 100644 --- a/commands.go +++ b/commands.go @@ -155,15 +155,12 @@ func (cli *DockerCli) CmdBuild(args ...string) error { compression := Bzip2 + isRemote := false + if cmd.Arg(0) == "-" { file = os.Stdin - } else if utils.IsURL(cmd.Arg(0)) { - f, err := utils.Download(cmd.Arg(0), ioutil.Discard) - if err != nil { - return err - } - defer f.Body.Close() - file = f.Body + } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { + isRemote = true } else { // Send Dockerfile from arg/Dockerfile (deprecate later) f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")) @@ -192,16 +189,20 @@ func (cli *DockerCli) CmdBuild(args ...string) error { io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf)) multipartBody = io.MultiReader(multipartBody, boundary) } - // Create a FormFile multipart for the Dockerfile - wField, err := w.CreateFormFile("Dockerfile", "Dockerfile") - if err != nil { - return err + if !isRemote { + // Create a FormFile multipart for the Dockerfile + wField, err := w.CreateFormFile("Dockerfile", "Dockerfile") + if err != nil { + return err + } + io.Copy(wField, file) + multipartBody = io.MultiReader(multipartBody, boundary) } - io.Copy(wField, file) - multipartBody = io.MultiReader(multipartBody, boundary) - v := &url.Values{} v.Set("t", *tag) + if isRemote { + v.Set("remote", cmd.Arg(0)) + } // Send the multipart request with correct content-type req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody) if err != nil { diff --git a/utils/utils.go b/utils/utils.go index 752cc03f32..f0b0f22b46 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -624,3 +624,7 @@ func (sf *StreamFormatter) Used() bool { func IsURL(str string) bool { return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://") } + +func IsGIT(str string) bool { + return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/") +} From 12c9b9b3c94d595ab155fc90dfc426eeada8bc75 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Jun 2013 16:41:41 -0700 Subject: [PATCH 06/30] Implement build from git --- api.go | 36 ++++++++++++++++++++++++++++++++++-- commands.go | 3 ++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index 94dd94c404..58125c934e 100644 --- a/api.go +++ b/api.go @@ -10,6 +10,9 @@ import ( "io/ioutil" "log" "net/http" + "os" + "os/exec" + "path" "strconv" "strings" ) @@ -684,7 +687,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ repoName = remoteParts[0] } - var dockerfile, context io.ReadCloser + var dockerfile, context io.Reader if remoteURL == "" { d, _, err := r.FormFile("Dockerfile") @@ -703,7 +706,36 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } } else { if utils.IsGIT(remoteURL) { - return fmt.Errorf("Builder from git is not yet supported") + if !strings.HasPrefix(remoteURL, "git://") { + remoteURL = "https://" + remoteURL + } + root, err := ioutil.TempDir("", "docker-build-git") + if err != nil { + return err + } + defer os.RemoveAll(root) + + if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil { + return fmt.Errorf("Error trying to use git: %s (%s)", err, output) + } + + d, err := os.Open(path.Join(root, "Dockerfile")) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("No Dockerfile found in the repository") + } + return err + } else { + dockerfile = d + } + + c, err := Tar(root, Bzip2) + if err != nil { + return err + } else { + context = c + } + } else if utils.IsURL(remoteURL) { f, err := utils.Download(remoteURL, ioutil.Discard) if err != nil { diff --git a/commands.go b/commands.go index de0e6432f2..853f1cdebc 100644 --- a/commands.go +++ b/commands.go @@ -196,8 +196,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { return err } io.Copy(wField, file) - multipartBody = io.MultiReader(multipartBody, boundary) } + multipartBody = io.MultiReader(multipartBody, boundary) + v := &url.Values{} v.Set("t", *tag) if isRemote { From 2cc22de696d5a0847ecd7d07eaee1d7f385dd29c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Jun 2013 16:48:36 -0700 Subject: [PATCH 07/30] Update documentation for docker build --- docs/sources/api/docker_remote_api.rst | 1 + docs/sources/commandline/command/build.rst | 4 +++- docs/sources/use/builder.rst | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 1c46cf148a..fd268e2760 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -871,6 +871,7 @@ Build an image from Dockerfile via stdin {{ STREAM }} :query t: tag to be applied to the resulting image in case of success + :query remote: URL to be fetch. Either a single Dockerfile or a Git repository :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 81120b22d2..4810ed46ff 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -8,9 +8,11 @@ :: - Usage: docker build [OPTIONS] PATH | - + 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. + When a single Dockerfile is given as URL, then no context is set. When a git reppository is set as URL, the repository is used as context + Examples -------- diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index abd5b9ecb1..4cf4045f7d 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -132,6 +132,8 @@ curl was installed within the image. The `ADD` instruction will insert the files from the `` path of the context into `` path of the container. +`` can be a local path or a remote file URL. + The context must be set in order to use this instruction. (see examples) 3. Dockerfile Examples From b103ac70bfb9f9419d4487bf969b0f244b260014 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Jun 2013 09:31:59 -0700 Subject: [PATCH 08/30] Allow multiple tab/spaces between instructions and arguments --- buildfile.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/buildfile.go b/buildfile.go index 6ed0276bec..b38101c72b 100644 --- a/buildfile.go +++ b/buildfile.go @@ -124,8 +124,8 @@ func (b *buildFile) CmdEnv(args string) error { if len(tmp) != 2 { return fmt.Errorf("Invalid ENV format") } - key := strings.Trim(tmp[0], " ") - value := strings.Trim(tmp[1], " ") + key := strings.Trim(tmp[0], " \t") + value := strings.Trim(tmp[1], " \t") for i, elem := range b.config.Env { if strings.HasPrefix(elem, key+"=") { @@ -215,8 +215,8 @@ func (b *buildFile) CmdAdd(args string) error { if len(tmp) != 2 { return fmt.Errorf("Invalid ADD format") } - orig := strings.Trim(tmp[0], " ") - dest := strings.Trim(tmp[1], " ") + orig := strings.Trim(tmp[0], " \t") + dest := strings.Trim(tmp[1], " \t") cmd := b.config.Cmd b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} @@ -344,7 +344,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { } return "", err } - line = strings.TrimSpace(strings.Replace(line, " ", " ", -1)) + line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n") // Skip comments and empty line if len(line) == 0 || line[0] == '#' { continue From 8984aef8999509e5e6a1cb70286e44d0508f93e0 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Jun 2013 09:32:31 -0700 Subject: [PATCH 09/30] Fix typo in docs --- docs/sources/commandline/command/build.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index 4810ed46ff..e3b56dbd44 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -11,7 +11,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. - When a single Dockerfile is given as URL, then no context is set. When a git reppository is set as URL, the repository is used as context + 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 Examples From b38c6929be500336de6f6a5f7a52d73cbf543fb6 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Jun 2013 10:50:55 -0700 Subject: [PATCH 10/30] Updated build usage --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 853f1cdebc..ffe9b29b1b 100644 --- a/commands.go +++ b/commands.go @@ -131,7 +131,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error { } func (cli *DockerCli) CmdBuild(args ...string) error { - cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH") + 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") if err := cmd.Parse(args); err != nil { return nil From d0084ce5f23453fbc008f5a2c5dd147b0df890e7 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Jun 2013 14:57:50 -0700 Subject: [PATCH 11/30] Remove run from the ADD instruction --- buildfile.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/buildfile.go b/buildfile.go index b38101c72b..3721958a93 100644 --- a/buildfile.go +++ b/buildfile.go @@ -220,12 +220,15 @@ func (b *buildFile) CmdAdd(args string) error { cmd := b.config.Cmd b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} - cid, err := b.run() + + // Create the container and start it + c, err := b.builder.Create(b.config) if err != nil { return err } + b.tmpContainers[c.ID] = struct{}{} - container := b.runtime.Get(cid) + container := b.runtime.Get(c.ID) if container == nil { return fmt.Errorf("Error while creating the container (CmdAdd)") } @@ -244,7 +247,7 @@ func (b *buildFile) CmdAdd(args string) error { } } - if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { + if err := b.commit(c.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { return err } b.config.Cmd = cmd From f5fe3ce34e12f1660ee98cecdbe2c104567d88a6 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Jun 2013 15:08:38 -0700 Subject: [PATCH 12/30] Remove run from non-running commmands --- buildfile.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/buildfile.go b/buildfile.go index 3721958a93..f4e503842f 100644 --- a/buildfile.go +++ b/buildfile.go @@ -222,16 +222,12 @@ func (b *buildFile) CmdAdd(args string) error { b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} // Create the container and start it - c, err := b.builder.Create(b.config) + container, err := b.builder.Create(b.config) if err != nil { return err } - b.tmpContainers[c.ID] = struct{}{} + b.tmpContainers[container.ID] = struct{}{} - container := b.runtime.Get(c.ID) - if container == nil { - return fmt.Errorf("Error while creating the container (CmdAdd)") - } if err := container.EnsureMounted(); err != nil { return err } @@ -247,7 +243,7 @@ func (b *buildFile) CmdAdd(args string) error { } } - if err := b.commit(c.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { + if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { return err } b.config.Cmd = cmd @@ -266,6 +262,7 @@ func (b *buildFile) run() (string, error) { return "", err } b.tmpContainers[c.ID] = struct{}{} + fmt.Fprintf(b.out, "## %s\n", c.ID) //start the container if err := c.Start(); err != nil { @@ -301,11 +298,16 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { utils.Debugf("[BUILDER] Cache miss") } - cid, err := b.run() + container, err := b.builder.Create(b.config) if err != nil { return err } - id = cid + b.tmpContainers[container.ID] = struct{}{} + id = container.ID + if err := container.EnsureMounted(); err != nil { + return err + } + defer container.Unmount() } container := b.runtime.Get(id) @@ -374,7 +376,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { fmt.Fprintf(b.out, "===> %v\n", b.image) } if b.image != "" { - fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image) + fmt.Fprintf(b.out, "Build successful.\n%s\n", b.image) return b.image, nil } return "", fmt.Errorf("An error occured during the build\n") From f03ebc20aa33dbb9b468fe476ce26467eb328060 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Jun 2013 18:42:27 -0700 Subject: [PATCH 13/30] Fix issue with ADD --- buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 36fbf7c4da..2b29a46b1f 100644 --- a/buildfile.go +++ b/buildfile.go @@ -221,6 +221,7 @@ func (b *buildFile) CmdAdd(args string) error { cmd := b.config.Cmd b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} + b.config.Image = b.image // Create the container and start it container, err := b.builder.Create(b.config) if err != nil { @@ -297,7 +298,6 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { } else { utils.Debugf("[BUILDER] Cache miss") } - container, err := b.builder.Create(b.config) if err != nil { return err From 2f14dae83f23f62085015243a74df4ffc415c6c4 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Jun 2013 18:52:41 -0700 Subject: [PATCH 14/30] Add build UT --- buildfile_test.go | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index d9c60a70d5..ef5663c0be 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -11,8 +11,13 @@ const Dockerfile = ` # DOCKER-VERSION 0.2 from ` + unitTestImageName + ` +maintainer docker +expose 22 +env FOO BAR +cmd ["echo", "hello", "world"] run sh -c 'echo root:testpass > /tmp/passwd' run mkdir -p /var/run/sshd +add . /src ` const DockerfileNoNewLine = ` @@ -20,10 +25,15 @@ const DockerfileNoNewLine = ` # DOCKER-VERSION 0.2 from ` + unitTestImageName + ` +maintainer docker +expose 22 +env FOO BAR +cmd ["echo", "hello", "world"] run sh -c 'echo root:testpass > /tmp/passwd' -run mkdir -p /var/run/sshd` +run mkdir -p /var/run/sshd +add . /src` -func TestBuild(t *testing.T) { +func TestBuildFile(t *testing.T) { dockerfiles := []string{Dockerfile, DockerfileNoNewLine} for _, Dockerfile := range dockerfiles { runtime, err := newTestRuntime() @@ -36,7 +46,12 @@ func TestBuild(t *testing.T) { buildfile := NewBuildFile(srv, &utils.NopWriter{}) - imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil) + context, err := fakeTar() + if err != nil { + t.Fatal(err) + } + + imgID, err := buildfile.Build(strings.NewReader(Dockerfile), context) if err != nil { t.Fatal(err) } @@ -79,5 +94,25 @@ func TestBuild(t *testing.T) { if string(output) != "/var/run/sshd\n" { t.Fatal("/var/run/sshd has not been created") } + + container3, err := builder.Create( + &Config{ + Image: imgID, + Cmd: []string{"ls", "/src"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container3) + + output, err = container3.Output() + if err != nil { + t.Fatal(err) + } + if string(output) != "etc\nvar\n" { + t.Fatalf("Unexpected output. Expected: '%s', received: '%s'", "etc\nvar\n", string(output)) + } + } } From cc7de8df755d9e4a845a8aa5800424b1546492d6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 15 Jun 2013 09:30:52 -0700 Subject: [PATCH 15/30] Removed deprecated file builder_client.go --- builder_client.go | 314 ---------------------------------------------- 1 file changed, 314 deletions(-) delete mode 100644 builder_client.go diff --git a/builder_client.go b/builder_client.go deleted file mode 100644 index dc9528ff41..0000000000 --- a/builder_client.go +++ /dev/null @@ -1,314 +0,0 @@ -package docker - -import ( - "bufio" - "encoding/json" - "fmt" - "github.com/dotcloud/docker/utils" - "io" - "net/url" - "os" - "reflect" - "strings" -) - -type builderClient struct { - cli *DockerCli - - image string - maintainer string - config *Config - - tmpContainers map[string]struct{} - tmpImages map[string]struct{} - - needCommit bool -} - -func (b *builderClient) clearTmp(containers, images map[string]struct{}) { - for i := range images { - if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil { - utils.Debugf("%s", err) - } - utils.Debugf("Removing image %s", i) - } -} - -func (b *builderClient) CmdFrom(name string) error { - obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil) - if statusCode == 404 { - - remote := name - var tag string - if strings.Contains(remote, ":") { - remoteParts := strings.Split(remote, ":") - tag = remoteParts[1] - remote = remoteParts[0] - } - var out io.Writer - if os.Getenv("DEBUG") != "" { - out = os.Stdout - } else { - out = &utils.NopWriter{} - } - if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil { - return err - } - obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil) - if err != nil { - return err - } - } - if err != nil { - return err - } - - img := &APIID{} - if err := json.Unmarshal(obj, img); err != nil { - return err - } - b.image = img.ID - utils.Debugf("Using image %s", b.image) - return nil -} - -func (b *builderClient) CmdMaintainer(name string) error { - b.needCommit = true - b.maintainer = name - return nil -} - -func (b *builderClient) 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) - - body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config}) - if err != nil { - if statusCode != 404 { - return err - } - } - if statusCode != 404 { - apiID := &APIID{} - if err := json.Unmarshal(body, apiID); err != nil { - return err - } - utils.Debugf("Use cached version") - b.image = apiID.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 *builderClient) 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 *builderClient) 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 *builderClient) CmdExpose(args string) error { - ports := strings.Split(args, " ") - b.config.PortSpecs = append(ports, b.config.PortSpecs...) - return nil -} - -func (b *builderClient) CmdInsert(args string) error { - // 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") -} - -func (b *builderClient) run() (string, error) { - if b.image == "" { - return "", fmt.Errorf("Please provide a source image with `from` prior to run") - } - b.config.Image = b.image - body, _, err := b.cli.call("POST", "/containers/create", b.config) - if err != nil { - return "", err - } - - apiRun := &APIRun{} - if err := json.Unmarshal(body, apiRun); err != nil { - return "", err - } - for _, warning := range apiRun.Warnings { - fmt.Fprintln(os.Stderr, "WARNING: ", warning) - } - - //start the container - _, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil) - if err != nil { - return "", err - } - b.tmpContainers[apiRun.ID] = struct{}{} - - // Wait for it to finish - body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil) - if err != nil { - return "", err - } - apiWait := &APIWait{} - if err := json.Unmarshal(body, apiWait); err != nil { - return "", err - } - if apiWait.StatusCode != 0 { - return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode) - } - - return apiRun.ID, nil -} - -func (b *builderClient) commit(id string) error { - if b.image == "" { - return fmt.Errorf("Please provide a source image with `from` prior to run") - } - b.config.Image = b.image - - if id == "" { - cmd := b.config.Cmd - b.config.Cmd = []string{"true"} - cid, err := b.run() - if err != nil { - return err - } - id = cid - b.config.Cmd = cmd - } - - // Commit the container - v := url.Values{} - v.Set("container", id) - v.Set("author", b.maintainer) - - body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config) - if err != nil { - return err - } - apiID := &APIID{} - if err := json.Unmarshal(body, apiID); err != nil { - return err - } - b.tmpImages[apiID.ID] = struct{}{} - b.image = apiID.ID - b.needCommit = false - return nil -} - -func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) { - 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(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.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.Fprintf(os.Stderr, "===> %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) - } - for i := range b.tmpContainers { - delete(b.tmpContainers, i) - } - 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) BuildFile { - return &builderClient{ - cli: NewDockerCli(addr, port), - config: &Config{}, - tmpContainers: make(map[string]struct{}), - tmpImages: make(map[string]struct{}), - } -} From 38554fc2a722d1a77514f477a189b0dbd149691b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 15 Jun 2013 09:38:18 -0700 Subject: [PATCH 16/30] * Builder: simplify the upload of the build context. Simply stream a tarball instead of multipart upload with 4 intermediary buffers. Simpler, less memory usage, less disk usage, and faster. --- api.go | 21 ++--------- buildfile.go | 29 +++++++++------- buildfile_test.go | 13 +++++-- commands.go | 88 +++++++++++++++++++---------------------------- 4 files changed, 66 insertions(+), 85 deletions(-) diff --git a/api.go b/api.go index e870ca7723..563888c6b1 100644 --- a/api.go +++ b/api.go @@ -13,7 +13,7 @@ import ( "strings" ) -const APIVERSION = 1.2 +const APIVERSION = 1.3 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -715,9 +715,7 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r * } 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 - } + // FIXME: "remote" is not a clear variable name. remote := r.FormValue("t") tag := "" if strings.Contains(remote, ":") { @@ -725,21 +723,8 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ tag = remoteParts[1] remote = remoteParts[0] } - - dockerfile, _, err := r.FormFile("Dockerfile") - if err != nil { - return err - } - - context, _, err := r.FormFile("Context") - if err != nil { - if err != http.ErrMissingFile { - return err - } - } - b := NewBuildFile(srv, utils.NewWriteFlusher(w)) - if id, err := b.Build(dockerfile, context); err != nil { + if id, err := b.Build(r.Body); err != nil { fmt.Fprintf(w, "Error build: %s\n", err) } else if remote != "" { srv.runtime.repositories.Set(remote, tag, id, false) diff --git a/buildfile.go b/buildfile.go index aaa6f6bc7c..a4e6c7f725 100644 --- a/buildfile.go +++ b/buildfile.go @@ -14,7 +14,7 @@ import ( ) type BuildFile interface { - Build(io.Reader, io.Reader) (string, error) + Build(io.Reader) (string, error) CmdFrom(string) error CmdRun(string) error } @@ -305,18 +305,23 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { return nil } -func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { - 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 +func (b *buildFile) Build(context io.Reader) (string, error) { + // FIXME: @creack any reason for using /tmp instead of ""? + // FIXME: @creack "name" is a terrible variable name + 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 + dockerfile, err := os.Open(path.Join(name, "Dockerfile")) + if err != nil { + return "", fmt.Errorf("Can't build a directory with no Dockerfile") + } + // FIXME: "file" is also a terrible variable name ;) file := bufio.NewReader(dockerfile) for { line, err := file.ReadString('\n') diff --git a/buildfile_test.go b/buildfile_test.go index d9c60a70d5..0432fda3e6 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -2,7 +2,6 @@ package docker import ( "github.com/dotcloud/docker/utils" - "strings" "testing" ) @@ -23,6 +22,16 @@ from ` + unitTestImageName + ` run sh -c 'echo root:testpass > /tmp/passwd' run mkdir -p /var/run/sshd` +// mkTestContext generates a build context from the contents of the provided dockerfile. +// This context is suitable for use as an argument to BuildFile.Build() +func mkTestContext(dockerfile string, t *testing.T) Archive { + context, err := mkBuildContext(dockerfile) + if err != nil { + t.Fatal(err) + } + return context +} + func TestBuild(t *testing.T) { dockerfiles := []string{Dockerfile, DockerfileNoNewLine} for _, Dockerfile := range dockerfiles { @@ -36,7 +45,7 @@ func TestBuild(t *testing.T) { buildfile := NewBuildFile(srv, &utils.NopWriter{}) - imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil) + imgID, err := buildfile.Build(mkTestContext(Dockerfile, t)) if err != nil { t.Fatal(err) } diff --git a/commands.go b/commands.go index 04eb994f01..4441536a95 100644 --- a/commands.go +++ b/commands.go @@ -1,6 +1,7 @@ package docker import ( + "archive/tar" "bytes" "encoding/json" "flag" @@ -10,14 +11,12 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "mime/multipart" "net" "net/http" "net/http/httputil" "net/url" "os" "os/signal" - "path" "path/filepath" "reflect" "regexp" @@ -131,6 +130,27 @@ func (cli *DockerCli) CmdInsert(args ...string) error { return nil } +// mkBuildContext returns an archive of an empty context with the contents +// of `dockerfile` at the path ./Dockerfile +func mkBuildContext(content string) (Archive, error) { + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + hdr := &tar.Header{ + Name: "Dockerfile", + Size: int64(len(content)), + } + if err := tw.WriteHeader(hdr); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(content)); err != nil { + return nil, err + } + if err := tw.Close(); err != nil { + return nil, err + } + return buf, nil +} + func (cli *DockerCli) CmdBuild(args ...string) error { 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") @@ -143,70 +163,32 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } var ( - multipartBody io.Reader - file io.ReadCloser - contextPath string + context Archive + 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") - - compression := Bzip2 - if cmd.Arg(0) == "-" { - file = os.Stdin + // As a special case, 'docker build -' will build from an empty context with the + // contents of stdin as a Dockerfile + dockerfile, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return err + } + context, err = mkBuildContext(string(dockerfile)) } else { - // Send Dockerfile from arg/Dockerfile (deprecate later) - f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")) - if err != nil { - return err - } - 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 { - return err - } - // NOTE: Do this in case '.' or '..' is input - absPath, err := filepath.Abs(cmd.Arg(0)) - if err != nil { - return err - } - wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension()) - if err != nil { - return err - } - // FIXME: Find a way to have a progressbar for the upload too - sf := utils.NewStreamFormatter(false) - io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf)) - multipartBody = io.MultiReader(multipartBody, boundary) + context, err = Tar(cmd.Arg(0), Uncompressed) } - // Create a FormFile multipart for the Dockerfile - wField, err := w.CreateFormFile("Dockerfile", "Dockerfile") if err != nil { return err } - io.Copy(wField, file) - multipartBody = io.MultiReader(multipartBody, boundary) - + // Upload the build context 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?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody) + req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), context) if err != nil { return err } - req.Header.Set("Content-Type", w.FormDataContentType()) - if contextPath != "" { - req.Header.Set("X-Docker-Context-Compression", compression.Flag()) - fmt.Println("Uploading Context...") - } - + req.Header.Set("Content-Type", "application/tar") resp, err := http.DefaultClient.Do(req) if err != nil { return err From 061f8d12e04e494fdcbee949facaf598d201c2ec Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 15 Jun 2013 11:07:49 -0700 Subject: [PATCH 17/30] * Builder: reorganized unit tests for better code reuse, and to test non-empty contexts --- buildfile_test.go | 136 ++++++++++++++++++++++++++-------------------- commands.go | 26 +++++---- 2 files changed, 92 insertions(+), 70 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 0432fda3e6..383463ce24 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -1,40 +1,75 @@ package docker import ( - "github.com/dotcloud/docker/utils" + "io/ioutil" "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 -` - -const DockerfileNoNewLine = ` -# VERSION 0.1 -# DOCKER-VERSION 0.2 - -from ` + unitTestImageName + ` -run sh -c 'echo root:testpass > /tmp/passwd' -run mkdir -p /var/run/sshd` - // mkTestContext generates a build context from the contents of the provided dockerfile. // This context is suitable for use as an argument to BuildFile.Build() -func mkTestContext(dockerfile string, t *testing.T) Archive { - context, err := mkBuildContext(dockerfile) +func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive { + context, err := mkBuildContext(dockerfile, files) if err != nil { t.Fatal(err) } return context } +// A testContextTemplate describes a build context and how to test it +type testContextTemplate struct { + // Contents of the Dockerfile + dockerfile string + // Additional files in the context, eg [][2]string{"./passwd", "gordon"} + files [][2]string + // Test commands to run in the resulting image + tests []testCommand +} + +// A testCommand describes a command to run in a container, and the exact output required to pass the test +type testCommand struct { + // The command to run, eg. []string{"echo", "hello", "world"} + cmd []string + // The exact output expected, eg. "hello world\n" + output string +} + +// A table of all the contexts to build and test. +// A new docker runtime will be created and torn down for each context. +var testContexts []testContextTemplate = []testContextTemplate{ + { + ` +# VERSION 0.1 +# DOCKER-VERSION 0.2 + +from docker-ut +run sh -c 'echo root:testpass > /tmp/passwd' +run mkdir -p /var/run/sshd +`, + nil, + []testCommand{ + {[]string{"cat", "/tmp/passwd"}, "root:testpass\n"}, + {[]string{"ls", "-d", "/var/run/sshd"}, "/var/run/sshd\n"}, + }, + }, + + { + ` +# VERSION 0.1 +# DOCKER-VERSION 0.2 + +from docker-ut +run sh -c 'echo root:testpass > /tmp/passwd' +run mkdir -p /var/run/sshd`, + nil, + []testCommand{ + {[]string{"cat", "/tmp/passwd"}, "root:testpass\n"}, + {[]string{"ls", "-d", "/var/run/sshd"}, "/var/run/sshd\n"}, + }, + }, +} + func TestBuild(t *testing.T) { - dockerfiles := []string{Dockerfile, DockerfileNoNewLine} - for _, Dockerfile := range dockerfiles { + for _, ctx := range testContexts { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -43,50 +78,33 @@ func TestBuild(t *testing.T) { srv := &Server{runtime: runtime} - buildfile := NewBuildFile(srv, &utils.NopWriter{}) + buildfile := NewBuildFile(srv, ioutil.Discard) - imgID, err := buildfile.Build(mkTestContext(Dockerfile, t)) + imgID, err := buildfile.Build(mkTestContext(ctx.dockerfile, ctx.files, t)) 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) + for _, testCmd := range ctx.tests { + container, err := builder.Create( + &Config{ + Image: imgID, + Cmd: testCmd.cmd, + }, + ) + 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") + output, err := container.Output() + if err != nil { + t.Fatal(err) + } + if string(output) != testCmd.output { + t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, testCmd.output) + } } } } diff --git a/commands.go b/commands.go index 4441536a95..274ea6fae5 100644 --- a/commands.go +++ b/commands.go @@ -132,18 +132,22 @@ func (cli *DockerCli) CmdInsert(args ...string) error { // mkBuildContext returns an archive of an empty context with the contents // of `dockerfile` at the path ./Dockerfile -func mkBuildContext(content string) (Archive, error) { +func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) { buf := new(bytes.Buffer) tw := tar.NewWriter(buf) - hdr := &tar.Header{ - Name: "Dockerfile", - Size: int64(len(content)), - } - if err := tw.WriteHeader(hdr); err != nil { - return nil, err - } - if _, err := tw.Write([]byte(content)); err != nil { - return nil, err + files = append(files, [2]string{"Dockerfile", dockerfile}) + for _, file := range files { + name, content := file[0], file[1] + hdr := &tar.Header{ + Name: name, + Size: int64(len(content)), + } + if err := tw.WriteHeader(hdr); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(content)); err != nil { + return nil, err + } } if err := tw.Close(); err != nil { return nil, err @@ -174,7 +178,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if err != nil { return err } - context, err = mkBuildContext(string(dockerfile)) + context, err = mkBuildContext(string(dockerfile), nil) } else { context, err = Tar(cmd.Arg(0), Uncompressed) } From f50e40008f39c77cab2b516ac2102f875bdef215 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 15 Jun 2013 11:35:56 -0700 Subject: [PATCH 18/30] * Builder: added a regression test for #895 --- buildfile_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/buildfile_test.go b/buildfile_test.go index 383463ce24..3b03b8dd04 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -66,6 +66,16 @@ run mkdir -p /var/run/sshd`, {[]string{"ls", "-d", "/var/run/sshd"}, "/var/run/sshd\n"}, }, }, + + { + ` +from docker-ut +add foo /usr/lib/bla/bar`, + [][2]string{{"foo", "hello world!"}}, + []testCommand{ + {[]string{"cat", "/usr/lib/bla/bar"}, "hello world!"}, + }, + }, } func TestBuild(t *testing.T) { From 0809f649d356b6b8d34a03bee8447049161ea09c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 17 Jun 2013 18:26:41 -0700 Subject: [PATCH 19/30] * Builder: upload progress bar Fix progress bar --- FIXME | 1 + commands.go | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/FIXME b/FIXME index b88757e229..c0bbd7e482 100644 --- a/FIXME +++ b/FIXME @@ -34,3 +34,4 @@ to put them - so we put them here :) * Caching after an ADD * entry point config * bring back git revision info, looks like it was lost +* Clean up the ProgressReader api, it's a PITA to use diff --git a/commands.go b/commands.go index 274ea6fae5..39cf749ef8 100644 --- a/commands.go +++ b/commands.go @@ -185,10 +185,14 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if err != nil { return err } + // Setup an upload progress bar + // FIXME: ProgressReader shouldn't be this annoyning to use + sf := utils.NewStreamFormatter(false) + body := utils.ProgressReader(ioutil.NopCloser(context), 0, os.Stderr, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf) // Upload the build context v := &url.Values{} v.Set("t", *tag) - req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), context) + req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), body) if err != nil { return err } From 6e17cc45eabf6d094241fc3e017a90da8ba6f5d9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 18 Jun 2013 12:33:06 -0700 Subject: [PATCH 20/30] Fix merge issue --- docs/sources/api/docker_remote_api.rst | 281 +------------------------ 1 file changed, 2 insertions(+), 279 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 15d709d0c8..824bfa4657 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -66,285 +66,8 @@ Uses json stream instead of HTML hijack, it looks like this: docker v0.3.4 8d73740_ - HTTP/1.1 200 OK - Content-Type: application/vnd.docker.raw-stream - - {{ STREAM }} - - :query registry: the registry you wan to push, optional - :statuscode 200: no error - :statuscode 404: no such image - :statuscode 500: server error - - -Tag an image into a repository -****************************** - -.. http:post:: /images/(name)/tag - - Tag the image ``name`` into a repository - - **Example request**: - - .. sourcecode:: http - - POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - - :query repo: The repository to tag in - :query force: 1/True/true or 0/False/false, default false - :statuscode 200: no error - :statuscode 400: bad parameter - :statuscode 404: no such image - :statuscode 500: server error - - -Remove an image -*************** - -.. http:delete:: /images/(name) - - Remove the image ``name`` from the filesystem - - **Example request**: - - .. sourcecode:: http - - DELETE /images/test HTTP/1.1 - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 204 OK - - :statuscode 204: no error - :statuscode 404: no such image - :statuscode 500: server error - - -Search images -************* - -.. http:get:: /images/search - - Search for an image in the docker index - - **Example request**: - - .. sourcecode:: http - - GET /images/search?term=sshd HTTP/1.1 - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "Name":"cespare/sshd", - "Description":"" - }, - { - "Name":"johnfuller/sshd", - "Description":"" - }, - { - "Name":"dhrp/mongodb-sshd", - "Description":"" - } - ] - - :query term: term to search - :statuscode 200: no error - :statuscode 500: server error - - -3.3 Misc --------- - -Build an image from Dockerfile via stdin -**************************************** - -.. http:post:: /build - - Build an image from Dockerfile via stdin - - **Example request**: - - .. sourcecode:: http - - POST /build HTTP/1.1 - - {{ STREAM }} - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - - {{ STREAM }} - - :query t: tag to be applied to the resulting image in case of success - :query remote: URL to be fetch. Either a single Dockerfile or a Git repository - :statuscode 200: no error - :statuscode 500: server error - - -Get default username and email -****************************** - -.. http:get:: /auth - - Get the default username and email - - **Example request**: - - .. sourcecode:: http - - GET /auth HTTP/1.1 - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "username":"hannibal", - "email":"hannibal@a-team.com" - } - - :statuscode 200: no error - :statuscode 500: server error - - -Set auth configuration -********************** - -.. http:post:: /auth - - Get the default username and email - - **Example request**: - - .. sourcecode:: http - - POST /auth HTTP/1.1 - Content-Type: application/json - - { - "username":"hannibal", - "password:"xxxx", - "email":"hannibal@a-team.com" - } - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - - :statuscode 200: no error - :statuscode 204: no error - :statuscode 500: server error - - -Display system-wide information -******************************* - -.. http:get:: /info - - Display system-wide information - - **Example request**: - - .. sourcecode:: http - - GET /info HTTP/1.1 - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "Containers":11, - "Images":16, - "Debug":false, - "NFd": 11, - "NGoroutines":21, - "MemoryLimit":true, - "SwapLimit":false - } - - :statuscode 200: no error - :statuscode 500: server error - - -Show the docker version information -*********************************** - -.. http:get:: /version - - Show the docker version information - - **Example request**: - - .. sourcecode:: http - - GET /version HTTP/1.1 - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "Version":"0.2.2", - "GitCommit":"5a2a5cc+CHANGES", - "GoVersion":"go1.0.3" - } - - :statuscode 200: no error - :statuscode 500: server error - - -Create a new image from a container's changes -********************************************* - -.. http:post:: /commit - - Create a new image from a container's changes - - **Example request**: - - .. sourcecode:: http - - POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 201 OK - Content-Type: application/vnd.docker.raw-stream - - {"Id":"596069db4bf5"} +What's new +---------- Initial version From 84ceeaa8708293de81765a0f854952a74d191024 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 18 Jun 2013 14:36:35 -0700 Subject: [PATCH 21/30] Update documentaiton --- docs/sources/api/docker_remote_api_v1.2.rst | 5 ++++- docs/sources/commandline/command/build.rst | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index fb69168120..c366f09f30 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -846,7 +846,7 @@ Build an image from Dockerfile via stdin .. http:post:: /build - Build an image from Dockerfile via stdin + Build an image from Dockerfile **Example request**: @@ -865,9 +865,12 @@ Build an image from Dockerfile via stdin {{ STREAM }} :query t: tag 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 +{{ STREAM }} is the raw text output of the build command. It uses the HTTP Hijack method in order to stream. + Check auth configuration ************************ diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index 4472d98367..1645002ba2 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -29,7 +29,15 @@ Examples .. code-block:: bash - docker build - + docker build - < Dockerfile | This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon. | ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container. + + +.. code-block:: bash + + docker build github.com/creack/docker-firefox + +| This will clone the github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile. +| Note that you can specify an arbitrary git repository by using the 'git://' schema. From 90dde9beabcac3874a274f9f7952f25ba8c1c7a3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 19 Jun 2013 14:59:28 -0700 Subject: [PATCH 22/30] *Builder: warn pre-1.3 clients that they need to upgrade. This breaks semver, but our API should still be in 0.X versioning, in which case semver allows breaking changes. --- api.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api.go b/api.go index 563888c6b1..1aa46e6b3d 100644 --- a/api.go +++ b/api.go @@ -715,6 +715,9 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r * } func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if version < 1.3 { + return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") + } // FIXME: "remote" is not a clear variable name. remote := r.FormValue("t") tag := "" From 55edbcd02fe5d519069677c3970ea90e9973a4eb Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 19 Jun 2013 14:59:42 -0700 Subject: [PATCH 23/30] * Builder: remove duplicate unit test --- buildfile_test.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 3b03b8dd04..2a5cc6682e 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -52,21 +52,6 @@ run mkdir -p /var/run/sshd }, }, - { - ` -# VERSION 0.1 -# DOCKER-VERSION 0.2 - -from docker-ut -run sh -c 'echo root:testpass > /tmp/passwd' -run mkdir -p /var/run/sshd`, - nil, - []testCommand{ - {[]string{"cat", "/tmp/passwd"}, "root:testpass\n"}, - {[]string{"ls", "-d", "/var/run/sshd"}, "/var/run/sshd\n"}, - }, - }, - { ` from docker-ut From d6ab71f450ac3673ffd4bc806de23e42a6d32c07 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 19 Jun 2013 15:03:33 -0700 Subject: [PATCH 24/30] * Remote API: updated docs for 1.3 --- docs/sources/api/docker_remote_api_v1.3.rst | 1038 +++++++++++++++++++ 1 file changed, 1038 insertions(+) create mode 100644 docs/sources/api/docker_remote_api_v1.3.rst diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst new file mode 100644 index 0000000000..c6b5856c42 --- /dev/null +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -0,0 +1,1038 @@ +:title: Remote API v1.2 +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +====================== +Docker Remote API v1.2 +====================== + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API is replacing rcli +- Default port in the docker deamon 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 +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers/json + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers/json?all=1&before=8dfafdbc3a40 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":"", + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":"", + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":"", + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":"", + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers/create + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{}, + "VolumesFrom":"" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id)/json + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "" + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {} + } + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Inspect changes on a container's filesystem +******************************************* + +.. http:get:: /containers/(id)/changes + + Inspect changes on container ``id`` 's filesystem + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Export a container +****************** + +.. http:get:: /containers/(id)/export + + Export the contents of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + + **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 + + +Start a container +***************** + +.. http:post:: /containers/(id)/start + + Start the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/start HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Stop a contaier +*************** + +.. http:post:: /containers/(id)/stop + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Restart a container +******************* + +.. http:post:: /containers/(id)/restart + + Restart the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Kill a container +**************** + +.. http:post:: /containers/(id)/kill + + Kill the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Attach to the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Wait a container +**************** + +.. http:post:: /containers/(id)/wait + + Block until container ``id`` stops, then returns the exit code + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /containers/(id) + + Remove the container ``id`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :statuscode 204: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images/(format) + + List images ``format`` could be json or viz (json default) + + **Example request**: + + .. sourcecode:: http + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Repository":"base", + "Tag":"ubuntu-12.10", + "Id":"b750fe79269d", + "Created":1364102658, + "Size":24653, + "VirtualSize":180116135 + }, + { + "Repository":"base", + "Tag":"ubuntu-quantal", + "Id":"b750fe79269d", + "Created":1364102658, + "Size":24653, + "VirtualSize":180116135 + } + ] + + + **Example request**: + + .. sourcecode:: http + + GET /images/viz HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: text/plain + + digraph docker { + "d82cbacda43a" -> "074be284591f" + "1496068ca813" -> "08306dc45919" + "08306dc45919" -> "0e7893146ac2" + "b750fe79269d" -> "1496068ca813" + base -> "27cf78414709" [style=invis] + "f71189fff3de" -> "9a33b36209ed" + "27cf78414709" -> "b750fe79269d" + "0e7893146ac2" -> "d6434d954665" + "d6434d954665" -> "d82cbacda43a" + base -> "e9aa60c60128" [style=invis] + "074be284591f" -> "f71189fff3de" + "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + base [style=invisible] + } + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create an image +*************** + +.. http:post:: /images/create + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :query registry: the registry to pull from + :statuscode 200: no error + :statuscode 500: server error + + +Insert a file in a image +************************ + +.. http:post:: /images/(name)/insert + + Insert a file from ``url`` in the image ``name`` at ``path`` + + **Example request**: + + .. sourcecode:: http + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + :statuscode 200: no error + :statuscode 500: server error + + +Inspect an image +**************** + +.. http:get:: /images/(name)/json + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"" + }, + "Size": 6824592 + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name)/history + + Return the history of the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/history HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Push an image on the registry +***************************** + +.. http:post:: /images/(name)/push + + Push the image ``name`` on the registry + + **Example request**: + + .. sourcecode:: http + + POST /images/test/push HTTP/1.1 + {{ authConfig }} + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + :query registry: the registry you wan to push, optional + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Tag an image into a repository +****************************** + +.. http:post:: /images/(name)/tag + + Tag the image ``name`` into a repository + + **Example request**: + + .. sourcecode:: http + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Remove an image +*************** + +.. http:delete:: /images/(name) + + Remove the image ``name`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /images/test HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :statuscode 204: no error + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Name":"cespare/sshd", + "Description":"" + }, + { + "Name":"johnfuller/sshd", + "Description":"" + }, + { + "Name":"dhrp/mongodb-sshd", + "Description":"" + } + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error + + +2.3 Misc +-------- + +Build an image from Dockerfile via stdin +**************************************** + +.. http:post:: /build + + Build an image from Dockerfile via stdin + + **Example request**: + + .. sourcecode:: http + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + .. 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 Content-type header should be set to "application/tar". + + :query t: tag to be applied to the resulting image in case of success + :statuscode 200: no error + :statuscode 500: server error + + +Check auth configuration +************************ + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 204: no error + :statuscode 500: server error + + +Display system-wide information +******************************* + +.. http:get:: /info + + Display system-wide information + + **Example request**: + + .. sourcecode:: http + + GET /info HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false + } + + :statuscode 200: no error + :statuscode 500: server error + + +Show the docker version information +*********************************** + +.. http:get:: /version + + Show the docker version information + + **Example request**: + + .. sourcecode:: http + + GET /version HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } + + :statuscode 200: no error + :statuscode 500: server error + + +Create a new image from a container's changes +********************************************* + +.. http:post:: /commit + + Create a new image from a container's changes + + **Example request**: + + .. sourcecode:: http + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 500: server error + + +3. Going further +================ + +3.1 Inside 'docker run' +----------------------- + +Here are the steps of 'docker run' : + +* Create the container +* If the status code is 404, it means the image doesn't exists: + * Try to pull it + * Then retry to create the container +* Start the container +* If you are not in detached mode: + * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 +* If in detached mode or only stdin is attached: + * Display the container's id + + +3.2 Hijacking +------------- + +In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. + +3.3 CORS Requests +----------------- + +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 + From cff2187a4ccf90a5be821c316b564e934d80998c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 12:30:02 -0700 Subject: [PATCH 25/30] Fixed API version numbers in api docs --- docs/sources/api/docker_remote_api_v1.3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index c6b5856c42..2237b52398 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -1,9 +1,9 @@ -:title: Remote API v1.2 +:title: Remote API v1.3 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation ====================== -Docker Remote API v1.2 +Docker Remote API v1.3 ====================== .. contents:: Table of Contents From cc0f59742f192ccbc178c59bdc7eb98d0f6dd943 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 20:16:39 -0700 Subject: [PATCH 26/30] * Builder: simplified unit tests. The tests are now embedded in the build itself. Yeah baby. --- buildfile_test.go | 48 ++++++----------------------------------------- 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 33e6a3146b..773470b809 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -13,6 +13,8 @@ const Dockerfile = ` from ` + unitTestImageName + ` run sh -c 'echo root:testpass > /tmp/passwd' run mkdir -p /var/run/sshd +run [ "$(cat /tmp/passwd)" = "root:testpass" ] +run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] ` const DockerfileNoNewLine = ` @@ -21,7 +23,9 @@ const DockerfileNoNewLine = ` from ` + unitTestImageName + ` run sh -c 'echo root:testpass > /tmp/passwd' -run mkdir -p /var/run/sshd` +run mkdir -p /var/run/sshd +run [ "$(cat /tmp/passwd)" = "root:testpass" ] +run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]` // FIXME: test building with a context @@ -42,48 +46,8 @@ func TestBuild(t *testing.T) { buildfile := NewBuildFile(srv, &utils.NopWriter{}) - imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil) - if err != nil { + if _, err := buildfile.Build(strings.NewReader(Dockerfile), nil); 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") - } } } From 36d610a38806feb4cb5d09c4705a6f1fff42ef04 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 20:20:16 -0700 Subject: [PATCH 27/30] - Builder: fixed a regression in ADD. Improved regression tests for build behavior. --- archive.go | 98 +++++++++++++++++++++----------------- hack/test-build/Dockerfile | 17 +++++++ hack/test-build/README.md | 9 ++++ hack/test-build/d/ga | 1 + hack/test-build/f | 1 + 5 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 hack/test-build/Dockerfile create mode 100644 hack/test-build/README.md create mode 100644 hack/test-build/d/ga create mode 100644 hack/test-build/f diff --git a/archive.go b/archive.go index 16401e29fb..5756490bff 100644 --- a/archive.go +++ b/archive.go @@ -1,7 +1,9 @@ package docker import ( + "archive/tar" "bufio" + "bytes" "errors" "fmt" "github.com/dotcloud/docker/utils" @@ -10,6 +12,7 @@ import ( "os" "os/exec" "path" + "path/filepath" ) type Archive io.Reader @@ -160,51 +163,60 @@ func CopyWithTar(src, dst string) error { if err != nil { return err } - var dstExists bool - dstSt, err := os.Stat(dst) - if err != nil { - if !os.IsNotExist(err) { - return err - } - } else { - dstExists = true - } - // Things that can go wrong if the source is a directory - if srcSt.IsDir() { - // The destination exists and is a regular file - if dstExists && !dstSt.IsDir() { - return fmt.Errorf("Can't copy a directory over a regular file") - } - // Things that can go wrong if the source is a regular file - } else { - utils.Debugf("The destination exists, it's a directory, and doesn't end in /") - // The destination exists, it's a directory, and doesn't end in / - if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' { - return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1]) - } - } - // Create the destination - var dstDir string - if srcSt.IsDir() || dst[len(dst)-1] == '/' { - // The destination ends in /, or the source is a directory - // --> dst is the holding directory and needs to be created for -C - dstDir = dst - } else { - // The destination doesn't end in / - // --> dst is the file - dstDir = path.Dir(dst) - } - if !dstExists { - // Create the holding directory if necessary - utils.Debugf("Creating the holding directory %s", dstDir) - if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) { - return err - } - } if !srcSt.IsDir() { - return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir) + return CopyFileWithTar(src, dst) } - return TarUntar(src, nil, dstDir) + // 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) { + return err + } + utils.Debugf("Calling TarUntar(%s, %s)", src, dst) + return TarUntar(src, nil, dst) +} + +// CopyFileWithTar emulates the behavior of the 'cp' command-line +// for a single file. It copies a regular file from path `src` to +// path `dst`, and preserves all its metadata. +// +// If `dst` ends with a trailing slash '/', the final destination path +// will be `dst/base(src)`. +func CopyFileWithTar(src, dst string) error { + utils.Debugf("CopyFileWithTar(%s, %s)", src, dst) + srcSt, err := os.Stat(src) + if err != nil { + return err + } + if srcSt.IsDir() { + return fmt.Errorf("Can't copy a directory") + } + // Clean up the trailing / + if dst[len(dst)-1] == '/' { + dst = path.Join(dst, filepath.Base(src)) + } + // Create the holding directory if necessary + if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) { + return err + } + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + hdr, err := tar.FileInfoHeader(srcSt, "") + if err != nil { + return err + } + hdr.Name = filepath.Base(dst) + if err := tw.WriteHeader(hdr); err != nil { + return err + } + srcF, err := os.Open(src) + if err != nil { + return err + } + if _, err := io.Copy(tw, srcF); err != nil { + return err + } + tw.Close() + return Untar(buf, filepath.Dir(dst)) } // CmdStream executes a command, and returns its stdout as a stream. diff --git a/hack/test-build/Dockerfile b/hack/test-build/Dockerfile new file mode 100644 index 0000000000..a232253c9c --- /dev/null +++ b/hack/test-build/Dockerfile @@ -0,0 +1,17 @@ +from busybox +add f / +run [ "$(cat /f)" = "hello" ] +add f /abc +run [ "$(cat /abc)" = "hello" ] +add f /x/y/z +run [ "$(cat /x/y/z)" = "hello" ] +add f /x/y/d/ +run [ "$(cat /x/y/d/f)" = "hello" ] +add d / +run [ "$(cat /ga)" = "bu" ] +add d /somewhere +run [ "$(cat /somewhere/ga)" = "bu" ] +add d /anotherplace/ +run [ "$(cat /anotherplace/ga)" = "bu" ] +add d /somewheeeere/over/the/rainbooow +run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] diff --git a/hack/test-build/README.md b/hack/test-build/README.md new file mode 100644 index 0000000000..77636dcda6 --- /dev/null +++ b/hack/test-build/README.md @@ -0,0 +1,9 @@ +This directory is meant to test the behavior of 'docker build' in general, and its ADD command in particular. +Ideally it would be automatically called as part of the unit test suite - but that requires slightly more glue, +so for now we're just calling it manually, because it's better than nothing. + +To test, simply build this directory with the following command: + + docker build . + +The tests are embedded in the Dockerfile: if the build succeeds, the tests have passed. diff --git a/hack/test-build/d/ga b/hack/test-build/d/ga new file mode 100644 index 0000000000..7e6edb8302 --- /dev/null +++ b/hack/test-build/d/ga @@ -0,0 +1 @@ +bu diff --git a/hack/test-build/f b/hack/test-build/f new file mode 100644 index 0000000000..ce01362503 --- /dev/null +++ b/hack/test-build/f @@ -0,0 +1 @@ +hello From d9bce2defd56bbc47f47b263257b64016e48d607 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 22:15:19 -0700 Subject: [PATCH 28/30] - Builder: return an error when the build fails --- api.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index 72cbf3c56a..66b8362bc6 100644 --- a/api.go +++ b/api.go @@ -773,9 +773,12 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ context = c } b := NewBuildFile(srv, utils.NewWriteFlusher(w)) - if id, err := b.Build(context); err != nil { + id, err := b.Build(context) + if err != nil { fmt.Fprintf(w, "Error build: %s\n", err) - } else if repoName != "" { + return err + } + if repoName != "" { srv.runtime.repositories.Set(repoName, tag, id, false) } return nil From d0fa6927f8ad87e73a02f64ae9d6db6a820c1f4c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 21 Jun 2013 13:51:48 -0700 Subject: [PATCH 29/30] Update api docs --- docs/sources/api/docker_remote_api.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 824bfa4657..b8ce3343aa 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -19,10 +19,25 @@ Docker Remote API 2. Versions =========== -The current verson of the API is 1.2 -Calling /images//insert is the same as calling /v1.2/images//insert +The current verson of the API is 1.3 +Calling /images//insert is the same as calling /v1.3/images//insert You can still call an old version of the api using /v1.0/images//insert +:doc:`docker_remote_api_v1.3` +***************************** + +What's new +---------- + +Builder (/build): +- Simplify the upload of the build context +- Simply stream a tarball instead of multipart upload with 4 intermediary buffers +- Simpler, less memory usage, less disk usage and faster + +.. Note:: +The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build. + + :doc:`docker_remote_api_v1.2` ***************************** From 169ef21de768a45805f2afc56b3e5c6f21130b92 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 21 Jun 2013 14:12:52 -0700 Subject: [PATCH 30/30] Remove deprecated INSERT from documentation --- docs/sources/use/builder.rst | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 7892ec6caa..0978bd7d4c 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -121,19 +121,7 @@ functionally equivalent to prefixing the command with `=` .. note:: The environment variables will persist when a container is run from the resulting image. -2.7 INSERT ----------- - - ``INSERT `` - -The `INSERT` instruction will download the file from the given url to the given -path within the image. It is similar to `RUN curl -o `, assuming -curl was installed within the image. - -.. note:: - The path must include the file name. - -2.8 ADD +2.7 ADD ------- ``ADD `` @@ -182,7 +170,6 @@ files and directories are created with mode 0700, uid and gid 0. RUN apt-get update RUN apt-get install -y inotify-tools nginx apache2 openssh-server - INSERT https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper .. code-block:: bash