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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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/63] 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 9cc72ff1a9632736adc7dd12f096e3233896fced Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Jun 2013 09:53:48 +0000 Subject: [PATCH 15/63] fix auth in case you change your password on index.io --- commands.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/commands.go b/commands.go index 63c787c40a..918a781198 100644 --- a/commands.go +++ b/commands.go @@ -296,20 +296,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if username == "" { username = cli.authConfig.Username } - if username != cli.authConfig.Username { - fmt.Print("Password: ") - password = readString(os.Stdin, os.Stdout) + fmt.Print("Password: ") + password = readString(os.Stdin, os.Stdout) - if password == "" { - return fmt.Errorf("Error : Password Required") - } + if password == "" { + return fmt.Errorf("Error : Password Required") + } - fmt.Print("Email (", cli.authConfig.Email, "): ") - email = readAndEchoString(os.Stdin, os.Stdout) - if email == "" { - email = cli.authConfig.Email - } - } else { + fmt.Print("Email (", cli.authConfig.Email, "): ") + email = readAndEchoString(os.Stdin, os.Stdout) + if email == "" { email = cli.authConfig.Email } term.RestoreTerminal(oldState) From 90f6bdd6e41ec352009ffbd073c1f45983abb74b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Jun 2013 13:38:51 +0000 Subject: [PATCH 16/63] update docs, remove config file on 401 --- api.go | 4 +++ auth/auth.go | 5 ++-- commands.go | 32 ++++++++++++++------- docs/sources/api/docker_remote_api_v1.2.rst | 7 +++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index 5e1a6d7011..a69f6a3c2d 100644 --- a/api.go +++ b/api.go @@ -49,6 +49,10 @@ func httpError(w http.ResponseWriter, err error) { http.Error(w, err.Error(), http.StatusConflict) } else if strings.HasPrefix(err.Error(), "Impossible") { http.Error(w, err.Error(), http.StatusNotAcceptable) + } else if strings.HasPrefix(err.Error(), "Wrong login/password") { + http.Error(w, err.Error(), http.StatusUnauthorized) + } else if strings.Contains(err.Error(), "hasn't been activated") { + http.Error(w, err.Error(), http.StatusForbidden) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/auth/auth.go b/auth/auth.go index b7ad73b9ed..5f521ba3db 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -146,7 +146,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { if reqStatusCode == 201 { status = "Account created. Please use the confirmation link we sent" + - " to your e-mail to activate it.\n" + " to your e-mail to activate it." storeConfig = true } else if reqStatusCode == 403 { return "", fmt.Errorf("Login: Your account hasn't been activated. " + @@ -165,10 +165,11 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { return "", err } if resp.StatusCode == 200 { - status = "Login Succeeded\n" + status = "Login Succeeded" storeConfig = true } else if resp.StatusCode == 401 { if store { + authConfig.Email = "" if err := SaveConfig(authConfig); err != nil { return "", err } diff --git a/commands.go b/commands.go index 918a781198..d09eb962ce 100644 --- a/commands.go +++ b/commands.go @@ -296,16 +296,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if username == "" { username = cli.authConfig.Username } - fmt.Print("Password: ") - password = readString(os.Stdin, os.Stdout) + if username != cli.authConfig.Username { + fmt.Print("Password: ") + password = readString(os.Stdin, os.Stdout) - if password == "" { - return fmt.Errorf("Error : Password Required") - } + if password == "" { + return fmt.Errorf("Error : Password Required") + } - fmt.Print("Email (", cli.authConfig.Email, "): ") - email = readAndEchoString(os.Stdin, os.Stdout) - if email == "" { + fmt.Print("Email (", cli.authConfig.Email, "): ") + email = readAndEchoString(os.Stdin, os.Stdout) + if email == "" { + email = cli.authConfig.Email + } + } else { + password = cli.authConfig.Password email = cli.authConfig.Email } term.RestoreTerminal(oldState) @@ -314,7 +319,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { cli.authConfig.Password = password cli.authConfig.Email = email - body, _, err := cli.call("POST", "/auth", cli.authConfig) + body, statusCode, err := cli.call("POST", "/auth", cli.authConfig) + if statusCode == 401 { + cli.authConfig.Username = "" + cli.authConfig.Password = "" + cli.authConfig.Email = "" + auth.SaveConfig(cli.authConfig) + return err + } if err != nil { return err } @@ -327,7 +339,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } auth.SaveConfig(cli.authConfig) if out2.Status != "" { - fmt.Print(out2.Status) + fmt.Println(out2.Status) } return nil } diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index 8354760e2f..3231692102 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -877,9 +877,16 @@ Check auth configuration .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json + + { + "Status": "Login Succeeded" + } :statuscode 200: no error :statuscode 204: no error + :statuscode 401: unauthorized + :statuscode 403: forbidden :statuscode 500: server error From cc7de8df755d9e4a845a8aa5800424b1546492d6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 15 Jun 2013 09:30:52 -0700 Subject: [PATCH 17/63] 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 18/63] * 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 19/63] * 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 20/63] * 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 fde82f448f6b1508e03f419a3ce4c9b80311d466 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 17 Jun 2013 18:13:40 +0000 Subject: [PATCH 21/63] use go 1.1 cookiejar and revome ResetClient --- registry/registry.go | 15 +++++---------- server.go | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 131b02708e..21979fad8b 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -7,10 +7,10 @@ import ( "fmt" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" - "github.com/shin-/cookiejar" "io" "io/ioutil" "net/http" + "net/http/cookiejar" "net/url" "strings" ) @@ -438,11 +438,6 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { return result, err } -func (r *Registry) ResetClient(authConfig *auth.AuthConfig) { - r.authConfig = authConfig - r.client.Jar = cookiejar.NewCookieJar() -} - func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig { password := "" if withPasswd { @@ -478,18 +473,18 @@ type Registry struct { authConfig *auth.AuthConfig } -func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry { +func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) { httpTransport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, } - r := &Registry{ + r = &Registry{ authConfig: authConfig, client: &http.Client{ Transport: httpTransport, }, } - r.client.Jar = cookiejar.NewCookieJar() - return r + r.client.Jar, err = cookiejar.New(nil) + return r, err } diff --git a/server.go b/server.go index 30e3ec6b3a..7b7eabe321 100644 --- a/server.go +++ b/server.go @@ -54,8 +54,11 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { } func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { - - results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term) + r, err := registry.NewRegistry(srv.runtime.root, nil) + if err != nil { + return nil, err + } + results, err := r.SearchRepositories(term) if err != nil { return nil, err } @@ -402,7 +405,10 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re } func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - r := registry.NewRegistry(srv.runtime.root, authConfig) + r, err := registry.NewRegistry(srv.runtime.root, authConfig) + if err != nil { + return err + } out = utils.NewWriteFlusher(out) if endpoint != "" { if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { @@ -596,8 +602,10 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(name) - r := registry.NewRegistry(srv.runtime.root, authConfig) - + r, err2 := registry.NewRegistry(srv.runtime.root, authConfig) + if err2 != nil { + return err2 + } if err != nil { out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) // If it fails, try to get the repository From 13e03a691145921ffc17c56db24f06eacca99a77 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 17 Jun 2013 11:29:02 -0700 Subject: [PATCH 22/63] Fix the auth tests and add the offline mode --- auth/auth_test.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index 6c8d032cf7..e49ec03721 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -10,8 +10,8 @@ import ( func TestEncodeAuth(t *testing.T) { newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} - authStr := EncodeAuth(newAuthConfig) - decAuthConfig, err := DecodeAuth(authStr) + authStr := encodeAuth(newAuthConfig) + decAuthConfig, err := decodeAuth(authStr) if err != nil { t.Fatal(err) } @@ -27,10 +27,13 @@ func TestEncodeAuth(t *testing.T) { } func TestLogin(t *testing.T) { + if os.Getenv("OFFLINE") != "" { + t.Skip("Offline mode, skipping.") + } os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp") - status, err := Login(authConfig) + status, err := Login(authConfig, false) if err != nil { t.Fatal(err) } @@ -40,6 +43,9 @@ func TestLogin(t *testing.T) { } func TestCreateAccount(t *testing.T) { + if os.Getenv("OFFLINE") != "" { + t.Skip("Offline mode, skipping.") + } os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") tokenBuffer := make([]byte, 16) @@ -50,7 +56,7 @@ func TestCreateAccount(t *testing.T) { token := hex.EncodeToString(tokenBuffer)[:12] username := "ut" + token authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp") - status, err := Login(authConfig) + status, err := Login(authConfig, false) if err != nil { t.Fatal(err) } @@ -60,7 +66,7 @@ func TestCreateAccount(t *testing.T) { t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) } - status, err = Login(authConfig) + status, err = Login(authConfig, false) if err == nil { t.Fatalf("Expected error but found nil instead") } From 3a0ffbc77267e395676860db265ee3476c45b3c2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 17 Jun 2013 14:44:35 -0700 Subject: [PATCH 23/63] - Runtime: Fixes #884 enforce stdout/err sync by merging the stream --- commands.go | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/commands.go b/commands.go index ce15fd6cf1..abe91f6eab 100644 --- a/commands.go +++ b/commands.go @@ -1058,37 +1058,23 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } - splitStderr := container.Config.Tty - - connections := 1 - if splitStderr { - connections += 1 - } - chErrors := make(chan error, connections) + chErrors := make(chan error) if container.Config.Tty { cli.monitorTtySize(cmd.Arg(0)) } - if splitStderr { - go func() { - chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr) - }() - } + v := url.Values{} v.Set("stream", "1") v.Set("stdin", "1") v.Set("stdout", "1") - if !splitStderr { - v.Set("stderr", "1") - } + v.Set("stderr", "1") + go func() { chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout) }() - for connections > 0 { - err := <-chErrors - if err != nil { - return err - } - connections -= 1 + + if err := <-chErrors; err != nil { + return err } return nil } From 0809f649d356b6b8d34a03bee8447049161ea09c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 17 Jun 2013 18:26:41 -0700 Subject: [PATCH 24/63] * 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 3dc93e390ad3d310dede84948b726ce67e261375 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 18 Jun 2013 10:10:03 -0700 Subject: [PATCH 25/63] Remove useless goroutine --- commands.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/commands.go b/commands.go index abe91f6eab..19fb32f966 100644 --- a/commands.go +++ b/commands.go @@ -1058,7 +1058,6 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } - chErrors := make(chan error) if container.Config.Tty { cli.monitorTtySize(cmd.Arg(0)) } @@ -1069,11 +1068,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { v.Set("stdout", "1") v.Set("stderr", "1") - go func() { - chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout) - }() - - if err := <-chErrors; err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout); err != nil { return err } return nil From 3adf9ce04ef1632f82f7bd1585c135bb141aa450 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 18 Jun 2013 18:59:56 +0000 Subject: [PATCH 26/63] add basic support for unix sockets --- api.go | 19 +++++++++-- builder_client.go | 4 +-- commands.go | 47 +++++++++++++++++--------- docker/docker.go | 84 ++++++++++++++++++++++++++++++++--------------- utils/utils.go | 2 ++ 5 files changed, 110 insertions(+), 46 deletions(-) diff --git a/api.go b/api.go index e870ca7723..0b89fcf323 100644 --- a/api.go +++ b/api.go @@ -8,12 +8,16 @@ import ( "github.com/gorilla/mux" "io" "log" + "net" "net/http" + "os" "strconv" "strings" ) const APIVERSION = 1.2 +const DEFAULTHTTPHOST string = "127.0.0.1" +const DEFAULTHTTPPORT int = 4243 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -848,12 +852,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { return r, nil } -func ListenAndServe(addr string, srv *Server, logging bool) error { - log.Printf("Listening for HTTP on %s\n", addr) +func ListenAndServe(proto, addr string, srv *Server, logging bool) error { + log.Printf("Listening for HTTP on %s (%s)\n", addr, proto) r, err := createRouter(srv, logging) if err != nil { return err } - return http.ListenAndServe(addr, r) + l, e := net.Listen(proto, addr) + if e != nil { + return e + } + //as the daemon is launched as root, change to permission of the socket to allow non-root to connect + if proto == "unix" { + os.Chmod(addr, 0777) + } + httpSrv := http.Server{Addr: addr, Handler: r} + return httpSrv.Serve(l) } diff --git a/builder_client.go b/builder_client.go index dc9528ff41..d11e7fc995 100644 --- a/builder_client.go +++ b/builder_client.go @@ -304,9 +304,9 @@ func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) { return "", fmt.Errorf("An error occured during the build\n") } -func NewBuilderClient(addr string, port int) BuildFile { +func NewBuilderClient(proto, addr string) BuildFile { return &builderClient{ - cli: NewDockerCli(addr, port), + cli: NewDockerCli(proto, addr), config: &Config{}, tmpContainers: make(map[string]struct{}), tmpImages: make(map[string]struct{}), diff --git a/commands.go b/commands.go index cf23a8d65e..2c33485ecb 100644 --- a/commands.go +++ b/commands.go @@ -40,8 +40,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { return reflect.TypeOf(cli).MethodByName(methodName) } -func ParseCommands(addr string, port int, args ...string) error { - cli := NewDockerCli(addr, port) +func ParseCommands(proto, addr string, args ...string) error { + cli := NewDockerCli(proto, addr) if len(args) > 0 { method, exists := cli.getMethod(args[0]) @@ -74,7 +74,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } } - help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port) + help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT) for _, command := range [][2]string{ {"attach", "Attach to a running container"}, {"build", "Build a container from a Dockerfile"}, @@ -197,7 +197,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { 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("/v%s/build?%s", APIVERSION, v.Encode()), multipartBody) if err != nil { return err } @@ -206,8 +206,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error { req.Header.Set("X-Docker-Context-Compression", compression.Flag()) fmt.Println("Uploading Context...") } - - resp, err := http.DefaultClient.Do(req) + dial, err := net.Dial(cli.proto, cli.addr) + if err != nil { + return err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() if err != nil { return err } @@ -1339,7 +1344,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, params = bytes.NewBuffer(buf) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params) if err != nil { return nil, -1, err } @@ -1349,7 +1354,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, } else if method == "POST" { req.Header.Set("Content-Type", "plain/text") } - resp, err := http.DefaultClient.Do(req) + dial, err := net.Dial(cli.proto, cli.addr) + if err != nil { + return nil, -1, err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() if err != nil { if strings.Contains(err.Error(), "connection refused") { return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") @@ -1374,7 +1385,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader([]byte{}) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in) if err != nil { return err } @@ -1382,7 +1393,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if method == "POST" { req.Header.Set("Content-Type", "plain/text") } - resp, err := http.DefaultClient.Do(req) + dial, err := net.Dial(cli.proto, cli.addr) + if err != nil { + return err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") @@ -1432,7 +1449,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi return err } req.Header.Set("Content-Type", "plain/text") - dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port)) + dial, err := net.Dial(cli.proto, cli.addr) if err != nil { return err } @@ -1515,13 +1532,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet { return flags } -func NewDockerCli(addr string, port int) *DockerCli { +func NewDockerCli(proto, addr string) *DockerCli { authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) - return &DockerCli{addr, port, authConfig} + return &DockerCli{proto, addr, authConfig} } type DockerCli struct { - host string - port int + proto string + addr string authConfig *auth.AuthConfig } diff --git a/docker/docker.go b/docker/docker.go index 2e23999ad8..85c78c89da 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -24,15 +24,13 @@ func main() { docker.SysInit() return } - host := "127.0.0.1" - port := 4243 // FIXME: Switch d and D ? (to be more sshd like) flDaemon := flag.Bool("d", false, "Daemon mode") flDebug := flag.Bool("D", false, "Debug mode") flAutoRestart := flag.Bool("r", false, "Restart previously running containers") bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") - flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to") + flHost := flag.String("H", fmt.Sprintf("%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT), "Host:port to bind/connect to") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") flag.Parse() @@ -42,21 +40,8 @@ func main() { docker.NetworkBridgeIface = docker.DefaultNetworkBridge } - if strings.Contains(*flHost, ":") { - hostParts := strings.Split(*flHost, ":") - if len(hostParts) != 2 { - log.Fatal("Invalid bind address format.") - os.Exit(-1) - } - if hostParts[0] != "" { - host = hostParts[0] - } - if p, err := strconv.Atoi(hostParts[1]); err == nil { - port = p - } - } else { - host = *flHost - } + protoAddr := parseHost(*flHost) + protoAddrs := []string{protoAddr} if *flDebug { os.Setenv("DEBUG", "1") @@ -67,12 +52,13 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil { + if err := daemon(*pidfile, protoAddrs, *flAutoRestart, *flEnableCors, *flDns); err != nil { log.Fatal(err) os.Exit(-1) } } else { - if err := docker.ParseCommands(host, port, flag.Args()...); err != nil { + protoAddrParts := strings.SplitN(protoAddrs[0], "://", 2) + if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil { log.Fatal(err) os.Exit(-1) } @@ -106,10 +92,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error { - if addr != "127.0.0.1" { - log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") - } +func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -131,6 +114,55 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns if err != nil { return err } - - return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true) + chErrors := make(chan error, len(protoAddrs)) + for _, protoAddr := range protoAddrs { + protoAddrParts := strings.SplitN(protoAddr, "://", 2) + if protoAddrParts[0] == "unix" { + syscall.Unlink(protoAddrParts[1]); + } else if protoAddrParts[0] == "tcp" { + if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") { + log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + } + } else { + log.Fatal("Invalid protocol format.") + os.Exit(-1) + } + go func() { + chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true) + }() + } + for i :=0 ; i < len(protoAddrs); i+=1 { + err := <-chErrors + if err != nil { + return err + } + } + return nil +} + +func parseHost(addr string) string { + if strings.HasPrefix(addr, "unix://") { + return addr + } + host := docker.DEFAULTHTTPHOST + port := docker.DEFAULTHTTPPORT + if strings.HasPrefix(addr, "tcp://") { + addr = strings.TrimPrefix(addr, "tcp://") + } + if strings.Contains(addr, ":") { + hostParts := strings.Split(addr, ":") + if len(hostParts) != 2 { + log.Fatal("Invalid bind address format.") + os.Exit(-1) + } + if hostParts[0] != "" { + host = hostParts[0] + } + if p, err := strconv.Atoi(hostParts[1]); err == nil { + port = p + } + } else { + host = addr + } + return fmt.Sprintf("tcp://%s:%d", host, port) } diff --git a/utils/utils.go b/utils/utils.go index b77c1ea053..da848c45bc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -652,3 +652,5 @@ func CheckLocalDns() bool { } return false } + + From 6e17cc45eabf6d094241fc3e017a90da8ba6f5d9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 18 Jun 2013 12:33:06 -0700 Subject: [PATCH 27/63] 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 28/63] 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 6dccdd657f715c164f2fe6fc786c8274a2425f1b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 18 Jun 2013 17:09:47 -0700 Subject: [PATCH 29/63] remove offline mode from auth unit tests --- auth/auth_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index e49ec03721..ead69e8913 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -27,9 +27,6 @@ func TestEncodeAuth(t *testing.T) { } func TestLogin(t *testing.T) { - if os.Getenv("OFFLINE") != "" { - t.Skip("Offline mode, skipping.") - } os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp") @@ -43,9 +40,6 @@ func TestLogin(t *testing.T) { } func TestCreateAccount(t *testing.T) { - if os.Getenv("OFFLINE") != "" { - t.Skip("Offline mode, skipping.") - } os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") tokenBuffer := make([]byte, 16) From dede1585ee00f957e153691c464aab293c2dc469 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Jun 2013 12:31:54 +0000 Subject: [PATCH 30/63] add the possibility to use multiple -H --- commands.go | 2 +- docker/docker.go | 48 +++++++++++++++--------------------------------- utils/utils.go | 26 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/commands.go b/commands.go index 2c33485ecb..84af648f13 100644 --- a/commands.go +++ b/commands.go @@ -74,7 +74,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } } - help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT) + help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT) for _, command := range [][2]string{ {"attach", "Attach to a running container"}, {"build", "Build a container from a Dockerfile"}, diff --git a/docker/docker.go b/docker/docker.go index 85c78c89da..6d79972bd6 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -30,19 +30,23 @@ func main() { flAutoRestart := flag.Bool("r", false, "Restart previously running containers") bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") - flHost := flag.String("H", fmt.Sprintf("%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT), "Host:port to bind/connect to") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") + flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)} + flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() + if len(flHosts) > 1 { + flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage + } + for i, flHost := range flHosts { + flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost) + } + if *bridgeName != "" { docker.NetworkBridgeIface = *bridgeName } else { docker.NetworkBridgeIface = docker.DefaultNetworkBridge } - - protoAddr := parseHost(*flHost) - protoAddrs := []string{protoAddr} - if *flDebug { os.Setenv("DEBUG", "1") } @@ -52,12 +56,16 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, protoAddrs, *flAutoRestart, *flEnableCors, *flDns); err != nil { + if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { log.Fatal(err) os.Exit(-1) } } else { - protoAddrParts := strings.SplitN(protoAddrs[0], "://", 2) + if len(flHosts) > 1 { + log.Fatal("Please specify only one -H") + return + } + protoAddrParts := strings.SplitN(flHosts[0], "://", 2) if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil { log.Fatal(err) os.Exit(-1) @@ -140,29 +148,3 @@ func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, f return nil } -func parseHost(addr string) string { - if strings.HasPrefix(addr, "unix://") { - return addr - } - host := docker.DEFAULTHTTPHOST - port := docker.DEFAULTHTTPPORT - if strings.HasPrefix(addr, "tcp://") { - addr = strings.TrimPrefix(addr, "tcp://") - } - if strings.Contains(addr, ":") { - hostParts := strings.Split(addr, ":") - if len(hostParts) != 2 { - log.Fatal("Invalid bind address format.") - os.Exit(-1) - } - if hostParts[0] != "" { - host = hostParts[0] - } - if p, err := strconv.Atoi(hostParts[1]); err == nil { - port = p - } - } else { - host = addr - } - return fmt.Sprintf("tcp://%s:%d", host, port) -} diff --git a/utils/utils.go b/utils/utils.go index da848c45bc..37fda5c1c8 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -10,6 +10,7 @@ import ( "index/suffixarray" "io" "io/ioutil" + "log" "net/http" "os" "os/exec" @@ -653,4 +654,29 @@ func CheckLocalDns() bool { return false } +func ParseHost(host string, port int, addr string) string { + if strings.HasPrefix(addr, "unix://") { + return addr + } + if strings.HasPrefix(addr, "tcp://") { + addr = strings.TrimPrefix(addr, "tcp://") + } + if strings.Contains(addr, ":") { + hostParts := strings.Split(addr, ":") + if len(hostParts) != 2 { + log.Fatal("Invalid bind address format.") + os.Exit(-1) + } + if hostParts[0] != "" { + host = hostParts[0] + } + if p, err := strconv.Atoi(hostParts[1]); err == nil { + port = p + } + } else { + host = addr + } + return fmt.Sprintf("tcp://%s:%d", host, port) +} + From 9632bf228744dddfae2c303a0f2e8b962b6e0673 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Jun 2013 12:40:01 +0000 Subject: [PATCH 31/63] add tests --- utils/utils_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/utils/utils_test.go b/utils/utils_test.go index eec06d5134..623f08e383 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -274,3 +274,21 @@ func TestHumanSize(t *testing.T) { t.Errorf("1024 -> expected 1.024 kB, got %s", size1024) } } + +func TestParseHost(t *testing.T) { + if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" { + t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr) + } + if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" { + t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr) + } + if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" { + t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr) + } + if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" { + t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr) + } + if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" { + t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr) + } +} From 063c838c927615e9507eca2a5c2f38a5fc4c35b2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Jun 2013 12:48:50 +0000 Subject: [PATCH 32/63] update docs --- docs/sources/api/docker_remote_api_v1.2.rst | 2 +- docs/sources/commandline/cli.rst | 2 +- docs/sources/use/basics.rst | 22 ++++++++++++++++++--- 3 files changed, 21 insertions(+), 5 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..a34ee01e0e 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -1026,5 +1026,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a 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 + docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index 02691b4f56..118f42f6e8 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -15,7 +15,7 @@ To list available commands, either run ``docker`` with no parameters or execute $ docker Usage: docker [OPTIONS] COMMAND [arg...] - -H="127.0.0.1:4243": Host:port to bind/connect to + -H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use A self-sufficient runtime for linux containers. diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 444b74db51..0f64ec4cf8 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -33,11 +33,16 @@ Running an interactive shell # allocate a tty, attach stdin and stdout docker run -i -t base /bin/bash -Bind Docker to another host/port --------------------------------- +Bind Docker to another host/port or a unix socket +------------------------------------------------- If you want Docker to listen to another port and bind to another ip -use -host and -port on both deamon and client +use -H on both deamon and client. +-H could be (if no sheme, tcp is assumed): +* tcp://host -> tcp connection on host:4243 +* tcp://host:port -> tcp connection on host:port +* tcp://:port -> tcp connection on 127.0.0.1:port +* unix://path/to/socket -> unix socket located at path/to/socket .. code-block:: bash @@ -46,6 +51,17 @@ use -host and -port on both deamon and client # Download a base image docker -H :5555 pull base +You can use multiple -H, for exemple, if you want to listen +on both tcp and a unix socket + +.. code-block:: bash + + # Run docker in daemon mode + sudo /docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock + # Download a base image + docker pull base + # OR + docker -H unix:///var/run/docker.sock pull base Starting a long-running worker process -------------------------------------- From 5dcab2d361956e75d00d5bee18371395f17a663f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Jun 2013 14:50:58 +0000 Subject: [PATCH 33/63] gofmt and test sub directories in makefile --- Makefile | 2 +- auth/auth.go | 2 +- registry/registry.go | 2 +- term/termios_darwin.go | 20 ++++++++++---------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 8676014ad4..ee85221175 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR))) endif test: all - @(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS)) + @(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS)) fmt: @gofmt -s -l -w . diff --git a/auth/auth.go b/auth/auth.go index b7ad73b9ed..159c80f73a 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -82,7 +82,7 @@ func decodeAuth(authStr string) (*AuthConfig, error) { func LoadConfig(rootPath string) (*AuthConfig, error) { confFile := path.Join(rootPath, CONFIGFILE) if _, err := os.Stat(confFile); err != nil { - return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing + return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing } b, err := ioutil.ReadFile(confFile) if err != nil { diff --git a/registry/registry.go b/registry/registry.go index 131b02708e..23aef432c7 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -481,7 +481,7 @@ type Registry struct { func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry { httpTransport := &http.Transport{ DisableKeepAlives: true, - Proxy: http.ProxyFromEnvironment, + Proxy: http.ProxyFromEnvironment, } r := &Registry{ diff --git a/term/termios_darwin.go b/term/termios_darwin.go index ac18aab692..24e79de4b2 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -9,16 +9,16 @@ const ( getTermios = syscall.TIOCGETA setTermios = syscall.TIOCSETA - ECHO = 0x00000008 - ONLCR = 0x2 - ISTRIP = 0x20 - INLCR = 0x40 - ISIG = 0x80 - IGNCR = 0x80 - ICANON = 0x100 - ICRNL = 0x100 - IXOFF = 0x400 - IXON = 0x200 + ECHO = 0x00000008 + ONLCR = 0x2 + ISTRIP = 0x20 + INLCR = 0x40 + ISIG = 0x80 + IGNCR = 0x80 + ICANON = 0x100 + ICRNL = 0x100 + IXOFF = 0x400 + IXON = 0x200 ) type Termios struct { From 2d6a49215c15e7649f8b041e5a04e75b4d360c94 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Jun 2013 18:21:46 +0000 Subject: [PATCH 34/63] add testall rule --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index ee85221175..44497d7d32 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,9 @@ else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR))) endif test: all + @(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS)) + +testall: all @(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS)) fmt: From 88279439af6b5196aaeb9bcb3d7a7154be04d4bc Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 19 Jun 2013 22:07:56 +0300 Subject: [PATCH 35/63] batch apt-get install operations for speed The dockerbuilder Dockerfile was installing one package per apt-get install operation. This changes it so that consecutive run apt-get install operations are batched into a single operation. --- hack/dockerbuilder/Dockerfile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile index 5ccd293ca2..f377f0be76 100644 --- a/hack/dockerbuilder/Dockerfile +++ b/hack/dockerbuilder/Dockerfile @@ -19,19 +19,14 @@ run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu run apt-get update # Packages required to checkout, build and upload docker -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz run tar -C /usr/local -xzf /go.tar.gz run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential # Packages required to build an ubuntu package -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev -run apt-get install -y -q devscripts +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts # Copy dockerbuilder files into the container add . /src run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder From 5fc1329b2fac25c85621d819c82253e9481196d4 Mon Sep 17 00:00:00 2001 From: Rhys Hiltner Date: Wed, 19 Jun 2013 14:43:41 -0700 Subject: [PATCH 36/63] the kernel needs "swapaccount=1" set - some docs are updated, this one seems to have slipped through Fixes #952 --- docs/sources/installation/kernel.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/kernel.rst b/docs/sources/installation/kernel.rst index 6f242e9e10..58730f8191 100644 --- a/docs/sources/installation/kernel.rst +++ b/docs/sources/installation/kernel.rst @@ -100,7 +100,7 @@ Memory and Swap Accounting on Debian/Ubuntu If you use Debian or Ubuntu kernels, and want to enable memory and swap accounting, you must add the following command-line parameters to your kernel:: - cgroup_enable=memory swapaccount + cgroup_enable=memory swapaccount=1 On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can add those parameters by editing ``/etc/default/grub`` and extending @@ -110,6 +110,6 @@ add those parameters by editing ``/etc/default/grub`` and extending And replace it by the following one:: - GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount" + GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" Then run ``update-grub``, and reboot. From 90dde9beabcac3874a274f9f7952f25ba8c1c7a3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 19 Jun 2013 14:59:28 -0700 Subject: [PATCH 37/63] *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 38/63] * 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 39/63] * 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 05796bed57d4e1de352e02fcd6b28aed14f1b670 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 20 Jun 2013 12:31:48 +0000 Subject: [PATCH 40/63] update docs --- docs/sources/use/basics.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 0f64ec4cf8..11d55a5bea 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -36,9 +36,13 @@ Running an interactive shell Bind Docker to another host/port or a unix socket ------------------------------------------------- -If you want Docker to listen to another port and bind to another ip -use -H on both deamon and client. --H could be (if no sheme, tcp is assumed): +With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody. + +Similarly, the Docker client can use -H to connect to a custom port. + +-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path +For example: + * tcp://host -> tcp connection on host:4243 * tcp://host:port -> tcp connection on host:port * tcp://:port -> tcp connection on 127.0.0.1:port @@ -51,7 +55,7 @@ use -H on both deamon and client. # Download a base image docker -H :5555 pull base -You can use multiple -H, for exemple, if you want to listen +You can use multiple -H, for example, if you want to listen on both tcp and a unix socket .. code-block:: bash From 1c841d4feed99ad568b7a5b04cedf8d65c3bb92c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 20 Jun 2013 15:45:30 +0000 Subject: [PATCH 41/63] add warning when you rm a running container --- server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server.go b/server.go index e9be3a1668..e51613e2cf 100644 --- a/server.go +++ b/server.go @@ -751,6 +751,9 @@ func (srv *Server) ContainerRestart(name string, t int) error { func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { if container := srv.runtime.Get(name); container != nil { + if container.State.Running { + return fmt.Errorf("Impossible to remove a running container, please stop it first") + } volumes := make(map[string]struct{}) // Store all the deleted containers volumes for _, volumeId := range container.Volumes { From d8887f34888c1e6c1a846d54e3e8afc4791d88d0 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Thu, 20 Jun 2013 08:57:28 -0700 Subject: [PATCH 42/63] Packaging|ubuntu, issue #960: Add docker PPA staging in release process --- packaging/ubuntu/Makefile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile index f82892c813..582d5bcb25 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -2,11 +2,11 @@ # # Dependencies: debhelper autotools-dev devscripts golang-stable # Notes: -# Use 'make ubuntu' to create the ubuntu package -# GPG_KEY environment variable needs to contain a GPG private key for package to be signed -# and uploaded to docker PPA. -# If GPG_KEY is not defined, make ubuntu will create docker package and exit with -# status code 2 +# Use 'make ubuntu' to create the ubuntu package and push it to stating PPA by +# default. To push to production, set PUBLISH_PPA=1 before doing 'make ubuntu' +# GPG_KEY environment variable needs to contain a GPG private key for package +# to be signed and uploaded to docker PPA. If GPG_KEY is not defined, +# make ubuntu will create docker package and exit with status code 2 PKG_NAME=lxc-docker GITHUB_PATH=github.com/dotcloud/docker @@ -52,9 +52,11 @@ ubuntu: if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi mkdir ${BUILD_SRC} # Import gpg signing key - echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import + echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import || true # Sign the package cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa - cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes + # Upload to PPA + if [ "${PUBLISH_PPA}" = "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes; fi + if [ "${PUBLISH_PPA}" != "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/docker-staging ${PKG_NAME}_${VERSION}-1_source.changes; fi rm -rf ${BUILD_SRC} From c5be64fec478f5081a06480951811d30753fc46a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 3 Jun 2013 13:18:44 -0700 Subject: [PATCH 43/63] Add link flags in order to link statically and without debug symbols --- Makefile | 4 ++-- packaging/ubuntu/Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8676014ad4..bc9a24ecef 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ endif GIT_COMMIT = $(shell git rev-parse --short HEAD) GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES") -BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)" +BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w" SRC_DIR := $(GOPATH)/src @@ -33,7 +33,7 @@ all: $(DOCKER_BIN) $(DOCKER_BIN): $(DOCKER_DIR) @mkdir -p $(dir $@) - @(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@) + @(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@) @echo $(DOCKER_BIN_RELATIVE) is created. $(DOCKER_DIR): diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile index 582d5bcb25..2da1be764e 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -15,7 +15,7 @@ VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md) all: # Compile docker. Used by dpkg-buildpackage. - cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build + cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} CGO_ENABLED=0 go build -ldflags '-d -w' install: # Used by dpkg-buildpackage From db60337598d264dc02d67e0dde93b300bf971903 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 14 Jun 2013 17:43:12 -0700 Subject: [PATCH 44/63] Makefile: added missing -a option --- Makefile | 2 +- packaging/ubuntu/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index bc9a24ecef..bf79e298e9 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ endif GIT_COMMIT = $(shell git rev-parse --short HEAD) GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES") -BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w" +BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w" SRC_DIR := $(GOPATH)/src diff --git a/packaging/ubuntu/Makefile b/packaging/ubuntu/Makefile index 2da1be764e..f9e034c7b2 100644 --- a/packaging/ubuntu/Makefile +++ b/packaging/ubuntu/Makefile @@ -15,7 +15,7 @@ VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md) all: # Compile docker. Used by dpkg-buildpackage. - cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} CGO_ENABLED=0 go build -ldflags '-d -w' + cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} CGO_ENABLED=0 go build -a -ldflags '-d -w' install: # Used by dpkg-buildpackage From da5bb4db96b369365c4b913d9009d8bb1d0f51f4 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 19 Jun 2013 14:27:54 -0700 Subject: [PATCH 45/63] Bumped version to 0.4.3 --- CHANGELOG.md | 20 ++++++++++++++++++++ commands.go | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e2112218b..fa255e7c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.4.3 (2013-06-19) + + Builder: ADD of a local file will detect tar archives and unpack them + * Runtime: Remove bsdtar dependency + * Runtime: Add unix socket and multiple -H support + * Runtime: Prevent rm of running containers + * Runtime: Use go1.1 cookiejar + * Builder: ADD improvements: use tar for copy + automatically unpack local archives + * Builder: ADD uses tar/untar for copies instead of calling 'cp -ar' + * Builder: nicer output for 'docker build' + * Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented. + * Client: HumanReadable ProgressBar sizes in pull + * Client: Fix docker version's git commit output + * API: Send all tags on History API call + * API: Add tag lookup to history command. Fixes #882 + - Runtime: Fix issue detaching from running TTY container + - Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311 + - Runtime: Fix race condition within Run command when attaching. + - Builder: fix a bug which caused builds to fail if ADD was the first command + - Documentation: fix missing command in irc bouncer example + ## 0.4.2 (2013-06-17) - Packaging: Bumped version to work around an Ubuntu bug diff --git a/commands.go b/commands.go index cce2ec8557..6ff9ac4425 100644 --- a/commands.go +++ b/commands.go @@ -29,7 +29,7 @@ import ( "unicode" ) -const VERSION = "0.4.2" +const VERSION = "0.4.3" var ( GITCOMMIT string From cff2187a4ccf90a5be821c316b564e934d80998c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 12:30:02 -0700 Subject: [PATCH 46/63] 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 da063497232ebe7202b1c7a0c5ecdecaccb3681b Mon Sep 17 00:00:00 2001 From: Fabrizio Regini Date: Thu, 20 Jun 2013 23:15:38 +0200 Subject: [PATCH 47/63] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd722c4504..376ecea703 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Quick install on Ubuntu 12.04 and 12.10 --------------------------------------- ```bash -curl get.docker.io | sh -x +curl get.docker.io | sudo sh -x ``` Binary installs From dbfb3eb92391d90741be3238551a0fffcf2dbcdc Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 14:29:34 -0700 Subject: [PATCH 48/63] - Builder: hotfix for bug introduced in 3adf9ce04ef1632f82f7bd1585c135bb141aa450 --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 6ff9ac4425..2a3979ba5e 100644 --- a/commands.go +++ b/commands.go @@ -197,7 +197,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v := &url.Values{} v.Set("t", *tag) // Send the multipart request with correct content-type - req, err := http.NewRequest("POST", fmt.Sprintf("/v%s/build?%s", APIVERSION, v.Encode()), multipartBody) + req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), multipartBody) if err != nil { return err } From 02f0c1e46d1d73677aed899c1f0deaa69ab11ffc Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 14:33:59 -0700 Subject: [PATCH 49/63] Bump version to 0.4.4 --- CHANGELOG.md | 3 +++ commands.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa255e7c40..1144800150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.4.4 (2013-06-19) + - Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients. + ## 0.4.3 (2013-06-19) + Builder: ADD of a local file will detect tar archives and unpack them * Runtime: Remove bsdtar dependency diff --git a/commands.go b/commands.go index 2a3979ba5e..7cd981571b 100644 --- a/commands.go +++ b/commands.go @@ -29,7 +29,7 @@ import ( "unicode" ) -const VERSION = "0.4.3" +const VERSION = "0.4.4" var ( GITCOMMIT string From 08825fa611cf5067b99bed452ffe6320ed00003a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 20 Jun 2013 16:31:11 -0700 Subject: [PATCH 50/63] remove unused files --- getKernelVersion_darwin.go | 10 ------ getKernelVersion_linux.go | 71 -------------------------------------- 2 files changed, 81 deletions(-) delete mode 100644 getKernelVersion_darwin.go delete mode 100644 getKernelVersion_linux.go diff --git a/getKernelVersion_darwin.go b/getKernelVersion_darwin.go deleted file mode 100644 index 2fce282716..0000000000 --- a/getKernelVersion_darwin.go +++ /dev/null @@ -1,10 +0,0 @@ -package docker - -import ( - "fmt" - "github.com/dotcloud/docker/utils" -) - -func getKernelVersion() (*utils.KernelVersionInfo, error) { - return nil, fmt.Errorf("Kernel version detection is not available on darwin") -} diff --git a/getKernelVersion_linux.go b/getKernelVersion_linux.go deleted file mode 100644 index 4f9c7db70c..0000000000 --- a/getKernelVersion_linux.go +++ /dev/null @@ -1,71 +0,0 @@ -package docker - -import ( - "bytes" - "github.com/dotcloud/docker/utils" - "strconv" - "strings" - "syscall" -) - -// FIXME: Move this to utils package -func getKernelVersion() (*utils.KernelVersionInfo, error) { - var ( - uts syscall.Utsname - flavor string - kernel, major, minor int - err error - ) - - if err := syscall.Uname(&uts); err != nil { - return nil, err - } - - release := make([]byte, len(uts.Release)) - - i := 0 - for _, c := range uts.Release { - release[i] = byte(c) - i++ - } - - // Remove the \x00 from the release for Atoi to parse correctly - release = release[:bytes.IndexByte(release, 0)] - - tmp := strings.SplitN(string(release), "-", 2) - tmp2 := strings.SplitN(tmp[0], ".", 3) - - if len(tmp2) > 0 { - kernel, err = strconv.Atoi(tmp2[0]) - if err != nil { - return nil, err - } - } - - if len(tmp2) > 1 { - major, err = strconv.Atoi(tmp2[1]) - if err != nil { - return nil, err - } - } - - if len(tmp2) > 2 { - minor, err = strconv.Atoi(tmp2[2]) - if err != nil { - return nil, err - } - } - - if len(tmp) == 2 { - flavor = tmp[1] - } else { - flavor = "" - } - - return &utils.KernelVersionInfo{ - Kernel: kernel, - Major: major, - Minor: minor, - Flavor: flavor, - }, nil -} From b419699ab8f79f4826ec94583c6f7a46f74eeaa2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 20 Jun 2013 18:18:36 -0700 Subject: [PATCH 51/63] Use hijack for logs instead of stream --- api.go | 2 ++ commands.go | 12 +++++++++--- server.go | 8 ++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/api.go b/api.go index 0b89fcf323..403254649b 100644 --- a/api.go +++ b/api.go @@ -816,6 +816,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { localFct := fct f := func(w http.ResponseWriter, r *http.Request) { utils.Debugf("Calling %s %s", localMethod, localRoute) + if logging { log.Println(r.Method, r.RequestURI) } @@ -836,6 +837,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { w.WriteHeader(http.StatusNotFound) return } + if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil { httpError(w, err) } diff --git a/commands.go b/commands.go index 7cd981571b..1f3426f900 100644 --- a/commands.go +++ b/commands.go @@ -1036,10 +1036,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return nil } - if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, os.Stdout); err != nil { return err } - if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, os.Stderr); err != nil { return err } return nil @@ -1370,6 +1370,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e return err } defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 400 { body, err := ioutil.ReadAll(resp.Body) if err != nil { @@ -1407,19 +1408,24 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e } func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error { + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) if err != nil { return err } + req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Header.Set("Content-Type", "plain/text") + dial, err := net.Dial(cli.proto, cli.addr) if err != nil { return err } clientconn := httputil.NewClientConn(dial, nil) - clientconn.Do(req) defer clientconn.Close() + // Server hijacks the connection, error 'connection closed' expected + clientconn.Do(req) + rwc, br := clientconn.Hijack() defer rwc.Close() diff --git a/server.go b/server.go index 35388d8605..ad4f092a7f 100644 --- a/server.go +++ b/server.go @@ -979,17 +979,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std if stdout { cLog, err := container.ReadLog("stdout") if err != nil { - utils.Debugf(err.Error()) + utils.Debugf("Error reading logs (stdout): %s", err) } else if _, err := io.Copy(out, cLog); err != nil { - utils.Debugf(err.Error()) + utils.Debugf("Error streaming logs (stdout): %s", err) } } if stderr { cLog, err := container.ReadLog("stderr") if err != nil { - utils.Debugf(err.Error()) + utils.Debugf("Error reading logs (stderr): %s", err) } else if _, err := io.Copy(out, cLog); err != nil { - utils.Debugf(err.Error()) + utils.Debugf("Error streaming logs (stderr): %s", err) } } } From cc0f59742f192ccbc178c59bdc7eb98d0f6dd943 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 20:16:39 -0700 Subject: [PATCH 52/63] * 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 50b70eeb6830040db73c8b2b389d881a3200de8d Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Thu, 20 Jun 2013 23:19:44 -0400 Subject: [PATCH 53/63] Remove code unreachable using Go 1.1 --- container.go | 3 --- contrib/crashTest.go | 1 - network.go | 1 - utils/utils.go | 3 --- 4 files changed, 8 deletions(-) diff --git a/container.go b/container.go index f60de21bdc..cc360243f7 100644 --- a/container.go +++ b/container.go @@ -632,7 +632,6 @@ func (container *Container) waitLxc() error { } time.Sleep(500 * time.Millisecond) } - panic("Unreachable") } func (container *Container) monitor() { @@ -821,8 +820,6 @@ func (container *Container) WaitTimeout(timeout time.Duration) error { case <-done: return nil } - - panic("Unreachable") } func (container *Container) EnsureMounted() error { diff --git a/contrib/crashTest.go b/contrib/crashTest.go index b3dbacaf03..d3ba80698c 100644 --- a/contrib/crashTest.go +++ b/contrib/crashTest.go @@ -116,7 +116,6 @@ func crashTest() error { return err } } - return nil } func main() { diff --git a/network.go b/network.go index ea5e5c8586..37037dd14a 100644 --- a/network.go +++ b/network.go @@ -257,7 +257,6 @@ func proxy(listener net.Listener, proto, address string) error { utils.Debugf("Connected to backend, splicing") splice(src, dst) } - panic("Unreachable") } func halfSplice(dst, src net.Conn) error { diff --git a/utils/utils.go b/utils/utils.go index 3cd2f4e7ff..6ab8a64bc0 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -236,7 +236,6 @@ func (r *bufReader) Read(p []byte) (n int, err error) { } r.wait.Wait() } - panic("unreachable") } func (r *bufReader) Close() error { @@ -678,5 +677,3 @@ func ParseHost(host string, port int, addr string) string { } return fmt.Sprintf("tcp://%s:%d", host, port) } - - From 36d610a38806feb4cb5d09c4705a6f1fff42ef04 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 20:20:16 -0700 Subject: [PATCH 54/63] - 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 b0b690cf23871d36e1f981fcee6cb929158c4fee Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Thu, 20 Jun 2013 23:23:49 -0400 Subject: [PATCH 55/63] Update AUTHORS --- .mailmap | 4 ++++ AUTHORS | 22 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 1f38e55e28..452ac41d8f 100644 --- a/.mailmap +++ b/.mailmap @@ -19,3 +19,7 @@ Andy Smith Thatcher Peskens + +Walter Stanish + +Roberto Hashioka diff --git a/AUTHORS b/AUTHORS index 89fa178c3b..7c7ba52477 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,6 +6,8 @@ Al Tobey Alexey Shamrin Andrea Luzzardi +Andreas Tiefenthaler +Andrew Munsell Andy Rothfusz Andy Smith Antony Messerli @@ -14,7 +16,9 @@ Brandon Liu Brian McCallister Bruno Bigras Caleb Spare +Calen Pennington Charles Hooper +Christopher Currie Daniel Gasienica Daniel Mizyrycki Daniel Robinson @@ -22,11 +26,14 @@ Daniel Von Fange Dominik Honnef Don Spaulding Dr Nic Williams +Elias Probst +Eric Hanchrow Evan Wies ezbercih Flavio Castelli Francisco Souza Frederick F. Kautz IV +Gareth Rushgrove Guillaume J. Charmes Harley Laue Hunter Blanks @@ -34,15 +41,21 @@ Jeff Lindsay Jeremy Grosser Joffrey F John Costa +Jon Wedaman Jonas Pfenniger Jonathan Rudenberg +Joseph Anthony Pasquale Holsten Julien Barbier Jérôme Petazzoni Ken Cochrane Kevin J. Lynagh +kim0 +Kiran Gangadharan Louis Opter Marcus Farkas +Mark McGranaghan Maxim Treskin +meejah Michael Crosby Mikhail Sobolev Nate Jones @@ -51,18 +64,25 @@ Niall O'Higgins odk- Paul Bowsher Paul Hammond +Phil Spitler Piotr Bogdan +Renato Riccieri Santos Zannon Robert Obryk +Roberto Hashioka Sam Alba +Sam J Sharpe Shawn Siefkas Silas Sewell Solomon Hykes Sridhar Ratnakumar Thatcher Peskens Thomas Bikeev +Thomas Hansen Tianon Gravi Tim Terhorst -Troy Howard +Tobias Bieniek unclejack Victor Vieux Vivek Agarwal +Walter Stanish +Will Dietz From d9bce2defd56bbc47f47b263257b64016e48d607 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 20 Jun 2013 22:15:19 -0700 Subject: [PATCH 56/63] - 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 66910a76027d3e37b6a96c0bd1106cfd5d27ba40 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 21 Jun 2013 11:06:41 +0300 Subject: [PATCH 57/63] mark command as optional for docker run --- container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container.go b/container.go index f60de21bdc..f6abf14e68 100644 --- a/container.go +++ b/container.go @@ -76,7 +76,7 @@ type Config struct { } func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) { - cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") + cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) } From 6a1279fb90ad33fec05a48529981db7bb093b782 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 21 Jun 2013 11:07:14 +0300 Subject: [PATCH 58/63] docs: mark command as optional for docker run --- docs/sources/commandline/command/run.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index d6c9aef315..8227d9f310 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -8,7 +8,7 @@ :: - Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...] + Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container From 8e7d4cda078482b84836f51e3e7ce17aa0f65217 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 21 Jun 2013 11:13:13 +0200 Subject: [PATCH 59/63] fix doc post --- docs/sources/api/docker_remote_api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 824bfa4657..99e819655d 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -36,6 +36,7 @@ The client should send it's authConfig as POST on each call of /images/(name)/pu .. http:post:: /auth only checks the configuration but doesn't store it on the server Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any. + .. http:post:: /images//delete now returns a JSON with the list of images deleted/untagged From 639833aaf53d741176e0b4f817cbbf5a5da24037 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 21 Jun 2013 09:20:57 +0000 Subject: [PATCH 60/63] fix tests --- auth/auth_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index ead69e8913..8e7adaef8d 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -34,7 +34,7 @@ func TestLogin(t *testing.T) { if err != nil { t.Fatal(err) } - if status != "Login Succeeded\n" { + if status != "Login Succeeded" { t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status) } } @@ -55,7 +55,7 @@ func TestCreateAccount(t *testing.T) { t.Fatal(err) } expectedStatus := "Account created. Please use the confirmation link we sent" + - " to your e-mail to activate it.\n" + " to your e-mail to activate it." if status != expectedStatus { t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) } From 3ac68f1966222d9a1c0ff867515e3f5c2f83e422 Mon Sep 17 00:00:00 2001 From: Marcus Farkas Date: Fri, 21 Jun 2013 17:19:27 +0200 Subject: [PATCH 61/63] Client: better progressbar output --- utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 3cd2f4e7ff..4229a06631 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -87,7 +87,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) { } if r.readProgress-r.lastUpdate > updateEvery || err != nil { if r.readTotal > 0 { - fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100)) + fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%2.0f%%",float64(r.readProgress)/float64(r.readTotal)*100)) } else { fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a") } @@ -147,7 +147,7 @@ func HumanSize(size int64) string { sizef = sizef / 1000.0 i++ } - return fmt.Sprintf("%.4g %s", sizef, units[i]) + return fmt.Sprintf("%5.4g %s", sizef, units[i]) } func Trunc(s string, maxlen int) string { From d0fa6927f8ad87e73a02f64ae9d6db6a820c1f4c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 21 Jun 2013 13:51:48 -0700 Subject: [PATCH 62/63] 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 63/63] 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