From 4388bef996063b3b69e738082b6820d3a979921e Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Fri, 5 Jul 2013 16:49:55 -0700 Subject: [PATCH 001/101] testing, issue #1104: Make the test use static flags --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9b06df3d64eeed4b1222014f85f0efcaa476f7e1..46d003d878e46cf1f6be25e36e145af7bc659c67 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ BUILD_DIR := $(CURDIR)/.gopath GOPATH ?= $(BUILD_DIR) export GOPATH -GO_OPTIONS ?= +GO_OPTIONS ?= -a -ldflags='-w -d' ifeq ($(VERBOSE), 1) GO_OPTIONS += -v endif @@ -79,10 +79,10 @@ test: tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH} GOPATH=${CURDIR}/${BUILD_SRC} go get -d # Do the test - sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS} + sudo -E GOPATH=${CURDIR}/${BUILD_SRC} CGO_ENABLED=0 go test ${GO_OPTIONS} testall: all - @(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS)) + @(cd $(DOCKER_DIR); CGO_ENABLED=0 sudo -E go test ./... $(GO_OPTIONS)) fmt: @gofmt -s -l -w . From 1d01189f04f5187bd39e9212b7af3b3e83e86361 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:12:12 -0400 Subject: [PATCH 002/101] Added version checker interface --- registry/registry.go | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index fc84f19ec4fb1bece6ad0272eb6650749e72ac7c..12ca3c4bfb2e8d98955699b4bb94ad258ceb70ac 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -98,6 +98,35 @@ func ResolveRepositoryName(reposName string) (string, string, error) { return endpoint, reposName, err } +// VersionChecker is used to model entities which has a version. +// It is basically a tupple with name and version. +type VersionChecker interface { + Name() string + Version() string +} + +func setUserAgentHeader(req *http.Request, baseVersions []VersionChecker, extra ...VersionChecker) error { + if len(baseVersions)+len(extra) == 0 { + return nil + } + userAgent := make(map[string]string, len(baseVersions)+len(extra)) + + for _, v := range baseVersions { + userAgent[v.Name()] = v.Version() + } + for _, v := range extra { + userAgent[v.Name()] = v.Version() + } + + header, err := json.Marshal(userAgent) + userAgent = nil + if err != nil { + return err + } + req.Header.Set("User-Agent", string(header)) + return nil +} + func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { for _, cookie := range c.Jar.Cookies(req.URL) { req.AddCookie(cookie) @@ -536,11 +565,12 @@ type ImgData struct { } type Registry struct { - client *http.Client - authConfig *auth.AuthConfig + client *http.Client + authConfig *auth.AuthConfig + baseVersions []VersionChecker } -func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) { +func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...VersionChecker) (r *Registry, err error) { httpTransport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, @@ -553,5 +583,9 @@ func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err err }, } r.client.Jar, err = cookiejar.New(nil) - return r, err + if err != nil { + return nil, err + } + r.baseVersions = baseVersions + return r, nil } From 1bb8f60d5ae3810b465dd3c79a7a572fb017d078 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:24:54 -0400 Subject: [PATCH 003/101] inserted setUserAgent in each HTTP request --- registry/registry.go | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 12ca3c4bfb2e8d98955699b4bb94ad258ceb70ac..c51df1ac2de858230444cb02698e61320210af7e 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -105,33 +105,30 @@ type VersionChecker interface { Version() string } -func setUserAgentHeader(req *http.Request, baseVersions []VersionChecker, extra ...VersionChecker) error { - if len(baseVersions)+len(extra) == 0 { - return nil +func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { + for _, cookie := range c.Jar.Cookies(req.URL) { + req.AddCookie(cookie) } - userAgent := make(map[string]string, len(baseVersions)+len(extra)) + return c.Do(req) +} - for _, v := range baseVersions { +func (r *Registry) setUserAgent(req *http.Request, extra ...VersionChecker) { + if len(r.baseVersions)+len(extra) == 0 { + return + } + userAgent := make(map[string]string, len(r.baseVersions)+len(extra)) + + for _, v := range r.baseVersions { userAgent[v.Name()] = v.Version() } for _, v := range extra { userAgent[v.Name()] = v.Version() } - header, err := json.Marshal(userAgent) + header, _ := json.Marshal(userAgent) userAgent = nil - if err != nil { - return err - } req.Header.Set("User-Agent", string(header)) - return nil -} - -func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { - for _, cookie := range c.Jar.Cookies(req.URL) { - req.AddCookie(cookie) - } - return c.Do(req) + return } // Retrieve the history of a given image from the Registry. @@ -142,6 +139,9 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s return nil, err } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + if err != nil { + return nil, err + } res, err := r.client.Do(req) if err != nil || res.StatusCode != 200 { if res != nil { @@ -188,6 +188,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return nil, -1, fmt.Errorf("Failed to download json: %s", err) } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + r.setUserAgent(req, nil) res, err := r.client.Do(req) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) @@ -215,6 +216,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) ( return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + r.setUserAgent(req, nil) res, err := r.client.Do(req) if err != nil { return nil, err @@ -235,6 +237,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, err } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) + r.setUserAgent(req, nil) res, err := r.client.Do(req) if err != nil { return nil, err @@ -273,6 +276,7 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) } req.Header.Set("X-Docker-Token", "true") + r.setUserAgent(req, nil) res, err := r.client.Do(req) if err != nil { @@ -336,6 +340,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) req.Header.Set("X-Docker-Checksum", imgData.Checksum) + r.setUserAgent(req, nil) utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum) res, err := doWithCookies(r.client, req) @@ -370,6 +375,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + r.setUserAgent(req, nil) res, err := doWithCookies(r.client, req) if err != nil { return fmt.Errorf("Failed to upload layer: %s", err) @@ -407,6 +413,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token } req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + r.setUserAgent(req, nil) req.ContentLength = int64(len(revision)) res, err := doWithCookies(r.client, req) if err != nil { @@ -439,6 +446,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.ContentLength = int64(len(imgListJSON)) req.Header.Set("X-Docker-Token", "true") + r.setUserAgent(req, nil) if validate { req.Header["X-Docker-Endpoints"] = regs } @@ -459,6 +467,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.ContentLength = int64(len(imgListJSON)) req.Header.Set("X-Docker-Token", "true") + r.setUserAgent(req, nil) if validate { req.Header["X-Docker-Endpoints"] = regs } From 65185a565b8e05a2dd58e10d1c1ad560f4a255cf Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:33:28 -0400 Subject: [PATCH 004/101] added APIVersion when call NewRegistry --- api_params.go | 12 ++++++++++++ server.go | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/api_params.go b/api_params.go index b8af690c7f40a1ca12e017ef9f367d4847a2c355..217062b66fab5daa0ee55c838b75ab0007bb5db9 100644 --- a/api_params.go +++ b/api_params.go @@ -66,6 +66,18 @@ type APIVersion struct { GoVersion string `json:",omitempty"` } +func (v *APIVersion) Name() string { + return "docker" +} + +func (v *APIVersion) Version() string { + r, err := json.Marshal(v) + if err != nil { + return r.Version + } + return string(r) +} + type APIWait struct { StatusCode int } diff --git a/server.go b/server.go index f1c09095160ac5973abf097a5f5cc4bd84a24ce5..c1ea5670e01825a49808793cec6e5b66c0d64fdd 100644 --- a/server.go +++ b/server.go @@ -55,7 +55,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { } func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { - r, err := registry.NewRegistry(srv.runtime.root, nil) + r, err := registry.NewRegistry(srv.runtime.root, nil, srv.DockerVersion()) if err != nil { return nil, err } @@ -470,7 +470,7 @@ func (srv *Server) poolRemove(kind, key string) error { } func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - r, err := registry.NewRegistry(srv.runtime.root, authConfig) + r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.DockerVersion()) if err != nil { return err } @@ -687,7 +687,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) - r, err2 := registry.NewRegistry(srv.runtime.root, authConfig) + r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.DockerVersion()) if err2 != nil { return err2 } From 5705a493080ba5571a7929d37e7926678a982cb4 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:42:04 -0400 Subject: [PATCH 005/101] Insert version checkers when call NewRegistry() --- api_params.go | 12 ------------ server.go | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/api_params.go b/api_params.go index 217062b66fab5daa0ee55c838b75ab0007bb5db9..b8af690c7f40a1ca12e017ef9f367d4847a2c355 100644 --- a/api_params.go +++ b/api_params.go @@ -66,18 +66,6 @@ type APIVersion struct { GoVersion string `json:",omitempty"` } -func (v *APIVersion) Name() string { - return "docker" -} - -func (v *APIVersion) Version() string { - r, err := json.Marshal(v) - if err != nil { - return r.Version - } - return string(r) -} - type APIWait struct { StatusCode int } diff --git a/server.go b/server.go index c1ea5670e01825a49808793cec6e5b66c0d64fdd..b943ef2985e246f27eea3178fcdd81d643c36f74 100644 --- a/server.go +++ b/server.go @@ -26,6 +26,33 @@ func (srv *Server) DockerVersion() APIVersion { } } +type plainVersionChecker struct { + name string + version string +} + +func (v *plainVersionChecker) Name() string { + return v.name +} + +func (v *plainVersionChecker) Version() string { + return v.version +} + +func (srv *Server) versionCheckers() []registry.VersionChecker { + v := srv.DockerVersion() + ret := make([]registry.VersionChecker, 0, 3) + ret = append(ret, &plainVersionChecker{"docker", v.Version}) + + if len(v.GoVersion) > 0 { + ret = append(ret, &plainVersionChecker{"go", v.GoVersion}) + } + if len(v.GitCommit) > 0 { + ret = append(ret, &plainVersionChecker{"git-commit", v.GitCommit}) + } + return ret +} + func (srv *Server) ContainerKill(name string) error { if container := srv.runtime.Get(name); container != nil { if err := container.Kill(); err != nil { @@ -55,7 +82,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { } func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { - r, err := registry.NewRegistry(srv.runtime.root, nil, srv.DockerVersion()) + r, err := registry.NewRegistry(srv.runtime.root, nil, srv.versionCheckers()...) if err != nil { return nil, err } @@ -470,7 +497,7 @@ func (srv *Server) poolRemove(kind, key string) error { } func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.DockerVersion()) + r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionCheckers()...) if err != nil { return err } @@ -687,7 +714,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) - r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.DockerVersion()) + r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionCheckers()...) if err2 != nil { return err2 } From d40efc4648af6bb5c60b37a789effd602af1f132 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:48:37 -0400 Subject: [PATCH 006/101] added client's kernel version --- registry/registry.go | 24 +++++++++++++++--------- server.go | 7 ++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index c51df1ac2de858230444cb02698e61320210af7e..683a64ab6a514c11dc7610894320a4beef8885a0 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -119,9 +119,15 @@ func (r *Registry) setUserAgent(req *http.Request, extra ...VersionChecker) { userAgent := make(map[string]string, len(r.baseVersions)+len(extra)) for _, v := range r.baseVersions { + if v == nil { + continue + } userAgent[v.Name()] = v.Version() } for _, v := range extra { + if v == nil { + continue + } userAgent[v.Name()] = v.Version() } @@ -188,7 +194,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return nil, -1, fmt.Errorf("Failed to download json: %s", err) } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - r.setUserAgent(req, nil) + r.setUserAgent(req) res, err := r.client.Do(req) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) @@ -216,7 +222,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) ( return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - r.setUserAgent(req, nil) + r.setUserAgent(req) res, err := r.client.Do(req) if err != nil { return nil, err @@ -237,7 +243,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, err } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - r.setUserAgent(req, nil) + r.setUserAgent(req) res, err := r.client.Do(req) if err != nil { return nil, err @@ -276,7 +282,7 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) } req.Header.Set("X-Docker-Token", "true") - r.setUserAgent(req, nil) + r.setUserAgent(req) res, err := r.client.Do(req) if err != nil { @@ -340,7 +346,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) req.Header.Set("X-Docker-Checksum", imgData.Checksum) - r.setUserAgent(req, nil) + r.setUserAgent(req) utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum) res, err := doWithCookies(r.client, req) @@ -375,7 +381,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) - r.setUserAgent(req, nil) + r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { return fmt.Errorf("Failed to upload layer: %s", err) @@ -413,7 +419,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token } req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) - r.setUserAgent(req, nil) + r.setUserAgent(req) req.ContentLength = int64(len(revision)) res, err := doWithCookies(r.client, req) if err != nil { @@ -446,7 +452,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.ContentLength = int64(len(imgListJSON)) req.Header.Set("X-Docker-Token", "true") - r.setUserAgent(req, nil) + r.setUserAgent(req) if validate { req.Header["X-Docker-Endpoints"] = regs } @@ -467,7 +473,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) req.ContentLength = int64(len(imgListJSON)) req.Header.Set("X-Docker-Token", "true") - r.setUserAgent(req, nil) + r.setUserAgent(req) if validate { req.Header["X-Docker-Endpoints"] = regs } diff --git a/server.go b/server.go index b943ef2985e246f27eea3178fcdd81d643c36f74..70bb0bb87118e4cab4f54b605a518d76561b7293 100644 --- a/server.go +++ b/server.go @@ -41,7 +41,7 @@ func (v *plainVersionChecker) Version() string { func (srv *Server) versionCheckers() []registry.VersionChecker { v := srv.DockerVersion() - ret := make([]registry.VersionChecker, 0, 3) + ret := make([]registry.VersionChecker, 0, 4) ret = append(ret, &plainVersionChecker{"docker", v.Version}) if len(v.GoVersion) > 0 { @@ -50,6 +50,11 @@ func (srv *Server) versionCheckers() []registry.VersionChecker { if len(v.GitCommit) > 0 { ret = append(ret, &plainVersionChecker{"git-commit", v.GitCommit}) } + kernelVersion, err := utils.GetKernelVersion() + if err == nil { + ret = append(ret, &plainVersionChecker{"kernel", kernelVersion.String()}) + } + return ret } From 26c8eae6fea53e9a78bd035614fff20086f00b17 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 18:45:45 -0400 Subject: [PATCH 007/101] Removed an unnecessary error check. --- registry/registry.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 683a64ab6a514c11dc7610894320a4beef8885a0..26cefbbdeff83198383881c1a5e5ff19d582086e 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -145,9 +145,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s return nil, err } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) - if err != nil { - return nil, err - } + r.setUserAgent(req) res, err := r.client.Do(req) if err != nil || res.StatusCode != 200 { if res != nil { From e832b01349fec2acee6ec3219dc9bfb61ad38764 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 18:46:25 -0400 Subject: [PATCH 008/101] Removed an unnecessary nil assignment --- registry/registry.go | 1 - 1 file changed, 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 26cefbbdeff83198383881c1a5e5ff19d582086e..920f945935f46cf9957914b75d9bfe1c8d8ecc5f 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -132,7 +132,6 @@ func (r *Registry) setUserAgent(req *http.Request, extra ...VersionChecker) { } header, _ := json.Marshal(userAgent) - userAgent = nil req.Header.Set("User-Agent", string(header)) return } From 34cf976866f66bc77b961f4e66a9dd8aad1ffb00 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 19:29:02 -0400 Subject: [PATCH 009/101] format in the user agent header should follow RFC 2616 --- registry/registry.go | 59 ++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 920f945935f46cf9957914b75d9bfe1c8d8ecc5f..0840ffbb832ce342e043712d83bb957cfb36668a 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -116,23 +116,9 @@ func (r *Registry) setUserAgent(req *http.Request, extra ...VersionChecker) { if len(r.baseVersions)+len(extra) == 0 { return } - userAgent := make(map[string]string, len(r.baseVersions)+len(extra)) - for _, v := range r.baseVersions { - if v == nil { - continue - } - userAgent[v.Name()] = v.Version() - } - for _, v := range extra { - if v == nil { - continue - } - userAgent[v.Name()] = v.Version() - } - - header, _ := json.Marshal(userAgent) - req.Header.Set("User-Agent", string(header)) + userAgent := appendVersions(r.baseVersionsStr, extra...) + req.Header.Set("User-Agent", userAgent) return } @@ -577,9 +563,43 @@ type ImgData struct { } type Registry struct { - client *http.Client - authConfig *auth.AuthConfig - baseVersions []VersionChecker + client *http.Client + authConfig *auth.AuthConfig + baseVersions []VersionChecker + baseVersionsStr string +} + +func validVersion(version VersionChecker) bool { + stopChars := " \t\r\n/" + if strings.ContainsAny(version.Name(), stopChars) { + return false + } + if strings.ContainsAny(version.Version(), stopChars) { + return false + } + return true +} + +func appendVersions(base string, versions ...VersionChecker) string { + if len(versions) == 0 { + return base + } + + var buf bytes.Buffer + if len(base) > 0 { + buf.Write([]byte(base)) + } + + for _, v := range versions { + if !validVersion(v) { + continue + } + buf.Write([]byte(v.Name())) + buf.Write([]byte("/")) + buf.Write([]byte(v.Version())) + buf.Write([]byte(" ")) + } + return buf.String() } func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...VersionChecker) (r *Registry, err error) { @@ -599,5 +619,6 @@ func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...Versi return nil, err } r.baseVersions = baseVersions + r.baseVersionsStr = appendVersions("", baseVersions...) return r, nil } From 73e79a3310f3976b61a295f45e12aead9af41962 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Mon, 1 Jul 2013 17:57:56 -0400 Subject: [PATCH 010/101] reduce the number of string copy operations. --- registry/registry.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 0840ffbb832ce342e043712d83bb957cfb36668a..03a289010569a959699148d35e5e15a74e624134 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -116,9 +116,11 @@ func (r *Registry) setUserAgent(req *http.Request, extra ...VersionChecker) { if len(r.baseVersions)+len(extra) == 0 { return } - - userAgent := appendVersions(r.baseVersionsStr, extra...) - req.Header.Set("User-Agent", userAgent) + if len(extra) == 0 { + req.Header.Set("User-Agent", r.baseVersionsStr) + } else { + req.Header.Set("User-Agent", appendVersions(r.baseVersionsStr, extra...)) + } return } From 941e3e2ef09092306a7a287ce62b6fb518af9c56 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 11 Jul 2013 17:18:28 +0000 Subject: [PATCH 011/101] wip --- container.go | 18 ++++++++++++----- runtime.go | 4 ++-- utils/utils.go | 54 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/container.go b/container.go index 48661a3098615c6c5430fd79ba6411e0c0ea47de..e54440a5ae84fc91b18cf0d788607973234f7d27 100644 --- a/container.go +++ b/container.go @@ -617,13 +617,21 @@ func (container *Container) Start(hostConfig *HostConfig) error { container.cmd = exec.Command("lxc-start", params...) // Setup logging of stdout and stderr to disk - if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil { + /* + if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout"), ""); err != nil { return err } - if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil { + if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr"), ""); err != nil { return err } - + */ + if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { + return err + } + if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { + return err + } + var err error if container.Config.Tty { err = container.startPty() @@ -678,13 +686,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) { func (container *Container) StdoutPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() - container.stdout.AddWriter(writer) + container.stdout.AddWriter(writer, "") return utils.NewBufReader(reader), nil } func (container *Container) StderrPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() - container.stderr.AddWriter(writer) + container.stderr.AddWriter(writer, "") return utils.NewBufReader(reader), nil } diff --git a/runtime.go b/runtime.go index 5b0f7b2b2a7fc04119b25e6dc39927cc2250b9bc..d73dd16f708d5239a68ba04176eaafb5bf7d5f45 100644 --- a/runtime.go +++ b/runtime.go @@ -168,12 +168,12 @@ func (runtime *Runtime) Register(container *Container) error { return nil } -func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst string) error { +func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream string) error { log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) if err != nil { return err } - src.AddWriter(log) + src.AddWriter(log, stream) return nil } diff --git a/utils/utils.go b/utils/utils.go index df615844a78248beb2fc3ca6d8f7fd536249f0f7..3139e380a6bed1ad681e003907666d632be29de5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -247,30 +247,52 @@ func (r *bufReader) Close() error { type WriteBroadcaster struct { sync.Mutex - writers map[io.WriteCloser]struct{} + writers map[StreamWriter][]byte } -func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) { - w.Lock() - w.writers[writer] = struct{}{} - w.Unlock() +type StreamWriter struct { + wc io.WriteCloser + stream string } -// FIXME: Is that function used? -// FIXME: This relies on the concrete writer type used having equality operator -func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) { +func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) { w.Lock() - delete(w.writers, writer) + sw := StreamWriter{wc: writer, stream: stream} + w.writers[sw] = []byte{} w.Unlock() } +type JSONLog struct { + Log string `json:"log,omitempty"` + Stream string `json:"stream,omitempty"` + Created time.Time `json:"time"` +} + func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { w.Lock() defer w.Unlock() - for writer := range w.writers { - if n, err := writer.Write(p); err != nil || n != len(p) { + for sw := range w.writers { + lp := p + if sw.stream != "" { + w.writers[sw] = append(w.writers[sw], p...) + s := string(p) + if s[len(s)-1] == '\n' { + /* lp, err = json.Marshal(&JSONLog{Log: s, Stream: sw.stream, Created: time.Now()}) + if err != nil { + // On error, evict the writer + delete(w.writers, sw) + continue + } + */ + lp = []byte("[" + time.Now().String() + "] [" + sw.stream + "] " + s) + w.writers[sw] = []byte{} + } else { + continue + } + } + if n, err := sw.wc.Write(lp); err != nil || n != len(lp) { // On error, evict the writer - delete(w.writers, writer) + delete(w.writers, sw) } } return len(p), nil @@ -279,15 +301,15 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { func (w *WriteBroadcaster) CloseWriters() error { w.Lock() defer w.Unlock() - for writer := range w.writers { - writer.Close() + for sw := range w.writers { + sw.wc.Close() } - w.writers = make(map[io.WriteCloser]struct{}) + w.writers = make(map[StreamWriter][]byte) return nil } func NewWriteBroadcaster() *WriteBroadcaster { - return &WriteBroadcaster{writers: make(map[io.WriteCloser]struct{})} + return &WriteBroadcaster{writers: make(map[StreamWriter][]byte)} } func GetTotalUsedFds() int { From 080243f0407a90cdacf128dc3b53a802549d7797 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 12 Jul 2013 17:56:55 -0700 Subject: [PATCH 012/101] Hack: use helper functions in tests for less copy-pasting --- container_test.go | 117 ++++++---------------------------------------- runtime_test.go | 72 ++++------------------------ utils_test.go | 25 +++++++--- 3 files changed, 39 insertions(+), 175 deletions(-) diff --git a/container_test.go b/container_test.go index e7f6818eb6dd588fdbdcf880ba46f32a5459ce1b..bc1eaf99ec6d5d88fb32be8b2e34c4b09bf4dce8 100644 --- a/container_test.go +++ b/container_test.go @@ -39,16 +39,11 @@ func TestIDFormat(t *testing.T) { func TestMultipleAttachRestart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/sh", "-c", - "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"}, - }, + container, hostConfig, _ := mkContainer( + runtime, + []string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"}, + t, ) - if err != nil { - t.Fatal(err) - } defer runtime.Destroy(container) // Simulate 3 client attaching to the container and stop/restart @@ -65,7 +60,6 @@ func TestMultipleAttachRestart(t *testing.T) { if err != nil { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -140,19 +134,8 @@ func TestMultipleAttachRestart(t *testing.T) { func TestDiff(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - - builder := NewBuilder(runtime) - // Create a container and remove a file - container1, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/rm", "/etc/passwd"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t) defer runtime.Destroy(container1) if err := container1.Run(); err != nil { @@ -185,15 +168,7 @@ func TestDiff(t *testing.T) { } // Create a new container from the commited image - container2, err := builder.Create( - &Config{ - Image: img.ID, - Cmd: []string{"cat", "/etc/passwd"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t) defer runtime.Destroy(container2) if err := container2.Run(); err != nil { @@ -212,15 +187,7 @@ func TestDiff(t *testing.T) { } // Create a new containere - container3, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"rm", "/bin/httpd"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t) defer runtime.Destroy(container3) if err := container3.Run(); err != nil { @@ -246,17 +213,7 @@ func TestDiff(t *testing.T) { func TestCommitAutoRun(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - - builder := NewBuilder(runtime) - container1, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t) defer runtime.Destroy(container1) if container1.State.Running { @@ -279,14 +236,7 @@ func TestCommitAutoRun(t *testing.T) { } // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world - container2, err := builder.Create( - &Config{ - Image: img.ID, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t) defer runtime.Destroy(container2) stdout, err := container2.StdoutPipe() if err != nil { @@ -296,7 +246,6 @@ func TestCommitAutoRun(t *testing.T) { if err != nil { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } @@ -324,17 +273,7 @@ func TestCommitRun(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - builder := NewBuilder(runtime) - - container1, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t) defer runtime.Destroy(container1) if container1.State.Running { @@ -357,16 +296,7 @@ func TestCommitRun(t *testing.T) { } // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world - - container2, err := builder.Create( - &Config{ - Image: img.ID, - Cmd: []string{"cat", "/world"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t) defer runtime.Destroy(container2) stdout, err := container2.StdoutPipe() if err != nil { @@ -376,7 +306,6 @@ func TestCommitRun(t *testing.T) { if err != nil { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } @@ -403,18 +332,7 @@ func TestCommitRun(t *testing.T) { func TestStart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( - &Config{ - Image: GetTestImage(runtime).ID, - Memory: 33554432, - CpuShares: 1000, - Cmd: []string{"/bin/cat"}, - OpenStdin: true, - }, - ) - if err != nil { - t.Fatal(err) - } + container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t) defer runtime.Destroy(container) cStdin, err := container.StdinPipe() @@ -422,7 +340,6 @@ func TestStart(t *testing.T) { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -445,15 +362,7 @@ func TestStart(t *testing.T) { func TestRun(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container) if container.State.Running { diff --git a/runtime_test.go b/runtime_test.go index 9d43bd46e582d7c416a25f6d549f152d4e47b8a8..66d92c8100f223410e2c7f41fdda39c31f94b926 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/dotcloud/docker/utils" "io" - "io/ioutil" "log" "net" "os" @@ -247,36 +246,13 @@ func TestGet(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - builder := NewBuilder(runtime) - - container1, err := builder.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container1) - container2, err := builder.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container2) - container3, err := builder.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container3, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container3) if runtime.Get(container1.ID) != container1 { @@ -431,46 +407,14 @@ func TestAllocateUDPPortLocalhost(t *testing.T) { } func TestRestore(t *testing.T) { - - root, err := ioutil.TempDir("", "docker-test") - if err != nil { - t.Fatal(err) - } - if err := os.Remove(root); err != nil { - t.Fatal(err) - } - if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil { - t.Fatal(err) - } - - runtime1, err := NewRuntimeFromDirectory(root, false) - if err != nil { - t.Fatal(err) - } - - builder := NewBuilder(runtime1) - + runtime1 := mkRuntime(t) + defer nuke(runtime1) // Create a container with one instance of docker - container1, err := builder.Create(&Config{ - Image: GetTestImage(runtime1).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t) defer runtime1.Destroy(container1) // Create a second container meant to be killed - container2, err := builder.Create(&Config{ - Image: GetTestImage(runtime1).ID, - Cmd: []string{"/bin/cat"}, - OpenStdin: true, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t) defer runtime1.Destroy(container2) // Start the container non blocking @@ -505,7 +449,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(root, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) if err != nil { t.Fatal(err) } diff --git a/utils_test.go b/utils_test.go index 4951d3a02da608648fa1bce4725c5e53d98ae275..d1859ff8b457bb0b99df8e8847b5a6f66ca1969b 100644 --- a/utils_test.go +++ b/utils_test.go @@ -84,20 +84,28 @@ func readFile(src string, t *testing.T) (content string) { } // Create a test container from the given runtime `r` and run arguments `args`. -// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image. +// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is +// dynamically replaced by the current test image. // The caller is responsible for destroying the container. // Call t.Fatal() at the first error. -func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig) { +func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) { config, hostConfig, _, err := ParseRun(args, nil) + defer func() { + if err != nil && t != nil { + t.Fatal(err) + } + }() if err != nil { - t.Fatal(err) + return nil, nil, err + } + if config.Image == "_" { + config.Image = GetTestImage(r).ID } - config.Image = GetTestImage(r).ID c, err := NewBuilder(r).Create(config) if err != nil { - t.Fatal(err) + return nil, nil, err } - return c, hostConfig + return c, hostConfig, nil } // Create a test container, start it, wait for it to complete, destroy it, @@ -110,7 +118,10 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e t.Fatal(err) } }() - container, hostConfig := mkContainer(r, args, t) + container, hostConfig, err := mkContainer(r, args, t) + if err != nil { + return "", err + } defer r.Destroy(container) stdout, err := container.StdoutPipe() if err != nil { From 24dd50490a027f01ea086eb90663d53348fa770e Mon Sep 17 00:00:00 2001 From: Jonas Pfenniger Date: Mon, 15 Jul 2013 11:36:05 +0100 Subject: [PATCH 013/101] docker.upstart: avoid spawning a `sh` process start script / end script create an intermediate sh process. --- packaging/ubuntu/docker.upstart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packaging/ubuntu/docker.upstart b/packaging/ubuntu/docker.upstart index 2bd5565ee7b935b3ee2b1e21c4485d0389489928..1d35d7a4937970a5b14a8e1e06d35c1f8edd0d7d 100644 --- a/packaging/ubuntu/docker.upstart +++ b/packaging/ubuntu/docker.upstart @@ -4,6 +4,4 @@ start on runlevel [2345] stop on starting rc RUNLEVEL=[016] respawn -script - /usr/bin/docker -d -end script +exec /usr/bin/docker -d From 0900d3b7a6e94bfa42e3d4ac6dc6f5542f65a9b0 Mon Sep 17 00:00:00 2001 From: Jonas Pfenniger Date: Mon, 15 Jul 2013 11:41:19 +0100 Subject: [PATCH 014/101] docker.upstart: use the same start/stop events as sshd Is probably more solid --- packaging/ubuntu/docker.upstart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packaging/ubuntu/docker.upstart b/packaging/ubuntu/docker.upstart index 1d35d7a4937970a5b14a8e1e06d35c1f8edd0d7d..f4d2fbe9220e709973e3abc35b084b8173485e3e 100644 --- a/packaging/ubuntu/docker.upstart +++ b/packaging/ubuntu/docker.upstart @@ -1,7 +1,8 @@ description "Run docker" -start on runlevel [2345] -stop on starting rc RUNLEVEL=[016] +start on filesystem or runlevel [2345] +stop on runlevel [!2345] + respawn exec /usr/bin/docker -d From 599f85d4e4362f24dc2850c71a689671122c456b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 15 Jul 2013 16:17:58 +0000 Subject: [PATCH 015/101] store both logs in a same file, as JSON --- commands_test.go | 12 ++++++------ container.go | 10 +--------- server.go | 28 +++++++++++++++------------- utils/utils.go | 34 ++++++++++++++++++---------------- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/commands_test.go b/commands_test.go index 3f4c53db031b9dec9eb2328da379cab45d378fdf..233c6337d4298d1225b273c329f97ab7f9eb5df3 100644 --- a/commands_test.go +++ b/commands_test.go @@ -59,7 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } - // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname func TestRunHostname(t *testing.T) { stdout, stdoutPipe := io.Pipe() @@ -91,7 +90,6 @@ func TestRunHostname(t *testing.T) { } - // TestAttachStdin checks attaching to stdin without stdout and stderr. // 'docker run -i -a stdin' should sends the client's stdin to the command, // then detach from it and print the container id. @@ -144,15 +142,17 @@ func TestRunAttachStdin(t *testing.T) { }) // Check logs - if cmdLogs, err := container.ReadLog("stdout"); err != nil { + if cmdLogs, err := container.ReadLog("json"); err != nil { t.Fatal(err) } else { if output, err := ioutil.ReadAll(cmdLogs); err != nil { t.Fatal(err) } else { - expectedLog := "hello\nhi there\n" - if string(output) != expectedLog { - t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output) + expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""} + for _, expectedLog := range expectedLogs { + if !strings.Contains(string(output), expectedLog) { + t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output) + } } } } diff --git a/container.go b/container.go index 7b0070094a55d5ba2ff0ec2385a48893c284f337..1011f7a6e3e24076b9ec4178fac57e00a97535c5 100644 --- a/container.go +++ b/container.go @@ -640,21 +640,13 @@ func (container *Container) Start(hostConfig *HostConfig) error { container.cmd = exec.Command("lxc-start", params...) // Setup logging of stdout and stderr to disk - /* - if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout"), ""); err != nil { - return err - } - if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr"), ""); err != nil { - return err - } - */ if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err } if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { return err } - + var err error if container.Config.Tty { err = container.startPty() diff --git a/server.go b/server.go index c43cae0c3851b8c41266e82fb9570488c00fc09e..6129e3eb955c835788e08dd2e8077b226e1e6eaf 100644 --- a/server.go +++ b/server.go @@ -2,6 +2,7 @@ package docker import ( "bufio" + "encoding/json" "errors" "fmt" "github.com/dotcloud/docker/auth" @@ -1042,20 +1043,21 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std } //logs if logs { - if stdout { - cLog, err := container.ReadLog("stdout") - if err != nil { - utils.Debugf("Error reading logs (stdout): %s", err) - } else if _, err := io.Copy(out, cLog); err != nil { - utils.Debugf("Error streaming logs (stdout): %s", err) + cLog, err := container.ReadLog("json") + if err != nil { + utils.Debugf("Error reading logs (json): %s", err) + } + dec := json.NewDecoder(cLog) + for { + var l utils.JSONLog + if err := dec.Decode(&l); err == io.EOF { + break + } else if err != nil { + utils.Debugf("Error streaming logs: %s", err) + break } - } - if stderr { - cLog, err := container.ReadLog("stderr") - if err != nil { - utils.Debugf("Error reading logs (stderr): %s", err) - } else if _, err := io.Copy(out, cLog); err != nil { - utils.Debugf("Error streaming logs (stderr): %s", err) + if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) { + fmt.Fprintf(out, "%s", l.Log) } } } diff --git a/utils/utils.go b/utils/utils.go index 3ec853bf68ae5cd9c38e118562ff727a479c9e1b..9e6f0c9c0d06fc94f292918de1fa34680c93707d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -247,47 +247,49 @@ func (r *bufReader) Close() error { type WriteBroadcaster struct { sync.Mutex - writers map[StreamWriter][]byte + buf *bytes.Buffer + writers map[StreamWriter]bool } type StreamWriter struct { - wc io.WriteCloser + wc io.WriteCloser stream string } func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) { w.Lock() sw := StreamWriter{wc: writer, stream: stream} - w.writers[sw] = []byte{} + w.writers[sw] = true w.Unlock() } type JSONLog struct { - Log string `json:"log,omitempty"` - Stream string `json:"stream,omitempty"` + Log string `json:"log,omitempty"` + Stream string `json:"stream,omitempty"` Created time.Time `json:"time"` } func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { w.Lock() defer w.Unlock() + w.buf.Write(p) for sw := range w.writers { lp := p if sw.stream != "" { - w.writers[sw] = append(w.writers[sw], p...) - s := string(p) - if s[len(s)-1] == '\n' { - /* lp, err = json.Marshal(&JSONLog{Log: s, Stream: sw.stream, Created: time.Now()}) + lp = nil + for { + line, err := w.buf.ReadString('\n') + if err != nil { + w.buf.Write([]byte(line)) + break + } + b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()}) if err != nil { // On error, evict the writer delete(w.writers, sw) continue } - */ - lp = []byte("[" + time.Now().String() + "] [" + sw.stream + "] " + s) - w.writers[sw] = []byte{} - } else { - continue + lp = append(lp, b...) } } if n, err := sw.wc.Write(lp); err != nil || n != len(lp) { @@ -304,12 +306,12 @@ func (w *WriteBroadcaster) CloseWriters() error { for sw := range w.writers { sw.wc.Close() } - w.writers = make(map[StreamWriter][]byte) + w.writers = make(map[StreamWriter]bool) return nil } func NewWriteBroadcaster() *WriteBroadcaster { - return &WriteBroadcaster{writers: make(map[StreamWriter][]byte)} + return &WriteBroadcaster{writers: make(map[StreamWriter]bool), buf: bytes.NewBuffer(nil)} } func GetTotalUsedFds() int { From 92cbb7cc80a63299b5670a9fcbb2d11789200696 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 14 Jul 2013 15:26:57 -0900 Subject: [PATCH 016/101] Do not overwrite container volumes from config Fixes #819 Use same persistent volume when a container is restarted --- container.go | 51 +++++++++++++++++++++++++---------------------- container_test.go | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/container.go b/container.go index c714e9f0e183d55c6e1fca95dea66d28cad95512..53d720b7719df9e650a767fac053b3be2874ad50 100644 --- a/container.go +++ b/container.go @@ -516,8 +516,6 @@ func (container *Container) Start(hostConfig *HostConfig) error { log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") container.Config.MemorySwap = -1 } - container.Volumes = make(map[string]string) - container.VolumesRW = make(map[string]bool) // Create the requested bind mounts binds := make(map[string]BindMap) @@ -557,30 +555,35 @@ func (container *Container) Start(hostConfig *HostConfig) error { // FIXME: evaluate volumes-from before individual volumes, so that the latter can override the former. // Create the requested volumes volumes - for volPath := range container.Config.Volumes { - volPath = path.Clean(volPath) - // If an external bind is defined for this volume, use that as a source - if bindMap, exists := binds[volPath]; exists { - container.Volumes[volPath] = bindMap.SrcPath - if strings.ToLower(bindMap.Mode) == "rw" { - container.VolumesRW[volPath] = true - } - // Otherwise create an directory in $ROOT/volumes/ and use that - } else { - c, err := container.runtime.volumes.Create(nil, container, "", "", nil) - if err != nil { - return err + if container.Volumes == nil || len(container.Volumes) == 0 { + container.Volumes = make(map[string]string) + container.VolumesRW = make(map[string]bool) + + for volPath := range container.Config.Volumes { + volPath = path.Clean(volPath) + // If an external bind is defined for this volume, use that as a source + if bindMap, exists := binds[volPath]; exists { + container.Volumes[volPath] = bindMap.SrcPath + if strings.ToLower(bindMap.Mode) == "rw" { + container.VolumesRW[volPath] = true + } + // Otherwise create an directory in $ROOT/volumes/ and use that + } else { + c, err := container.runtime.volumes.Create(nil, container, "", "", nil) + if err != nil { + return err + } + srcPath, err := c.layer() + if err != nil { + return err + } + container.Volumes[volPath] = srcPath + container.VolumesRW[volPath] = true // RW by default } - srcPath, err := c.layer() - if err != nil { - return err + // Create the mountpoint + if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { + return nil } - container.Volumes[volPath] = srcPath - container.VolumesRW[volPath] = true // RW by default - } - // Create the mountpoint - if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { - return nil } } diff --git a/container_test.go b/container_test.go index f431c7dc9a944e865dac3253731e8360fcfe5879..2209bc0be024954ffeb35941aab990a69b34f156 100644 --- a/container_test.go +++ b/container_test.go @@ -1300,3 +1300,46 @@ func TestVolumesFromReadonlyMount(t *testing.T) { t.Fail() } } + +// Test that restarting a container with a volume does not create a new volume on restart. Regression test for #819. +func TestRestartWithVolumes(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"echo", "-n", "foobar"}, + Volumes: map[string]struct{}{"/test": {}}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + for key := range container.Config.Volumes { + if key != "/test" { + t.Fail() + } + } + + _, err = container.Output() + if err != nil { + t.Fatal(err) + } + + expected := container.Volumes["/test"] + if expected == "" { + t.Fail() + } + // Run the container again to verify the volume path persists + _, err = container.Output() + if err != nil { + t.Fatal(err) + } + + actual := container.Volumes["/test"] + if expected != actual { + t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual) + } +} From 6e8bfc8d12ca062dc4c311d528784c1dec27079d Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 16 Jul 2013 13:45:43 -0700 Subject: [PATCH 017/101] Testing, issue #1217: Add coverage testing into docker-ci --- testing/buildbot/master.cfg | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index 65399bb1a1648f3b0960a0f15915acf96d1abf32..61912808eceea3db677054beb20251d4cccacf6b 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -2,6 +2,7 @@ import os from buildbot.buildslave import BuildSlave from buildbot.schedulers.forcesched import ForceScheduler from buildbot.schedulers.basic import SingleBranchScheduler +from buildbot.schedulers.timed import Nightly from buildbot.changes import filter from buildbot.config import BuilderConfig from buildbot.process.factory import BuildFactory @@ -40,12 +41,16 @@ c['db'] = {'db_url':"sqlite:///state.sqlite"} c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)] c['slavePortnum'] = PORT_MASTER -c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])] -c['schedulers'].append(SingleBranchScheduler(name="all", - change_filter=filter.ChangeFilter(branch='master'),treeStableTimer=None, - builderNames=[BUILDER_NAME])) +# Schedulers +c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[BUILDER_NAME, + 'coverage'])] +c['schedulers'] += [SingleBranchScheduler(name="all", + change_filter=filter.ChangeFilter(branch='master'), treeStableTimer=None, + builderNames=[BUILDER_NAME])] +c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage'], + hour=0, minute=30)] -# Builder +# Builders factory = BuildFactory() factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; " @@ -53,6 +58,16 @@ factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, "go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))])) c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] +# Docker coverage test +coverage_cmd = ('GOPATH=`pwd` go get -d github.com/dotcloud/docker\n' + 'GOPATH=`pwd` go get github.com/axw/gocov/gocov\n' + 'sudo -E GOPATH=`pwd` ./bin/gocov test github.com/dotcloud/docker | ' + './bin/gocov report') +factory = BuildFactory() +factory.addStep(ShellCommand(description='Coverage',logEnviron=False,usePTY=True, + command=coverage_cmd)) +c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'], + factory=factory)] # Status authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]), From a926cd4d880904258e01ea521ecd9e1b908f2b97 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 13:25:47 +0000 Subject: [PATCH 018/101] add legacy support --- server.go | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/server.go b/server.go index 6129e3eb955c835788e08dd2e8077b226e1e6eaf..d275fe814bff382c6eb27dbc0ade9e679c3f843a 100644 --- a/server.go +++ b/server.go @@ -1044,20 +1044,39 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std //logs if logs { cLog, err := container.ReadLog("json") - if err != nil { - utils.Debugf("Error reading logs (json): %s", err) - } - dec := json.NewDecoder(cLog) - for { - var l utils.JSONLog - if err := dec.Decode(&l); err == io.EOF { - break - } else if err != nil { - utils.Debugf("Error streaming logs: %s", err) - break + if err != nil && os.IsNotExist(err) { + // Legacy logs + if stdout { + cLog, err := container.ReadLog("stdout") + if err != nil { + utils.Debugf("Error reading logs (stdout): %s", err) + } else if _, err := io.Copy(out, cLog); err != nil { + utils.Debugf("Error streaming logs (stdout): %s", err) + } + } + if stderr { + cLog, err := container.ReadLog("stderr") + if err != nil { + utils.Debugf("Error reading logs (stderr): %s", err) + } else if _, err := io.Copy(out, cLog); err != nil { + utils.Debugf("Error streaming logs (stderr): %s", err) + } } - if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) { - fmt.Fprintf(out, "%s", l.Log) + } else if err != nil { + utils.Debugf("Error reading logs (json): %s", err) + } else { + dec := json.NewDecoder(cLog) + for { + var l utils.JSONLog + if err := dec.Decode(&l); err == io.EOF { + break + } else if err != nil { + utils.Debugf("Error streaming logs: %s", err) + break + } + if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) { + fmt.Fprintf(out, "%s", l.Log) + } } } } From 1b0fd7ead33722d8782634d54cbd797f284aa085 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 13:29:40 +0000 Subject: [PATCH 019/101] add debug and simplify docker logs --- commands.go | 5 +---- server.go | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index b581590bc2d8afdb2817085c77bfd547f901a0d8..def2ff72d7d21eafe8e527c23973febda440e2ab 100644 --- a/commands.go +++ b/commands.go @@ -1099,10 +1099,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return nil } - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, cli.out); err != nil { - return err - } - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, cli.err); err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil { return err } return nil diff --git a/server.go b/server.go index d275fe814bff382c6eb27dbc0ade9e679c3f843a..958dc75663478f4b20a14ab22f722c9b2a86f7bd 100644 --- a/server.go +++ b/server.go @@ -1046,6 +1046,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std cLog, err := container.ReadLog("json") if err != nil && os.IsNotExist(err) { // Legacy logs + utils.Debugf("Old logs format") if stdout { cLog, err := container.ReadLog("stdout") if err != nil { From cd209f406e889feaba50103e5ce50f7dcd23767a Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Thu, 18 Jul 2013 14:22:49 -0400 Subject: [PATCH 020/101] documentation. --- registry/registry.go | 8 ++++++++ server.go | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/registry/registry.go b/registry/registry.go index 03a289010569a959699148d35e5e15a74e624134..6ba80cbea53db9629be37f40cb8e51ce101ae32d 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -112,6 +112,8 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { return c.Do(req) } +// Set the user agent field in the header based on the versions provided +// in NewRegistry() and extra. func (r *Registry) setUserAgent(req *http.Request, extra ...VersionChecker) { if len(r.baseVersions)+len(extra) == 0 { return @@ -582,6 +584,12 @@ func validVersion(version VersionChecker) bool { return true } +// Convert versions to a string and append the string to the string base. +// +// Each VersionChecker will be converted to a string in the format of +// "product/version", where the "product" is get from the Name() method, while +// version is get from the Version() method. Several pieces of verson information +// will be concatinated and separated by space. func appendVersions(base string, versions ...VersionChecker) string { if len(versions) == 0 { return base diff --git a/server.go b/server.go index 70bb0bb87118e4cab4f54b605a518d76561b7293..925e4e3386708fef9f4a203795e8650de721c10b 100644 --- a/server.go +++ b/server.go @@ -26,6 +26,11 @@ func (srv *Server) DockerVersion() APIVersion { } } +// plainVersionChecker is a simple implementation of +// the interface VersionChecker, which is used +// to provide version information for some product, +// component, etc. It stores the product name and the version +// in string and returns them on calls to Name() and Version(). type plainVersionChecker struct { name string version string @@ -39,6 +44,10 @@ func (v *plainVersionChecker) Version() string { return v.version } +// versionCheckers() returns version informations of: +// docker, go, git-commit (of the docker) and the host's kernel. +// +// Such information will be used on call to NewRegistry(). func (srv *Server) versionCheckers() []registry.VersionChecker { v := srv.DockerVersion() ret := make([]registry.VersionChecker, 0, 4) From 54f9cdb0c30ef7d192c4ae59c669a7a5fd7d1003 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 18 Jul 2013 19:04:51 -0700 Subject: [PATCH 021/101] Make docs build without warnings or errors. Minor additional cleanup. --- docs/sources/api/docker_remote_api.rst | 61 +++++++++++++++------ docs/sources/api/docker_remote_api_v1.0.rst | 9 ++- docs/sources/api/docker_remote_api_v1.1.rst | 4 ++ docs/sources/api/docker_remote_api_v1.2.rst | 5 ++ docs/sources/api/docker_remote_api_v1.3.rst | 5 ++ docs/sources/api/index_api.rst | 2 +- docs/sources/api/registry_index_spec.rst | 5 +- docs/sources/index.rst | 2 - docs/sources/use/builder.rst | 8 +-- docs/sources/use/workingwithrepository.rst | 2 +- 10 files changed, 73 insertions(+), 30 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 183347c23b17f1759fdff24b4d48b8c1caf0264e..f8a1381c534f02d548dbc2772af8b216deb1304f 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -2,6 +2,9 @@ :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation +.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to +.. document the REST API. + ================= Docker Remote API ================= @@ -13,15 +16,23 @@ Docker Remote API - 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 -- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push +- 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 +- Since API version 1.2, the auth configuration is now handled client + side, so the client has to send the authConfig as POST in + /images/(name)/push 2. Versions =========== -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 +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` ***************************** @@ -29,19 +40,21 @@ You can still call an old version of the api using /v1.0/images//insert What's new ---------- -Listing processes (/top): - -- List the processes inside a container +.. http:get:: /containers/(id)/top + **New!** List the processes running inside a container. Builder (/build): - Simplify the upload of the build context -- Simply stream a tarball instead of multipart upload with 4 intermediary buffers +- 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. +.. Warning:: + + The /build improvements are not reverse-compatible. Pre 1.3 clients + will break on /build. List containers (/containers/json): @@ -49,7 +62,8 @@ List containers (/containers/json): Start containers (/containers//start): -- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls +- You can now pass host-specific configuration (e.g. bind mounts) in + the POST body for start calls :doc:`docker_remote_api_v1.2` ***************************** @@ -60,14 +74,25 @@ What's new ---------- The auth configuration is now handled by the client. -The client should send it's authConfig as POST on each call of /images/(name)/push -.. http:get:: /auth is now deprecated -.. http:post:: /auth only checks the configuration but doesn't store it on the server +The client should send it's authConfig as POST on each call of +/images/(name)/push + +.. http:get:: /auth + + **Deprecated.** + +.. 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 chidren and remove all the untagged parents if has any. -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 -.. http:post:: /images//delete now returns a JSON with the list of images deleted/untagged + Now returns a JSON structure with the list of images + deleted/untagged. :doc:`docker_remote_api_v1.1` @@ -82,7 +107,7 @@ What's new .. http:post:: /images/(name)/insert .. http:post:: /images/(name)/push -Uses json stream instead of HTML hijack, it looks like this: + Uses json stream instead of HTML hijack, it looks like this: .. sourcecode:: http diff --git a/docs/sources/api/docker_remote_api_v1.0.rst b/docs/sources/api/docker_remote_api_v1.0.rst index a78933709365e2ff2e2f4e612cd3103684fb2ed7..5aa98cbe5995585e2bbfefe959eea4605868c848 100644 --- a/docs/sources/api/docker_remote_api_v1.0.rst +++ b/docs/sources/api/docker_remote_api_v1.0.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.0 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation @@ -300,8 +305,8 @@ Start a container :statuscode 500: server error -Stop a contaier -*************** +Stop a container +**************** .. http:post:: /containers/(id)/stop diff --git a/docs/sources/api/docker_remote_api_v1.1.rst b/docs/sources/api/docker_remote_api_v1.1.rst index 3e0ef34eba6878abbcda3793bd8c22ea5c7189af..e0159ddb6551273d93deee61a4b63938b5a6ead5 100644 --- a/docs/sources/api/docker_remote_api_v1.1.rst +++ b/docs/sources/api/docker_remote_api_v1.1.rst @@ -1,3 +1,7 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: :title: Remote API v1.1 :description: API Documentation for Docker diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index a6c2c319201e83e772312baaa33e2a9bc3448be3..96ee6bb9bb7c6096c3478b418ef168d1f03f10bf 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.2 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 9f33365e8192bb8e42985e0c059d93a57e16e6ef..273ec2e98d267e8402e54f52a27c79c3998d6635 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.3 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation diff --git a/docs/sources/api/index_api.rst b/docs/sources/api/index_api.rst index 42dc49a5d7396b5dcfdf5da1ab0b77bb8bc5dcbd..1d4f475caf6e361e685d3e6ca5168fafa09a11fa 100644 --- a/docs/sources/api/index_api.rst +++ b/docs/sources/api/index_api.rst @@ -452,7 +452,7 @@ User Register "username": "foobar"'} :jsonparameter email: valid email address, that needs to be confirmed - :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. + :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9\_]. :jsonparameter password: min 5 characters **Example Response**: diff --git a/docs/sources/api/registry_index_spec.rst b/docs/sources/api/registry_index_spec.rst index c1854194b2fe9797318935cd19f561adfe170636..3ae39e37d9d456f57b39a52a87455f045675ac02 100644 --- a/docs/sources/api/registry_index_spec.rst +++ b/docs/sources/api/registry_index_spec.rst @@ -367,7 +367,8 @@ POST /v1/users {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'} **Validation**: - - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. + - **username**: min 4 character, max 30 characters, must match the regular + expression [a-z0-9\_]. - **password**: min 5 characters **Valid**: return HTTP 200 @@ -566,4 +567,4 @@ Next request:: --------------------- - 1.0 : May 6th 2013 : initial release -- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. \ No newline at end of file +- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. diff --git a/docs/sources/index.rst b/docs/sources/index.rst index 05e69dd8e54971031e76de22eac37fa849ca53dc..ba8f60c3fa64ea595bb939b230dda352668e451f 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -2,8 +2,6 @@ :description: An overview of the Docker Documentation :keywords: containers, lxc, concepts, explanation -.. _introduction: - Welcome ======= diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 9ea8033b9877eb27eab0c1f29c2d2bda49e9f8ac..7f370609c87aac4be89e30800a6e121a33834b64 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -1,6 +1,6 @@ -:title: Dockerfile Builder -:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image. -:keywords: builder, docker, Docker Builder, automation, image creation +:title: Dockerfiles for Images +:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. +:keywords: builder, docker, Dockerfile, automation, image creation ================== Dockerfile Builder @@ -177,7 +177,7 @@ The copy obeys the following rules: with mode 0700, uid and gid 0. 3.8 ENTRYPOINT -------------- +-------------- ``ENTRYPOINT /bin/echo`` diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst index 3cdbfe49d6f7b540891e21e4daca5a865dffe663..4a2e39aea1c670b57af5d0e65d93d7e0cd25f285 100644 --- a/docs/sources/use/workingwithrepository.rst +++ b/docs/sources/use/workingwithrepository.rst @@ -119,7 +119,7 @@ your container to an image within your username namespace. Pushing a container to its repository ------------------------------------- +------------------------------------- In order to push an image to its repository you need to have committed your container to a named image (see above) From a0eec14c7da5b213302c2675801aaf788e84efed Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 02:47:35 +0000 Subject: [PATCH 022/101] fix overwrites EXPOSE --- utils.go | 2 +- utils_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/utils.go b/utils.go index 33cfbe506fcbf92da7e4d679db94316301b51fc8..16efccd3875e9d6f0596dfcbf27c2f0c68b5c08a 100644 --- a/utils.go +++ b/utils.go @@ -78,7 +78,7 @@ func MergeConfig(userConf, imageConf *Config) { imageNat, _ := parseNat(imagePortSpec) for _, userPortSpec := range userConf.PortSpecs { userNat, _ := parseNat(userPortSpec) - if imageNat.Proto == userNat.Proto && imageNat.Frontend == userNat.Frontend { + if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend { found = true } } diff --git a/utils_test.go b/utils_test.go index 1bd18f4af0cbf8181baeced50f34da6eb66ad25b..5ea3682bac698fde76efe333b237c767d29b72a6 100644 --- a/utils_test.go +++ b/utils_test.go @@ -148,7 +148,7 @@ func TestMergeConfig(t *testing.T) { volumesUser["/test3"] = struct{}{} configUser := &Config{ Dns: []string{"3.3.3.3"}, - PortSpecs: []string{"2222:3333", "3333:3333"}, + PortSpecs: []string{"3333:2222", "3333:3333"}, Env: []string{"VAR2=3", "VAR3=3"}, Volumes: volumesUser, } @@ -165,11 +165,11 @@ func TestMergeConfig(t *testing.T) { } if len(configUser.PortSpecs) != 3 { - t.Fatalf("Expected 3 portSpecs, 1111:1111, 2222:3333 and 3333:3333, found %d", len(configUser.PortSpecs)) + t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs)) } for _, portSpecs := range configUser.PortSpecs { - if portSpecs != "1111:1111" && portSpecs != "2222:3333" && portSpecs != "3333:3333" { - t.Fatalf("Expected 1111:1111 or 2222:3333 or 3333:3333, found %s", portSpecs) + if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" { + t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs) } } if len(configUser.Env) != 3 { From 2b5386f039b5c99cf0f64fb3091cc14e4446dc64 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 03:01:39 +0000 Subject: [PATCH 023/101] add regression test from @crosbymichael --- utils_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/utils_test.go b/utils_test.go index 5ea3682bac698fde76efe333b237c767d29b72a6..78f3afa66c683a9c9670586d25b2761bc05cce30 100644 --- a/utils_test.go +++ b/utils_test.go @@ -190,3 +190,45 @@ func TestMergeConfig(t *testing.T) { } } } + +func TestMergeConfigPublicPortNotHonored(t *testing.T) { + volumesImage := make(map[string]struct{}) + volumesImage["/test1"] = struct{}{} + volumesImage["/test2"] = struct{}{} + configImage := &Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"1111", "2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + Volumes: volumesImage, + } + + volumesUser := make(map[string]struct{}) + volumesUser["/test3"] = struct{}{} + configUser := &Config{ + Dns: []string{"3.3.3.3"}, + PortSpecs: []string{"1111:3333"}, + Env: []string{"VAR2=3", "VAR3=3"}, + Volumes: volumesUser, + } + + MergeConfig(configUser, configImage) + + contains := func(a []string, expect string) bool { + for _, p := range a { + if p == expect { + return true + } + } + return false + } + + if !contains(configUser.PortSpecs, "2222") { + t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs) + t.Fail() + } + + if !contains(configUser.PortSpecs, "1111:3333") { + t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs) + t.Fail() + } +} From cfec1c3e1b88ceeca73144198df7a210ed3dc421 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 10:06:32 +0000 Subject: [PATCH 024/101] add ps args to docker top --- api.go | 6 +++++- api_params.go | 6 ++---- api_test.go | 27 ++++++++++++++++++--------- commands.go | 17 +++++++++++------ server.go | 31 +++++++++++++++---------------- 5 files changed, 51 insertions(+), 36 deletions(-) diff --git a/api.go b/api.go index b6ab7badfa1ceaf1a615eccffbd520c0e3da3276..d3b84df5f9bfaed180a5ab33618004bd838ccd5d 100644 --- a/api.go +++ b/api.go @@ -255,8 +255,12 @@ func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *ht if vars == nil { return fmt.Errorf("Missing parameter") } + if err := parseForm(r); err != nil { + return err + } name := vars["name"] - procsStr, err := srv.ContainerTop(name) + ps_args := r.Form.Get("ps_args") + procsStr, err := srv.ContainerTop(name, ps_args) if err != nil { return err } diff --git a/api_params.go b/api_params.go index b371ca314f4fd68ca15b75c5030057c8dbe40822..2296ee792ea2ce2a783b4e84da216e1ca58a5ea4 100644 --- a/api_params.go +++ b/api_params.go @@ -27,10 +27,8 @@ type APIInfo struct { } type APITop struct { - PID string - Tty string - Time string - Cmd string + Titles []string + Processes [][]string } type APIRmi struct { diff --git a/api_test.go b/api_test.go index 17ada96eab3e486baf97ec45e1026ef73a805576..9b7f08d1db92560813556062991a68dbcb53f1b7 100644 --- a/api_test.go +++ b/api_test.go @@ -444,24 +444,33 @@ func TestGetContainersTop(t *testing.T) { } r := httptest.NewRecorder() - if err := getContainersTop(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { + req, err := http.NewRequest("GET", "/"+container.ID+"/top?ps_args=u", bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + if err := getContainersTop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } - procs := []APITop{} + procs := APITop{} if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil { t.Fatal(err) } - if len(procs) != 2 { - t.Fatalf("Expected 2 processes, found %d.", len(procs)) + if len(procs.Titles) != 11 { + t.Fatalf("Expected 11 titles, found %d.", len(procs.Titles)) } - - if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" { - t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd) + if procs.Titles[0] != "USER" || procs.Titles[10] != "COMMAND" { + t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.Titles[0], procs.Titles[10]) } - if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" { - t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd) + if len(procs.Processes) != 2 { + t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes)) + } + if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "sleep" { + t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[0][10]) + } + if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "sleep" { + t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[1][10]) } } diff --git a/commands.go b/commands.go index 936b23fea277b26c90514e4bef6fe71b0608be35..b25e928efa23b66d7e501c2121562179f79b2965 100644 --- a/commands.go +++ b/commands.go @@ -585,23 +585,28 @@ func (cli *DockerCli) CmdTop(args ...string) error { if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 1 { + if cmd.NArg() == 0 { cmd.Usage() return nil } - body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top", nil) + val := url.Values{} + if cmd.NArg() > 1 { + val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) + } + + body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil) if err != nil { return err } - var procs []APITop + procs := APITop{} err = json.Unmarshal(body, &procs) if err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, "PID\tTTY\tTIME\tCMD") - for _, proc := range procs { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", proc.PID, proc.Tty, proc.Time, proc.Cmd) + fmt.Fprintln(w, strings.Join(procs.Titles, "\t")) + for _, proc := range procs.Processes { + fmt.Fprintln(w, strings.Join(proc, "\t")) } w.Flush() return nil diff --git a/server.go b/server.go index 954bbb208f2064d936c50c381a2f088e7cdf562f..ae5f605267c931321e2a6f93462824d2aeab58c3 100644 --- a/server.go +++ b/server.go @@ -249,35 +249,34 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) { } -func (srv *Server) ContainerTop(name string) ([]APITop, error) { +func (srv *Server) ContainerTop(name, ps_args string) (*APITop, error) { if container := srv.runtime.Get(name); container != nil { - output, err := exec.Command("lxc-ps", "--name", container.ID).CombinedOutput() + output, err := exec.Command("lxc-ps", "--name", container.ID, "--", ps_args).CombinedOutput() if err != nil { return nil, fmt.Errorf("Error trying to use lxc-ps: %s (%s)", err, output) } - var procs []APITop + procs := APITop{} for i, line := range strings.Split(string(output), "\n") { - if i == 0 || len(line) == 0 { + if len(line) == 0 { continue } - proc := APITop{} + words := []string{} scanner := bufio.NewScanner(strings.NewReader(line)) scanner.Split(bufio.ScanWords) if !scanner.Scan() { return nil, fmt.Errorf("Error trying to use lxc-ps") } // no scanner.Text because we skip container id - scanner.Scan() - proc.PID = scanner.Text() - scanner.Scan() - proc.Tty = scanner.Text() - scanner.Scan() - proc.Time = scanner.Text() - scanner.Scan() - proc.Cmd = scanner.Text() - procs = append(procs, proc) - } - return procs, nil + for scanner.Scan() { + words = append(words, scanner.Text()) + } + if i == 0 { + procs.Titles = words + } else { + procs.Processes = append(procs.Processes, words) + } + } + return &procs, nil } return nil, fmt.Errorf("No such container: %s", name) From eb4a0271fbb7318a7d66911ccd388c048db0c2fa Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 10:34:55 +0000 Subject: [PATCH 025/101] bump api version to 1.4 --- api.go | 5 +- docs/sources/api/docker_remote_api.rst | 17 +- docs/sources/api/docker_remote_api_v1.4.rst | 1093 +++++++++++++++++++ 3 files changed, 1112 insertions(+), 3 deletions(-) create mode 100644 docs/sources/api/docker_remote_api_v1.4.rst diff --git a/api.go b/api.go index d3b84df5f9bfaed180a5ab33618004bd838ccd5d..a3dd52108e8ea805138d04901e7fbdc4af4aac34 100644 --- a/api.go +++ b/api.go @@ -17,7 +17,7 @@ import ( "strings" ) -const APIVERSION = 1.3 +const APIVERSION = 1.4 const DEFAULTHTTPHOST string = "127.0.0.1" const DEFAULTHTTPPORT int = 4243 @@ -252,6 +252,9 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r } func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if version < 1.4 { + return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") + } if vars == nil { return fmt.Errorf("Missing parameter") } diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 183347c23b17f1759fdff24b4d48b8c1caf0264e..a08fb4694038e7427829bcdd9308eafecbb30dbc 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -19,8 +19,8 @@ Docker Remote API 2. Versions =========== -The current verson of the API is 1.3 -Calling /images//insert is the same as calling /v1.3/images//insert +The current verson of the API is 1.4 +Calling /images//insert is the same as calling /v1.4/images//insert You can still call an old version of the api using /v1.0/images//insert :doc:`docker_remote_api_v1.3` @@ -31,6 +31,18 @@ What's new Listing processes (/top): +- You can now use ps args with docker top, like `docker top aux` + +:doc:`docker_remote_api_v1.3` +***************************** + +docker v0.5.0 51f6c4a_ + +What's new +---------- + +Listing processes (/top): + - List the processes inside a container @@ -109,6 +121,7 @@ Initial version .. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f .. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4 .. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168 +.. _51f6c4a: https://github.com/dotcloud/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909 ================================== Docker Remote API Client Libraries diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst new file mode 100644 index 0000000000000000000000000000000000000000..c42adb286fc34ea1e6c5cb20f838b3cad9687d12 --- /dev/null +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -0,0 +1,1093 @@ +:title: Remote API v1.3 +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +====================== +Docker Remote API v1.3 +====================== + +.. 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&size=1 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. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :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 + + +List processes running inside a container +***************************************** + +.. http:get:: /containers/(id)/top + + List processes running inside the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + + :query ps_args: ps arguments to use (eg. aux) + :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/(id)/start HTTP/1.1 + Content-Type: application/json + + { + "Binds":["/tmp:/tmp"] + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain + + :jsonparam hostConfig: the container's host configuration (optional) + :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 200: 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 + :query q: suppress verbose build output + :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 64e74cefb746caa7f2a581149bbd523dd1ac9215 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 8 Jul 2013 11:01:16 +0300 Subject: [PATCH 026/101] add support for container ID files (a la pidfile) --- commands.go | 14 ++++++++++++++ container.go | 7 +++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 936b23fea277b26c90514e4bef6fe71b0608be35..42cccf6b969d5cdca17cbc9c86b6b78015e8e2c4 100644 --- a/commands.go +++ b/commands.go @@ -1341,6 +1341,20 @@ func (cli *DockerCli) CmdRun(args ...string) error { for _, warning := range runResult.Warnings { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } + if len(hostConfig.ContainerIDFile) > 0 { + if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil { + return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile) + } + file, err := os.Create(hostConfig.ContainerIDFile) + if err != nil { + return fmt.Errorf("failed to create the container ID file: %s", err) + } + + defer file.Close() + if _, err = file.WriteString(runResult.ID); err != nil { + return fmt.Errorf("failed to write the container ID to the file: %s", err) + } + } //start the container if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil { diff --git a/container.go b/container.go index 95c5ba0f720411e70729f525ac45b4b716be8668..4443ad52a384a534e603f104116e243023411552 100644 --- a/container.go +++ b/container.go @@ -80,7 +80,8 @@ type Config struct { } type HostConfig struct { - Binds []string + Binds []string + ContainerIDFile string } type BindMap struct { @@ -103,6 +104,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached") flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") + flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") @@ -190,7 +192,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Entrypoint: entrypoint, } hostConfig := &HostConfig{ - Binds: binds, + Binds: binds, + ContainerIDFile: *flContainerIDFile, } if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { From 221ee504aa06d06eb868898cca2fcc020a861e84 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 11 Jul 2013 23:38:43 +0300 Subject: [PATCH 027/101] docs - add cidfile flag to run docs --- docs/sources/commandline/command/run.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index 1a14a9b616fb972fe2dc13081dad892071180c51..bfd35738fac2205148d5d4b01c21fee055830ced 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -14,6 +14,7 @@ -a=map[]: Attach to stdin, stdout or stderr. -c=0: CPU shares (relative weight) + -cidfile="": Write the container ID to the file -d=false: Detached mode: leave the container running in the background -e=[]: Set environment variables -h="": Container host name From 2a3b91e3b66c48c6a26dbd673957a46c1afacbbe Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 12 Jul 2013 00:50:03 +0300 Subject: [PATCH 028/101] docs - add example for cidfile --- docs/sources/commandline/command/run.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index bfd35738fac2205148d5d4b01c21fee055830ced..19efd858215ab4e87f2d4cf83c959c39de830ba0 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -27,3 +27,13 @@ -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume. -volumes-from="": Mount all volumes from the given container. -entrypoint="": Overwrite the default entrypoint set by the image. + + +Examples +-------- + +.. code-block:: bash + + docker run -cidfile /tmp/docker_test.cid ubuntu echo "test" + +| This will create a container and print "test" to the console. The cidfile flag makes docker attempt to create a new file and write the container ID to it. If the file exists already, docker will return an error. Docker will close this file when docker run exits. From 25be79208a1473a65be883989ae49b7c71081a83 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 12 Jul 2013 01:11:44 +0300 Subject: [PATCH 029/101] create the cidfile before creating the container This change makes docker attempt to create the container ID file and open it before attempting to create the container. This avoids leaving a stale container behind if docker has failed to create and open the container ID file. The container ID is written to the file after the container is created. --- commands.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/commands.go b/commands.go index 42cccf6b969d5cdca17cbc9c86b6b78015e8e2c4..7f8f9eec0bba66d2747b85f79ae3bf69bb0203c5 100644 --- a/commands.go +++ b/commands.go @@ -1311,6 +1311,18 @@ func (cli *DockerCli) CmdRun(args ...string) error { return nil } + var containerIDFile *os.File + if len(hostConfig.ContainerIDFile) > 0 { + if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil { + return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile) + } + containerIDFile, err = os.Create(hostConfig.ContainerIDFile) + if err != nil { + return fmt.Errorf("failed to create the container ID file: %s", err) + } + defer containerIDFile.Close() + } + //create the container body, statusCode, err := cli.call("POST", "/containers/create", config) //if image not found try to pull it @@ -1342,16 +1354,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } if len(hostConfig.ContainerIDFile) > 0 { - if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil { - return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile) - } - file, err := os.Create(hostConfig.ContainerIDFile) - if err != nil { - return fmt.Errorf("failed to create the container ID file: %s", err) - } - - defer file.Close() - if _, err = file.WriteString(runResult.ID); err != nil { + if _, err = containerIDFile.WriteString(runResult.ID); err != nil { return fmt.Errorf("failed to write the container ID to the file: %s", err) } } From 2e3b660dd0d49dc78f4c486e952ea6db9c007d6a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 13:56:36 +0000 Subject: [PATCH 030/101] fix error in utils tests --- utils/utils_test.go | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/utils/utils_test.go b/utils/utils_test.go index 2c68be50e3e58e32a9f021cf7878977076c2e82e..5caa809f677f807f5ea80c5a25d1dbebfd62d213 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -60,9 +60,9 @@ func TestWriteBroadcaster(t *testing.T) { // Test 1: Both bufferA and bufferB should contain "foo" bufferA := &dummyWriter{} - writer.AddWriter(bufferA) + writer.AddWriter(bufferA, "") bufferB := &dummyWriter{} - writer.AddWriter(bufferB) + writer.AddWriter(bufferB, "") writer.Write([]byte("foo")) if bufferA.String() != "foo" { @@ -76,7 +76,7 @@ func TestWriteBroadcaster(t *testing.T) { // Test2: bufferA and bufferB should contain "foobar", // while bufferC should only contain "bar" bufferC := &dummyWriter{} - writer.AddWriter(bufferC) + writer.AddWriter(bufferC, "") writer.Write([]byte("bar")) if bufferA.String() != "foobar" { @@ -91,35 +91,22 @@ func TestWriteBroadcaster(t *testing.T) { t.Errorf("Buffer contains %v", bufferC.String()) } - // Test3: Test removal - writer.RemoveWriter(bufferB) - writer.Write([]byte("42")) - if bufferA.String() != "foobar42" { - t.Errorf("Buffer contains %v", bufferA.String()) - } - if bufferB.String() != "foobar" { - t.Errorf("Buffer contains %v", bufferB.String()) - } - if bufferC.String() != "bar42" { - t.Errorf("Buffer contains %v", bufferC.String()) - } - - // Test4: Test eviction on failure + // Test3: Test eviction on failure bufferA.failOnWrite = true writer.Write([]byte("fail")) - if bufferA.String() != "foobar42" { + if bufferA.String() != "foobar" { t.Errorf("Buffer contains %v", bufferA.String()) } - if bufferC.String() != "bar42fail" { + if bufferC.String() != "barfail" { t.Errorf("Buffer contains %v", bufferC.String()) } // Even though we reset the flag, no more writes should go in there bufferA.failOnWrite = false writer.Write([]byte("test")) - if bufferA.String() != "foobar42" { + if bufferA.String() != "foobar" { t.Errorf("Buffer contains %v", bufferA.String()) } - if bufferC.String() != "bar42failtest" { + if bufferC.String() != "barfailtest" { t.Errorf("Buffer contains %v", bufferC.String()) } @@ -141,7 +128,7 @@ func TestRaceWriteBroadcaster(t *testing.T) { writer := NewWriteBroadcaster() c := make(chan bool) go func() { - writer.AddWriter(devNullCloser(0)) + writer.AddWriter(devNullCloser(0), "") c <- true }() writer.Write([]byte("hello")) From e8ad82f9ba126414e1813fadfb17167a34afa8d4 Mon Sep 17 00:00:00 2001 From: Ryan Fowler Date: Fri, 19 Jul 2013 10:11:21 -0500 Subject: [PATCH 031/101] Make the ENTRYPOINT example work The incantation listed in the ENTRYPOINT example didn't actually pass the arguments to your script. Changing the definition to an array fixes this. --- docs/sources/use/builder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 9ea8033b9877eb27eab0c1f29c2d2bda49e9f8ac..3f7a8e8ef191538beab19d84c5cfde7551fbedd5 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -179,7 +179,7 @@ The copy obeys the following rules: 3.8 ENTRYPOINT ------------- - ``ENTRYPOINT /bin/echo`` + ``ENTRYPOINT ["/bin/echo"]`` The ``ENTRYPOINT`` instruction adds an entry command that will not be overwritten when arguments are passed to docker run, unlike the From ea1258852493a83754553163db1db52a72ffb8fc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 15:56:00 +0000 Subject: [PATCH 032/101] remove usage from tests --- container.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container.go b/container.go index d0a0dd7714505ad77527b9ded6adcf0b80a6e60f..f18aa0fe742d5f8af60360f00916c7b686431c2c 100644 --- a/container.go +++ b/container.go @@ -94,6 +94,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, 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) + cmd.Usage = nil } flHostname := cmd.String("h", "", "Container host name") From 921c6994b1ad41c940bdb08732225b8db74b68f2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 16:36:23 +0000 Subject: [PATCH 033/101] add LXC version to docker info in debug mode --- api_params.go | 9 +++++---- commands.go | 1 + server.go | 9 +++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/api_params.go b/api_params.go index b371ca314f4fd68ca15b75c5030057c8dbe40822..967b736bdcbbfd43dc97ecfcd6b3bdf5ea73c1fd 100644 --- a/api_params.go +++ b/api_params.go @@ -20,10 +20,11 @@ type APIInfo struct { Debug bool Containers int Images int - NFd int `json:",omitempty"` - NGoroutines int `json:",omitempty"` - MemoryLimit bool `json:",omitempty"` - SwapLimit bool `json:",omitempty"` + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` + MemoryLimit bool `json:",omitempty"` + SwapLimit bool `json:",omitempty"` + LXCVersion string `json:",omitempty"` } type APITop struct { diff --git a/commands.go b/commands.go index f0e1695b3f62d426949b02f3444720ed14a524ea..e2b26321f5567bb64bb215b564b28403061172b9 100644 --- a/commands.go +++ b/commands.go @@ -463,6 +463,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd) fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines) + fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion) } if !out.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") diff --git a/server.go b/server.go index b92ed8fd7340fd95c5e88c56a980500f94a55dcd..90d56f74a0954580c5077a0919138b0d5013c1ea 100644 --- a/server.go +++ b/server.go @@ -208,6 +208,14 @@ func (srv *Server) DockerInfo() *APIInfo { } else { imgcount = len(images) } + lxcVersion := "" + if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil { + outputStr := string(output) + if len(strings.SplitN(outputStr, ":", 2)) == 2 { + lxcVersion = strings.TrimSpace(strings.SplitN(string(output), ":", 2)[1]) + } + } + return &APIInfo{ Containers: len(srv.runtime.List()), Images: imgcount, @@ -216,6 +224,7 @@ func (srv *Server) DockerInfo() *APIInfo { Debug: os.Getenv("DEBUG") != "", NFd: utils.GetTotalUsedFds(), NGoroutines: runtime.NumGoroutine(), + LXCVersion: lxcVersion, } } From 67f1e3f5ed4d3061e07e910e28ac866b7bb13e18 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 17:22:16 +0000 Subject: [PATCH 034/101] add container=lxc in default env --- container.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container.go b/container.go index d0a0dd7714505ad77527b9ded6adcf0b80a6e60f..f472b199ea5af8df5f53425f7f93f088db3d01bc 100644 --- a/container.go +++ b/container.go @@ -640,6 +640,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "-e", "HOME=/", "-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "-e", "container=lxc", ) for _, elem := range container.Config.Env { From 32663bf431b64d1169509bacba36e3c90e131b44 Mon Sep 17 00:00:00 2001 From: dsissitka Date: Sat, 20 Jul 2013 21:27:55 -0400 Subject: [PATCH 035/101] Fixed a couple of minor syntax errors. --- docs/sources/contributing/devenvironment.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/contributing/devenvironment.rst b/docs/sources/contributing/devenvironment.rst index 6f5d6c1dc1c11ae20d5d1fb08a32766ba214aa0b..869cc43749f3a0684035fd8ea41fc8aa9c70a134 100644 --- a/docs/sources/contributing/devenvironment.rst +++ b/docs/sources/contributing/devenvironment.rst @@ -46,11 +46,13 @@ in a standard build environment. You can run an interactive session in the newly built container: :: + docker run -i -t docker bash To extract the binaries from the container: :: + docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build From 788935175e8500b451a844f6971ac62dd099bdfc Mon Sep 17 00:00:00 2001 From: dsissitka Date: Sun, 21 Jul 2013 18:30:51 -0400 Subject: [PATCH 036/101] Added top to the list of commands in the sidebar. --- docs/sources/commandline/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/commandline/index.rst b/docs/sources/commandline/index.rst index f1a3e2da454d96c167c1e6dcff17d45a801c0ed1..a7296b27da2933b5f20d5055d88c907c822c0089 100644 --- a/docs/sources/commandline/index.rst +++ b/docs/sources/commandline/index.rst @@ -37,5 +37,6 @@ Contents: start stop tag + top version - wait \ No newline at end of file + wait From 1d02a7ffb63915055f5fd9bda420bd08a8679da1 Mon Sep 17 00:00:00 2001 From: David Sissitka Date: Sun, 21 Jul 2013 19:00:18 -0400 Subject: [PATCH 037/101] Updated the stop command's docs. --- commands.go | 2 +- docs/sources/commandline/command/stop.rst | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index f0e1695b3f62d426949b02f3444720ed14a524ea..86385f41876ca95f24ea19a32df3e4e1a1a374aa 100644 --- a/commands.go +++ b/commands.go @@ -475,7 +475,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { func (cli *DockerCli) CmdStop(args ...string) error { cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container") - nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10") + nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.") if err := cmd.Parse(args); err != nil { return nil } diff --git a/docs/sources/commandline/command/stop.rst b/docs/sources/commandline/command/stop.rst index 3d571563ec145d30d7e97687849ce83f778be021..6a64908eae91753f621c0b29b1aa3c5213f261be 100644 --- a/docs/sources/commandline/command/stop.rst +++ b/docs/sources/commandline/command/stop.rst @@ -8,6 +8,8 @@ :: - Usage: docker stop [OPTIONS] NAME + Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] Stop a running container + + -t=10: Number of seconds to wait for the container to stop before killing it. From 3342bdb33184b83cac66921807c5403168d13f6b Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 17:11:47 -0700 Subject: [PATCH 038/101] Support networkless containers with new docker run option '-n' --- container.go | 86 ++++++++++++++---------- container_test.go | 38 +++++++++++ docs/sources/commandline/command/run.rst | 1 + lxc_template.go | 5 ++ 4 files changed, 93 insertions(+), 37 deletions(-) diff --git a/container.go b/container.go index f18aa0fe742d5f8af60360f00916c7b686431c2c..6ae9ab5a44d3ece2415c5ea97a5d4a8d2a6a4400 100644 --- a/container.go +++ b/container.go @@ -58,25 +58,26 @@ type Container struct { } type Config struct { - Hostname string - User string - Memory int64 // Memory limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 // CPU shares (relative weight vs. other containers) - AttachStdin bool - AttachStdout bool - AttachStderr bool - PortSpecs []string - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string - Cmd []string - Dns []string - Image string // Name of the image as it was passed by the operator (eg. could be symbolic) - Volumes map[string]struct{} - VolumesFrom string - Entrypoint []string + Hostname string + User string + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) + AttachStdin bool + AttachStdout bool + AttachStderr bool + PortSpecs []string + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string + Cmd []string + Dns []string + Image string // Name of the image as it was passed by the operator (eg. could be symbolic) + Volumes map[string]struct{} + VolumesFrom string + Entrypoint []string + NetworkEnabled bool } type HostConfig struct { @@ -106,6 +107,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") + flNetwork := cmd.Bool("n", true, "Enable networking for this container") if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") @@ -174,23 +176,24 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } config := &Config{ - Hostname: *flHostname, - PortSpecs: flPorts, - User: *flUser, - Tty: *flTty, - OpenStdin: *flStdin, - Memory: *flMemory, - CpuShares: *flCpuShares, - AttachStdin: flAttach.Get("stdin"), - AttachStdout: flAttach.Get("stdout"), - AttachStderr: flAttach.Get("stderr"), - Env: flEnv, - Cmd: runCmd, - Dns: flDns, - Image: image, - Volumes: flVolumes, - VolumesFrom: *flVolumesFrom, - Entrypoint: entrypoint, + Hostname: *flHostname, + PortSpecs: flPorts, + User: *flUser, + Tty: *flTty, + NetworkEnabled: *flNetwork, + OpenStdin: *flStdin, + Memory: *flMemory, + CpuShares: *flCpuShares, + AttachStdin: flAttach.Get("stdin"), + AttachStdout: flAttach.Get("stdout"), + AttachStderr: flAttach.Get("stderr"), + Env: flEnv, + Cmd: runCmd, + Dns: flDns, + Image: image, + Volumes: flVolumes, + VolumesFrom: *flVolumesFrom, + Entrypoint: entrypoint, } hostConfig := &HostConfig{ Binds: binds, @@ -626,7 +629,9 @@ func (container *Container) Start(hostConfig *HostConfig) error { } // Networking - params = append(params, "-g", container.network.Gateway.String()) + if container.Config.NetworkEnabled { + params = append(params, "-g", container.network.Gateway.String()) + } // User if container.Config.User != "" { @@ -727,6 +732,10 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { } func (container *Container) allocateNetwork() error { + if !container.Config.NetworkEnabled { + return nil + } + iface, err := container.runtime.networkManager.Allocate() if err != nil { return err @@ -753,6 +762,9 @@ func (container *Container) allocateNetwork() error { } func (container *Container) releaseNetwork() { + if !container.Config.NetworkEnabled { + return + } container.network.Release() container.network = nil container.NetworkSettings = &NetworkSettings{} diff --git a/container_test.go b/container_test.go index 028c03a3188c47855235a949b6c20c9b74a3ac17..8d4bbf6c74027462e4d9e0b0b35e117d6ef5d923 100644 --- a/container_test.go +++ b/container_test.go @@ -1251,3 +1251,41 @@ func TestRestartWithVolumes(t *testing.T) { t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual) } } + +func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + config, hc, _, err := ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil) + if err != nil { + t.Fatal(err) + } + c, err := NewBuilder(runtime).Create(config) + if err != nil { + t.Fatal(err) + } + stdout, err := c.StdoutPipe() + if err != nil { + t.Fatal(err) + } + + defer runtime.Destroy(c) + if err := c.Start(hc); err != nil { + t.Fatal(err) + } + c.WaitTimeout(500 * time.Millisecond) + c.Wait() + output, err := ioutil.ReadAll(stdout) + if err != nil { + t.Fatal(err) + } + + interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1) + if len(interfaces) != 1 { + t.Fatalf("Wrong interface count in test container: expected [1: lo], got [%s]", interfaces) + } + if interfaces[0] != "1: lo" { + t.Fatalf("Wrong interface in test container: expected [1: lo], got [%s]", interfaces) + } + +} diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index 19efd858215ab4e87f2d4cf83c959c39de830ba0..db67ef0705394273b6b1d125b480a63bbdfc29a2 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -20,6 +20,7 @@ -h="": Container host name -i=false: Keep stdin open even if not attached -m=0: Memory limit (in bytes) + -n=true: Enable networking for this container -p=[]: Map a network port to the container -t=false: Allocate a pseudo-tty -u="": Username or UID diff --git a/lxc_template.go b/lxc_template.go index 93b795e9015774b0c412541a317dd505313b767a..27670c807644a4bf7bb7fd4fa97d67110c24961f 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -13,6 +13,7 @@ lxc.utsname = {{.Id}} {{end}} #lxc.aa_profile = unconfined +{{if .Config.NetworkEnabled}} # network configuration lxc.network.type = veth lxc.network.flags = up @@ -20,6 +21,10 @@ lxc.network.link = {{.NetworkSettings.Bridge}} lxc.network.name = eth0 lxc.network.mtu = 1500 lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}} +{{else}} +# Network configuration disabled +lxc.network.type = empty +{{end}} # root filesystem {{$ROOTFS := .RootfsPath}} From 49673fc45cc5cfc15219bf1eb6eaff7621696919 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 17:49:09 -0700 Subject: [PATCH 039/101] Support completely disabling network configuration with docker -d -b none --- container.go | 8 ++++++-- network.go | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/container.go b/container.go index 6ae9ab5a44d3ece2415c5ea97a5d4a8d2a6a4400..189e882c44cba973f756579349040ee5db41018d 100644 --- a/container.go +++ b/container.go @@ -514,8 +514,12 @@ func (container *Container) Start(hostConfig *HostConfig) error { if err := container.EnsureMounted(); err != nil { return err } - if err := container.allocateNetwork(); err != nil { - return err + if container.runtime.networkManager.disabled { + container.Config.NetworkEnabled = false + } else { + if err := container.allocateNetwork(); err != nil { + return err + } } // Make sure the config is compatible with the current kernel diff --git a/network.go b/network.go index 0f98c899f1492445d2d343da07e36cb42d10f1a3..c71ecfb3ae9fe01d57771e5f493c59622bbdc4da 100644 --- a/network.go +++ b/network.go @@ -17,6 +17,7 @@ var NetworkBridgeIface string const ( DefaultNetworkBridge = "docker0" + DisableNetworkBridge = "none" portRangeStart = 49153 portRangeEnd = 65535 ) @@ -453,10 +454,16 @@ type NetworkInterface struct { manager *NetworkManager extPorts []*Nat + disabled bool } // Allocate an external TCP port and map it to the interface func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { + + if iface.disabled { + return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME + } + nat, err := parseNat(spec) if err != nil { return nil, err @@ -552,6 +559,11 @@ func parseNat(spec string) (*Nat, error) { // Release: Network cleanup - release all resources func (iface *NetworkInterface) Release() { + + if iface.disabled { + return + } + for _, nat := range iface.extPorts { utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { @@ -579,10 +591,17 @@ type NetworkManager struct { tcpPortAllocator *PortAllocator udpPortAllocator *PortAllocator portMapper *PortMapper + + disabled bool } // Allocate a network interface func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { + + if manager.disabled { + return &NetworkInterface{disabled: true}, nil + } + ip, err := manager.ipAllocator.Acquire() if err != nil { return nil, err @@ -596,6 +615,14 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { } func newNetworkManager(bridgeIface string) (*NetworkManager, error) { + + if bridgeIface == DisableNetworkBridge { + manager := &NetworkManager{ + disabled: true, + } + return manager, nil + } + addr, err := getIfaceAddr(bridgeIface) if err != nil { // If the iface is not found, try to create it From 964e826a9beb69a0246ed22c7935fd86df6712e1 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 18:01:52 -0700 Subject: [PATCH 040/101] Document -b none --- docker/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker.go b/docker/docker.go index fb7c4653691139de470cd28139ec82d0325aba2c..2db50bf32816e5d48c950155c49e0f418cf3a341 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -28,7 +28,7 @@ func main() { 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") + bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") From f236e62d9d288ac695129157d1753512f5cd2b0a Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sun, 7 Jul 2013 23:24:52 -0700 Subject: [PATCH 041/101] Test pulling remote files using ADD in a buildfile. --- buildfile_test.go | 103 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 14edbc088f7ba3bd6a79c4403e44f19c8769060f..602af5061bf6a039874585803d2810d6314a8418 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -3,13 +3,17 @@ package docker import ( "fmt" "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "strings" "testing" ) // 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, files [][2]string, t *testing.T) Archive { - context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files) + context, err := mkBuildContext(dockerfile, files) if err != nil { t.Fatal(err) } @@ -22,6 +26,8 @@ type testContextTemplate struct { dockerfile string // Additional files in the context, eg [][2]string{"./passwd", "gordon"} files [][2]string + // Additional remote files to host on a local HTTP server. + remoteFiles [][2]string } // A table of all the contexts to build and test. @@ -29,27 +35,31 @@ type testContextTemplate struct { var testContexts = []testContextTemplate{ { ` -from %s +from {IMAGE} 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" ] `, nil, + nil, }, { ` -from %s +from {IMAGE} add foo /usr/lib/bla/bar -run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ] +run [ "$(cat /usr/lib/bla/bar)" = 'hello' ] +add http://{SERVERADDR}/baz /usr/lib/baz/quux +run [ "$(cat /usr/lib/baz/quux)" = 'world!' ] `, - [][2]string{{"foo", "hello world!"}}, + [][2]string{{"foo", "hello"}}, + [][2]string{{"/baz", "world!"}}, }, { ` -from %s +from {IMAGE} add f / run [ "$(cat /f)" = "hello" ] add f /abc @@ -71,38 +81,70 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] {"f", "hello"}, {"d/ga", "bu"}, }, + nil, }, { ` -from %s +from {IMAGE} env FOO BAR run [ "$FOO" = "BAR" ] `, nil, + nil, }, { ` -from %s +from {IMAGE} ENTRYPOINT /bin/echo CMD Hello world `, nil, + nil, }, { ` -from %s +from {IMAGE} VOLUME /test CMD Hello world `, nil, + nil, }, } // FIXME: test building with 2 successive overlapping ADD commands +func constructDockerfile(template string, ip net.IP, port string) string { + serverAddr := fmt.Sprintf("%s:%s", ip, port) + replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr) + return replacer.Replace(template) +} + +func mkTestingFileServer(files [][2]string) (*httptest.Server, error) { + mux := http.NewServeMux() + for _, file := range files { + name, contents := file[0], file[1] + mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(contents)) + }) + } + + // This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote + // connections (from the container). + listener, err := net.Listen("tcp", ":0") + if err != nil { + return nil, err + } + + s := httptest.NewUnstartedServer(mux) + s.Listener = listener + s.Start() + return s, nil +} + func TestBuild(t *testing.T) { for _, ctx := range testContexts { buildImage(ctx, t) @@ -121,9 +163,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image { pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } - buildfile := NewBuildFile(srv, ioutil.Discard, false) - id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t)) + httpServer, err := mkTestingFileServer(context.remoteFiles) + if err != nil { + t.Fatal(err) + } + defer httpServer.Close() + + idx := strings.LastIndex(httpServer.URL, ":") + if idx < 0 { + t.Fatalf("could not get port from test http server address %s", httpServer.URL) + } + port := httpServer.URL[idx+1:] + + ip := runtime.networkManager.bridgeNetwork.IP + dockerfile := constructDockerfile(context.dockerfile, ip, port) + + buildfile := NewBuildFile(srv, ioutil.Discard, false) + id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err != nil { t.Fatal(err) } @@ -137,10 +194,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image { func TestVolume(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} volume /test cmd Hello world - `, nil}, t) + `, nil, nil}, t) if len(img.Config.Volumes) == 0 { t.Fail() @@ -154,9 +211,9 @@ func TestVolume(t *testing.T) { func TestBuildMaintainer(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} maintainer dockerio - `, nil}, t) + `, nil, nil}, t) if img.Author != "dockerio" { t.Fail() @@ -165,10 +222,10 @@ func TestBuildMaintainer(t *testing.T) { func TestBuildEnv(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} env port 4243 `, - nil}, t) + nil, nil}, t) if img.Config.Env[0] != "port=4243" { t.Fail() @@ -177,10 +234,10 @@ func TestBuildEnv(t *testing.T) { func TestBuildCmd(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} cmd ["/bin/echo", "Hello World"] `, - nil}, t) + nil, nil}, t) if img.Config.Cmd[0] != "/bin/echo" { t.Log(img.Config.Cmd[0]) @@ -194,10 +251,10 @@ func TestBuildCmd(t *testing.T) { func TestBuildExpose(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} expose 4243 `, - nil}, t) + nil, nil}, t) if img.Config.PortSpecs[0] != "4243" { t.Fail() @@ -206,10 +263,10 @@ func TestBuildExpose(t *testing.T) { func TestBuildEntrypoint(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} entrypoint ["/bin/echo"] `, - nil}, t) + nil, nil}, t) if img.Config.Entrypoint[0] != "/bin/echo" { } From 2b0ebf5d32c65276ce50fce168f32483ffb9c311 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 00:43:22 -0700 Subject: [PATCH 042/101] Buildfile: for ADD command, determine filename from URL. This is used if the destination is a directory. This makes the URL download behavior more closely match file copying. Fixes #1142. --- buildfile.go | 21 ++++++++++++++++++++- buildfile_test.go | 16 ++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 7ade058c69f4c2ccd02d2a63e15ecf04201be4c6..75ebdd7a7c12d6325347fda81eb3eecafc3021e6 100644 --- a/buildfile.go +++ b/buildfile.go @@ -7,6 +7,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" + "net/url" "os" "path" "reflect" @@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error { } defer file.Body.Close() + // If the destination is a directory, figure out the filename. + if strings.HasSuffix(dest, "/") { + u, err := url.Parse(orig) + if err != nil { + return err + } + path := u.Path + if strings.HasSuffix(path, "/") { + path = path[:len(path)-1] + } + parts := strings.Split(path, "/") + filename := parts[len(parts)-1] + if filename == "" { + return fmt.Errorf("cannot determine filename from url: %s", u) + } + dest = dest + filename + } + return container.Inject(file.Body, dest) } @@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error { origPath := path.Join(b.context, orig) destPath := path.Join(container.RootfsPath(), dest) // Preserve the trailing '/' - if dest[len(dest)-1] == '/' { + if strings.HasSuffix(dest, "/") { destPath = destPath + "/" } fi, err := os.Stat(origPath) diff --git a/buildfile_test.go b/buildfile_test.go index 602af5061bf6a039874585803d2810d6314a8418..b7eca523364cc5ed17ed0d79d0e45edfbdd84b17 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -84,6 +84,22 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] nil, }, + { + ` +from {IMAGE} +add http://{SERVERADDR}/x /a/b/c +run [ "$(cat /a/b/c)" = "hello" ] +add http://{SERVERADDR}/x?foo=bar / +run [ "$(cat /x)" = "hello" ] +add http://{SERVERADDR}/x /d/ +run [ "$(cat /d/x)" = "hello" ] +add http://{SERVERADDR} /e +run [ "$(cat /e)" = "blah" ] +`, + nil, + [][2]string{{"/x", "hello"}, {"/", "blah"}}, + }, + { ` from {IMAGE} From 416fdaa3d5d7b84f7e36fdfcff7153e3a38262c9 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 01:14:01 -0700 Subject: [PATCH 043/101] Remove some trailing whitespace. --- docs/sources/use/builder.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 7f370609c87aac4be89e30800a6e121a33834b64..ea98541dbf548262b9c3ff192ad5e3ed2266e781 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -30,7 +30,7 @@ build succeeds: ``docker build -t shykes/myapp .`` -Docker will run your steps one-by-one, committing the result if necessary, +Docker will run your steps one-by-one, committing the result if necessary, before finally outputting the ID of your new image. 2. Format @@ -43,7 +43,7 @@ The Dockerfile format is quite simple: # Comment INSTRUCTION arguments -The Instruction is not case-sensitive, however convention is for them to be +The Instruction is not case-sensitive, however convention is for them to be UPPERCASE in order to distinguish them from arguments more easily. Docker evaluates the instructions in a Dockerfile in order. **The first @@ -106,7 +106,7 @@ The ``CMD`` instruction sets the command to be executed when running the image. This is functionally equivalent to running ``docker commit -run '{"Cmd": }'`` outside the builder. -.. note:: +.. note:: Don't confuse `RUN` with `CMD`. `RUN` actually runs a command and commits the result; `CMD` does not execute anything at build time, but specifies the intended command for the image. @@ -131,7 +131,7 @@ value ````. This value will be passed to all future ``RUN`` instructions. This is functionally equivalent to prefixing the command with ``=`` -.. note:: +.. note:: The environment variables will persist when a container is run from the resulting image. @@ -158,10 +158,10 @@ The copy obeys the following rules: (identity, gzip, bzip2 or xz), it is unpacked as a directory. When a directory is copied or unpacked, it has the same behavior as - ``tar -x``: the result is the union of + ``tar -x``: the result is the union of 1. whatever existed at the destination path and - 2. the contents of the source tree, + 2. the contents of the source tree, with conflicts resolved in favor of 2) on a file-by-file basis. @@ -203,14 +203,14 @@ container created from the image. # Nginx # # VERSION 0.0.1 - + FROM ubuntu MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com" - + # make sure the package repository is up to date RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update - + RUN apt-get install -y inotify-tools nginx apache2 openssh-server .. code-block:: bash @@ -218,12 +218,12 @@ container created from the image. # Firefox over VNC # # VERSION 0.3 - + FROM ubuntu # make sure the package repository is up to date RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update - + # Install vnc, xvfb in order to create a 'fake' display and firefox RUN apt-get install -y x11vnc xvfb firefox RUN mkdir /.vnc @@ -231,7 +231,7 @@ container created from the image. RUN x11vnc -storepasswd 1234 ~/.vnc/passwd # Autostart firefox (might not be the best way, but it does the trick) RUN bash -c 'echo "firefox" >> /.bashrc' - + EXPOSE 5900 CMD ["x11vnc", "-forever", "-usepw", "-create"] From c383d598809daec6bccd60bd211c10f6a4ef60b0 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 01:29:13 -0700 Subject: [PATCH 044/101] Update ADD documentation to specify new behavior. --- docs/sources/use/builder.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index ea98541dbf548262b9c3ff192ad5e3ed2266e781..302527a74045df65acd859f34a7c814a3890ed06 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -152,6 +152,14 @@ destination container. The copy obeys the following rules: +* If ```` is a URL and ```` does not end with a trailing slash, + then a file is downloaded from the URL and copied to ````. +* If ```` is a URL and ```` does end with a trailing slash, + then the filename is inferred from the URL and the file is downloaded to + ``/``. For instance, ``ADD http://example.com/foobar /`` + would create the file ``/foobar``. The URL must have a nontrivial path + so that an appropriate filename can be discovered in this case + (``http://example.com`` will not work). * If ```` is a directory, the entire directory is copied, including filesystem metadata. * If ````` is a tar archive in a recognized compression format From 74a2b13687787a33b0963f020f704d3cdde4b06d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Jul 2013 14:52:05 +0000 Subject: [PATCH 045/101] fix error message when invalid directory --- commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/commands.go b/commands.go index 86385f41876ca95f24ea19a32df3e4e1a1a374aa..b0e32162e6e4e5952b3c73e0c05b675a9665212a 100644 --- a/commands.go +++ b/commands.go @@ -185,6 +185,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { isRemote = true } else { + if _, err := os.Stat(cmd.Arg(0)); err != nil { + return err + } context, err = Tar(cmd.Arg(0), Uncompressed) } var body io.Reader From 5c1af383eb14121174b43fa706f524d4eedd5cad Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Jul 2013 16:26:05 +0000 Subject: [PATCH 046/101] fix test env --- container_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container_test.go b/container_test.go index 028c03a3188c47855235a949b6c20c9b74a3ac17..a90018decce7867ee962336340eff037921985ec 100644 --- a/container_test.go +++ b/container_test.go @@ -959,6 +959,7 @@ func TestEnv(t *testing.T) { goodEnv := []string{ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/", + "container=lxc", } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { From 4714f102d72f03159acd0f7be71cde3d169c06b8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 12:06:24 -0700 Subject: [PATCH 047/101] Allocate a /16 IP range by default, with fallback to /24. Try a total of 12 ranges instead of 3. --- network.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/network.go b/network.go index 0f98c899f1492445d2d343da07e36cb42d10f1a3..d2d6668b26e70d55061b1753e9e95b94a5f2a10f 100644 --- a/network.go +++ b/network.go @@ -111,10 +111,29 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error { return nil } +// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, +// and attempts to configure it with an address which doesn't conflict with any other interface on the host. +// If it can't find an address which doesn't conflict, it will return an error. func CreateBridgeIface(ifaceName string) error { - // FIXME: try more IP ranges - // FIXME: try bigger ranges! /24 is too small. - addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"} + addrs := []string{ + // Here we don't follow the convention of using the 1st IP of the range for the gateway. + // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. + // In theory this shouldn't matter - in practice there's bound to be a few scripts relying + // on the internal addressing or other stupid things like that. + // The shouldn't, but hey, let's not break them unless we really have to. + "172.16.42.1/16", + "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive + "10.1.42.1/16", + "10.42.42.1/16", + "172.16.42.1/24", + "172.16.43.1/24", + "172.16.44.1/24", + "10.0.42.1/24", + "10.0.43.1/24", + "192.168.42.1/24", + "192.168.43.1/24", + "192.168.44.1/24", + } var ifaceAddr string for _, addr := range addrs { From ce43f4af1cf7c2692c4c6203bc5872053d089863 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:32:55 -0700 Subject: [PATCH 048/101] Hack: first draft of the maintainer boot camp. Work in progress! --- hack/bootcamp/README.md | 92 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 hack/bootcamp/README.md diff --git a/hack/bootcamp/README.md b/hack/bootcamp/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f4cc91f19ee4c1abacdb80c0fc810648d4e60748 --- /dev/null +++ b/hack/bootcamp/README.md @@ -0,0 +1,92 @@ +# Docker maintainer bootcamp + +## Introduction: we need more maintainers + +Docker is growing incredibly fast. At the time of writing, it has received over 200 contributions from 90 people, +and its API is used by dozens of 3d-party tools. Over 1,000 issues have been opened. As the first production deployments +start going live, the growth will only accelerate. + +Also at the time of writing, Docker has 3 full-time maintainers, and 7 part-time subsystem maintainers. If docker +is going to live up to the expectations, we need more than that. + +This document describes a *bootcamp* to guide and train volunteers interested in helping the project, either with individual +contributions, maintainer work, or both. + +This bootcamp is an experiment. If you decide to go through it, consider yourself an alpha-tester. You should expect quirks, +and report them to us as you encounter them to help us smooth out the process. + + +## How it works + +The maintainer bootcamp is a 12-step program - one step for each of the maintainer's responsibilities. The aspiring maintainer must +validate all 12 steps by 1) studying it, 2) practicing it, and 3) getting endorsed for it. + +Steps are all equally important and can be validated in any order. Validating all 12 steps is a pre-requisite for becoming a core +maintainer, but even 1 step will make you a better contributor! + +### List of steps + +#### 1) Be a power user + +Use docker daily, build cool things with it, know its quirks inside and out. + + +#### 2) Help users + +Answer questions on irc, twitter, email, in person. + + +#### 3) Manage the bug tracker + +Help triage tickets - ask the right questions, find duplicates, reference relevant resources, know when to close a ticket when necessary, take the time to go over older tickets. + + +#### 4) Improve the documentation + +Follow the documentation from scratch regularly and make sure it is still up-to-date. Find and fix inconsistencies. Remove stale information. Find a frequently asked question that is not documented. Simplify the content and the form. + + +#### 5) Evangelize the principles of docker + +Understand what the underlying goals and principle of docker are. Explain design decisions based on what docker is, and what it is not. When someone is not using docker, find how docker can be valuable to them. If they are using docker, find how they can use it better. + + +#### 6) Fix bugs + +Self-explanatory. Contribute improvements to docker which solve defects. Bugfixes should be well-tested, and prioritized by impact to the user. + + +#### 7) Improve the testing infrastructure + +Automated testing is complicated and should be perpetually improved. Invest time to improve the current tooling. Refactor existing tests, create new ones, make testing more accessible to developers, add new testing capabilities (integration tests, mocking, stress test...), improve integration between tests and documentation... + + +#### 8) Contribute features + +Improve docker to do more things, or get better at doing the same things. Features should be well-tested, not break existing APIs, respect the project goals. They should make the user's life measurably better. Features should be discussed ahead of time to avoid wasting time and duplicating effort. + + +#### 9) Refactor internals + +Improve docker to repay technical debt. Simplify code layout, improve performance, add missing comments, reduce the number of files and functions, rename functions and variables to be more readable, go over FIXMEs, etc. + +#### 10) Review and merge contributions + +Review pull requests in a timely manner, review code in detail and offer feedback. Keep a high bar without being pedantic. Share the load of testing and merging pull requests. + +#### 11) Release + +Manage a release of docker from beginning to end. Tests, final review, tags, builds, upload to mirrors, distro packaging, etc. + +#### 12) Train other maintainers + +Contribute to training other maintainers. Give advic + + +### How to study a step + +### How to practice a step + +### How to get endorsed for a step + + From 5714f0a74ef6f6b0be521d7e1e21c1cab1a9ad73 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:36:36 -0700 Subject: [PATCH 049/101] Hack: completed step 12 of the bootcamp --- hack/bootcamp/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hack/bootcamp/README.md b/hack/bootcamp/README.md index f4cc91f19ee4c1abacdb80c0fc810648d4e60748..88440e8c648d80103f20236d92913c4ba9679bc0 100644 --- a/hack/bootcamp/README.md +++ b/hack/bootcamp/README.md @@ -80,8 +80,7 @@ Manage a release of docker from beginning to end. Tests, final review, tags, bui #### 12) Train other maintainers -Contribute to training other maintainers. Give advic - +Contribute to training other maintainers. Give advice, delegate work, help organize the bootcamp. This also means contribute to the maintainer's manual, look for ways to improve the project organization etc. ### How to study a step From 6745bdd0b3a7944985614dae194538645eacb4e7 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:39:58 -0700 Subject: [PATCH 050/101] Typo in 3rd-party --- hack/bootcamp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/bootcamp/README.md b/hack/bootcamp/README.md index 88440e8c648d80103f20236d92913c4ba9679bc0..2c3d356daf411646a2400d8a80b885448d818864 100644 --- a/hack/bootcamp/README.md +++ b/hack/bootcamp/README.md @@ -3,7 +3,7 @@ ## Introduction: we need more maintainers Docker is growing incredibly fast. At the time of writing, it has received over 200 contributions from 90 people, -and its API is used by dozens of 3d-party tools. Over 1,000 issues have been opened. As the first production deployments +and its API is used by dozens of 3rd-party tools. Over 1,000 issues have been opened. As the first production deployments start going live, the growth will only accelerate. Also at the time of writing, Docker has 3 full-time maintainers, and 7 part-time subsystem maintainers. If docker From bc172e5e5f1f231f878727a180a8da46e653c0a7 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Mon, 22 Jul 2013 19:00:35 -0700 Subject: [PATCH 051/101] Invert network disable flag and logic (unbreaks TestAllocate*PortLocalhost) --- container.go | 84 ++++++++++++++++++++++++------------------------- lxc_template.go | 8 ++--- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/container.go b/container.go index 189e882c44cba973f756579349040ee5db41018d..f172604356f89f66651b03472fe84172d6bc8e87 100644 --- a/container.go +++ b/container.go @@ -58,26 +58,26 @@ type Container struct { } type Config struct { - Hostname string - User string - Memory int64 // Memory limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 // CPU shares (relative weight vs. other containers) - AttachStdin bool - AttachStdout bool - AttachStderr bool - PortSpecs []string - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string - Cmd []string - Dns []string - Image string // Name of the image as it was passed by the operator (eg. could be symbolic) - Volumes map[string]struct{} - VolumesFrom string - Entrypoint []string - NetworkEnabled bool + Hostname string + User string + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) + AttachStdin bool + AttachStdout bool + AttachStderr bool + PortSpecs []string + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string + Cmd []string + Dns []string + Image string // Name of the image as it was passed by the operator (eg. could be symbolic) + Volumes map[string]struct{} + VolumesFrom string + Entrypoint []string + NetworkDisabled bool } type HostConfig struct { @@ -176,24 +176,24 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, } config := &Config{ - Hostname: *flHostname, - PortSpecs: flPorts, - User: *flUser, - Tty: *flTty, - NetworkEnabled: *flNetwork, - OpenStdin: *flStdin, - Memory: *flMemory, - CpuShares: *flCpuShares, - AttachStdin: flAttach.Get("stdin"), - AttachStdout: flAttach.Get("stdout"), - AttachStderr: flAttach.Get("stderr"), - Env: flEnv, - Cmd: runCmd, - Dns: flDns, - Image: image, - Volumes: flVolumes, - VolumesFrom: *flVolumesFrom, - Entrypoint: entrypoint, + Hostname: *flHostname, + PortSpecs: flPorts, + User: *flUser, + Tty: *flTty, + NetworkDisabled: !*flNetwork, + OpenStdin: *flStdin, + Memory: *flMemory, + CpuShares: *flCpuShares, + AttachStdin: flAttach.Get("stdin"), + AttachStdout: flAttach.Get("stdout"), + AttachStderr: flAttach.Get("stderr"), + Env: flEnv, + Cmd: runCmd, + Dns: flDns, + Image: image, + Volumes: flVolumes, + VolumesFrom: *flVolumesFrom, + Entrypoint: entrypoint, } hostConfig := &HostConfig{ Binds: binds, @@ -515,7 +515,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { return err } if container.runtime.networkManager.disabled { - container.Config.NetworkEnabled = false + container.Config.NetworkDisabled = true } else { if err := container.allocateNetwork(); err != nil { return err @@ -633,7 +633,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } // Networking - if container.Config.NetworkEnabled { + if !container.Config.NetworkDisabled { params = append(params, "-g", container.network.Gateway.String()) } @@ -736,7 +736,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { } func (container *Container) allocateNetwork() error { - if !container.Config.NetworkEnabled { + if container.Config.NetworkDisabled { return nil } @@ -766,7 +766,7 @@ func (container *Container) allocateNetwork() error { } func (container *Container) releaseNetwork() { - if !container.Config.NetworkEnabled { + if container.Config.NetworkDisabled { return } container.network.Release() diff --git a/lxc_template.go b/lxc_template.go index 27670c807644a4bf7bb7fd4fa97d67110c24961f..d1f67e337622c3c8451d79781e2dfe88dd3193b6 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -13,7 +13,10 @@ lxc.utsname = {{.Id}} {{end}} #lxc.aa_profile = unconfined -{{if .Config.NetworkEnabled}} +{{if .Config.NetworkDisabled}} +# network is disabled (-n=false) +lxc.network.type = empty +{{else}} # network configuration lxc.network.type = veth lxc.network.flags = up @@ -21,9 +24,6 @@ lxc.network.link = {{.NetworkSettings.Bridge}} lxc.network.name = eth0 lxc.network.mtu = 1500 lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}} -{{else}} -# Network configuration disabled -lxc.network.type = empty {{end}} # root filesystem From 58a1c5720a42fde72c27532ce1122609225bc3bb Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 22 Jul 2013 20:26:40 -0700 Subject: [PATCH 052/101] Added new docker logo to the documentation header, and added other links. --- docs/sources/use/basics.rst | 4 ++-- docs/theme/docker/layout.html | 13 ++++++------- docs/theme/docker/static/css/main.css | 10 +++++----- docs/theme/docker/static/css/main.less | 10 +++------- docs/theme/docker/static/img/docker-top-logo.png | Bin 0 -> 3426 bytes .../docker/static/img/external-link-icon.png | Bin 0 -> 2917 bytes 6 files changed, 16 insertions(+), 21 deletions(-) create mode 100644 docs/theme/docker/static/img/docker-top-logo.png create mode 100644 docs/theme/docker/static/img/external-link-icon.png diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 7c9e2e9055362973b2200c531d5db70cd0635fc6..7ce86416dd33718a17d17cba4ac6c78d8556b729 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -51,7 +51,7 @@ For example: .. code-block:: bash # Run docker in daemon mode - sudo /docker -H 0.0.0.0:5555 & + sudo /docker -H 0.0.0.0:5555 -d & # Download a base image docker -H :5555 pull base @@ -61,7 +61,7 @@ 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 + sudo /docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d & # Download a base image docker pull base # OR diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index f4f17f2dba95c1adc31002588c0518e9f403f366..198cd5d7d81c3cd599feb463b5c218f354fc2d29 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -68,19 +68,18 @@
- +
@@ -96,7 +95,7 @@ -

DOCUMENTATION

+

DOCUMENTATION

diff --git a/docs/theme/docker/static/css/main.css b/docs/theme/docker/static/css/main.css index c79d590e078fe8b067d169c37b481e6b0f577a40..0b11890f710f21db65e4b94171ee37cd13c3664f 100755 --- a/docs/theme/docker/static/css/main.css +++ b/docs/theme/docker/static/css/main.css @@ -34,12 +34,12 @@ h4 { .navbar .nav li a { padding: 22px 15px 22px; } -.navbar .brand { - padding: 13px 10px 13px 28px ; -} .navbar-dotcloud .container { border-bottom: 2px #000000 solid; } +.inline-icon { + margin-bottom: 6px; +} /* * Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS * http://www.jonsuh.com @@ -82,7 +82,7 @@ h4 { .btn-custom { background-color: #292929 !important; background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828"); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828"); background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828)); background-image: -moz-linear-gradient(top, #515151, #282828); background-image: -ms-linear-gradient(top, #515151, #282828); @@ -301,7 +301,7 @@ section.header { height: 28px; line-height: 28px; background-color: #43484c; - filter: progid:dximagetransform.microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35'); + filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35'); background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c)); background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%); background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%); diff --git a/docs/theme/docker/static/css/main.less b/docs/theme/docker/static/css/main.less index c8c38dc8edd7f8afe6d32f3fc4a4c9f5193f17b3..8c470518d82a80c1bdd85725c4eb24733a15fe0b 100644 --- a/docs/theme/docker/static/css/main.less +++ b/docs/theme/docker/static/css/main.less @@ -53,13 +53,6 @@ h1, h2, h3, h4 { padding: 22px 15px 22px; } } - - .brand { - padding: 13px 10px 13px 28px ; - // padding-left: 30px; - - } - background-color: white; } @@ -67,6 +60,9 @@ h1, h2, h3, h4 { border-bottom: 2px @black solid; } +.inline-icon { + margin-bottom: 6px; +} /* * Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS diff --git a/docs/theme/docker/static/img/docker-top-logo.png b/docs/theme/docker/static/img/docker-top-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4955f499bdc39daa24c3e6b779283e113eeaf1ce GIT binary patch literal 3426 zcmZwKS2!CA8wc>%B|>qG7*%bJlA5hmdn>hx)EI=yLvmrZ|ZwGxxvlgPOc%y4!F{vWomsbb&KH1&1?q~i!r{o z$o3|2k+NJZ^#Sdy4E0;Z-vSK>arcLQ$_O#Ya}vKBHSR$Tucvx+#WJ z3>6r8_J7IzIrJnwF(LEAg0r(I{rW9Y(a3TFWeq`GxuL6iV}7%`CI|ypzX*5p+V(vn z`t{D7DKwm&UG!E9xge1&dS43ccve}HulcjastKq37c8rHSYH|4`WLe&a4B<<+qYeM z9)>RUpxvBbY}KDPD#%Uux*5bmq<$%>Hxy4??d=vuO$40^gcUkOWqT>FJp=GjhGj`! z*@9h_xha8Dh+=z`Qgk@IKkM4s&VUR`CZ!JU8^3)c7G?VXQ<8F+_(Jx-L~ZfL=5Th- zvQE%-nN4B4__Ov|nW#g*TJjTJy%9FUoC-tevZoS>+af>}Ptujw#19DZygp4MF&o!; z=vC$INI1I&q*R7;Pb^03vX7Xy1TiJK7>HROft?cRQ z=H_#0PaoN(Yr7j=kUz=2+}ce>H_kL321L`oy1d`<0D4->q+nQg=u#HXG*V7s(pM7d z#_gOY;}MkVxm8lPzp>WjgDFk(e46kihg?9c$O%A3$6<6UFQGF{c7EJyzI%bTyoCq( zI`MhY$}sYsa5YpiZiaBAC0K~DIZ8@*+9PotMyCyEC{^@d2yAWlLS`sTlUx=EkWizf& zTY%@Yn-#g?M=h=5)CL%=5Ed2S?&&!V&$rnw33JfldtrUT5WlU$wPl%Ri8}h&HrLM| zxmDgxvnhm95T4zUNmMbGj*2?lXR^nPdY4EH=!?$?V@#5)pwi;pLF_L0m?973r!sdj zk5Nb^UuAJSr=-Q>oD;)zfHy`sIglz&kkH9n5D5oi@U}%S= zwEXEaT{~@U95sbJbzK3B3R6~#ny^p0$B*40)yU}B97vt!m;kV{?{17+13r07`O39? zD25bcn*IK1IM~BEw9oQd+Iw{Ub9;w+>bYjfN4^ZzHpT4|;hT`UO6P;fgnETkyPCJy z0pHQ0lIsn3rE9HwZxgxRbnuL{2ypyn&!IG;FJl^25rK%MZ=`p|g;N&sg`twew2Ghi z+y67N&;ax3>u{35e7}ozG06*=K9~C`*yIqCIhN90`>0JZ&?aENzs;)0QAJLc(eL4r zj;eF*m{YBMXp7bEOS*ZwfU}R-} z?7QRhKP_2v#GQOrPOtH`#5dmC`<%&KX4T;~GF`|R#j`p8>buSJiWCgnlv3_lDKQ*( z??^w;IC$1QgnyDzt$DWGfFi`TI=UXA=d2b0;yvdcX80|MwiLE2@OtJ`_4ZTVTbhT{fJKbvo}<)5w$?31wDLgk*yPU#;`I|7Y_@Dk z9bk1Zj&`J5^vwaFCdF;TK(8;_>QZFNdVQPZFE|?DKdGext@ro68J|xrJW%5WN{AyTb zQN`LGsQc%ecm z#3~yWkJvJ@ppQ*$Q#DhIqh1S23Y)?s_^XIJSIKfx08I1q9E2=o`2zA6a8wXYkmozi z^Nl=yEnpkMIr~~ESgsSA2#;;qzwS>hKdNv{CTt*f_ki>n>?`TEeY@QE zRyF3_s~w2V-tUbmh)+cL#DLQsrH=R8^_-F?|5_Q2{fQE%hImRv7zSO{lklx_5~v}yLVo&aKKcXE}xBl@);gGVQVgPF)=k+tBLCud-_F@1p44STUUwW1NFLGRL^^2LSSwCb8J!Z8Yw~&iQFBI=rYsxvZv3uh z9=AO0GkhSu(m-e6{!5cg&^$8Z6N|aE2l$ZVQ@q`7>RnEuimbc5!OP`-MIXQ;{8Q+| z_lX;4No&GQF^{X0Q^2#2uv`ta&6rDy>gwvb$g`E5wRV5shPR-y9mAKeyfzhK9Lx6` zvOwLbCvmj(t0CKo!MLl4M=9(u#BWB#{cv95jfmPO?Xushg)XE7e$k$rglsjZYV<~iv1xtJAA<4+?tc`fO91U9 zESH)jxT@wkY}IPbvSjiSykt6l6%6XsAn z0waENhrXXQu*(F^&2p&la!#SiCV%_dMG5m5y{DG2Y8ljNH=z38so>$Ej z2(g9lp?1rm4vgd3agCOAyXN1#^n9E~@kqOW+Hm)T3wD1)8~8vrModdf%W4z7*&bp; zwrLI*M>lF!Gt}ZZE+T0IxEigfGey}|&lz~-RCi$>82YyFX{ENEm=|>v3D8NVX z0|iDFrHe`xZ_4n&4WwT9x{US7IOGdeIHDfC-am!DIH=I6s7GuV4F|y3`$OK;FyByI zQam|i#?*-L$f^Yfa*UT)mHEtGTy`s+&n-N5F-R1@UO`-a|Mac~+Gpc3SUZyWuLMx& z@$1iHvpKBaFjp*UcBC}1N&LFp^CYT@oyo@QQU0>mhh;6S(rILWydqa9)9mzG3NR;0 zzTrGk$+Do6)UDr|S-BR*qeOEhOaCmpTKqknBmxors@r96Z^}}!=|D0hc!$1$lB4=j zoQa_4$rUe$dcNT>UI!JP15iHyMyNZyG=8y?RLAsM`?ZwQ)s|Zl0js9=mF|P!QK?LO z-L;4LO_OU^?pnD8?EV5=P2|C~&aa|jzu#G!z$zsc2H$dLT$3K>N4X6j3&IWAoUIB7 zzfz|gJ4CHNfHv;{jdH((S6Rs!DTG)CXvd*hnEqN|Pv>oN-ju=Z-{PRNd14O|gy8dX z_hInOJV034M7Dq`n7tUJ>B#|fc+rIvsU9uBWi)x%m)`O_7X5pvrcv4lpOzo yh7FBd{rYz(v$g({T(lmwVyxD`tc_gJZ~=-s?9joYKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0001xNkl$Cw%(46SJctNH^mn=q07@yJ8~+9XcPCzfBYN71 P00000NkvXXu0mjfh3RP# literal 0 HcmV?d00001 From 3bae188b8dc51911a44ea1c7b5681f9f07f9d3af Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 23 Jul 2013 15:04:31 +0000 Subject: [PATCH 053/101] change dockercfg to json and support multiple auth remote --- api.go | 58 +++------------------------- auth/auth.go | 104 ++++++++++++++++++++++++++------------------------- commands.go | 52 ++++++++++++++------------ 3 files changed, 87 insertions(+), 127 deletions(-) diff --git a/api.go b/api.go index b6ab7badfa1ceaf1a615eccffbd520c0e3da3276..975134f22ddc75a901d9f994e85c834d3a81b218 100644 --- a/api.go +++ b/api.go @@ -81,54 +81,15 @@ func getBoolParam(value string) (bool, error) { return ret, nil } -func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version > 1.1 { - w.WriteHeader(http.StatusNotFound) - return nil - } - authConfig, err := auth.LoadConfig(srv.runtime.root) - if err != nil { - if err != auth.ErrConfigFileMissing { - return err - } - authConfig = &auth.AuthConfig{} - } - b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email}) - if err != nil { - return err - } - writeJSON(w, b) - return nil -} - func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { authConfig := &auth.AuthConfig{} err := json.NewDecoder(r.Body).Decode(authConfig) if err != nil { return err } - status := "" - if version > 1.1 { - status, err = auth.Login(authConfig, false) - if err != nil { - return err - } - } else { - localAuthConfig, err := auth.LoadConfig(srv.runtime.root) - if err != nil { - if err != auth.ErrConfigFileMissing { - return err - } - } - if authConfig.Username == localAuthConfig.Username { - authConfig.Password = localAuthConfig.Password - } - - newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root) - status, err = auth.Login(newAuthConfig, true) - if err != nil { - return err - } + status, err := auth.Login(authConfig) + if err != nil { + return err } if status != "" { b, err := json.Marshal(&APIAuth{Status: status}) @@ -429,16 +390,8 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { authConfig := &auth.AuthConfig{} - if version > 1.1 { - if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { - return err - } - } else { - localAuthConfig, err := auth.LoadConfig(srv.runtime.root) - if err != nil && err != auth.ErrConfigFileMissing { - return err - } - authConfig = localAuthConfig + if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { + return err } if err := parseForm(r); err != nil { return err @@ -854,7 +807,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ "GET": { - "/auth": getAuth, "/version": getVersion, "/info": getInfo, "/images/json": getImagesJSON, diff --git a/auth/auth.go b/auth/auth.go index 97df928b6b3eed0379b7e2e132de442420618825..8f4d09fb8fe7a1afa515e38ebfee85e722ee18e9 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -25,19 +25,15 @@ var ( ) type AuthConfig struct { - Username string `json:"username"` - Password string `json:"password"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` Email string `json:"email"` - rootPath string } -func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { - return &AuthConfig{ - Username: username, - Password: password, - Email: email, - rootPath: rootPath, - } +type ConfigFile struct { + Configs map[string]AuthConfig `json:"configs,omitempty"` + rootPath string } func IndexServerAddress() string { @@ -54,61 +50,83 @@ func encodeAuth(authConfig *AuthConfig) string { } // decode the auth string -func decodeAuth(authStr string) (*AuthConfig, error) { +func decodeAuth(authStr string) (string, string, error) { decLen := base64.StdEncoding.DecodedLen(len(authStr)) decoded := make([]byte, decLen) authByte := []byte(authStr) n, err := base64.StdEncoding.Decode(decoded, authByte) if err != nil { - return nil, err + return "", "", err } if n > decLen { - return nil, fmt.Errorf("Something went wrong decoding auth config") + return "", "", fmt.Errorf("Something went wrong decoding auth config") } arr := strings.Split(string(decoded), ":") if len(arr) != 2 { - return nil, fmt.Errorf("Invalid auth configuration file") + return "", "", fmt.Errorf("Invalid auth configuration file") } password := strings.Trim(arr[1], "\x00") - return &AuthConfig{Username: arr[0], Password: password}, nil + return arr[0], password, nil } // load up the auth config information and return values // FIXME: use the internal golang config parser -func LoadConfig(rootPath string) (*AuthConfig, error) { +func LoadConfig(rootPath string) (*ConfigFile, error) { + configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} confFile := path.Join(rootPath, CONFIGFILE) if _, err := os.Stat(confFile); err != nil { - return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing + return &configFile, ErrConfigFileMissing } b, err := ioutil.ReadFile(confFile) if err != nil { return nil, err } - arr := strings.Split(string(b), "\n") - if len(arr) < 2 { - return nil, fmt.Errorf("The Auth config file is empty") - } - origAuth := strings.Split(arr[0], " = ") - origEmail := strings.Split(arr[1], " = ") - authConfig, err := decodeAuth(origAuth[1]) - if err != nil { - return nil, err + + if err := json.Unmarshal(b, &configFile.Configs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return nil, fmt.Errorf("The Auth config file is empty") + } + authConfig := AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) + if err != nil { + return nil, err + } + origEmail := strings.Split(arr[1], " = ") + authConfig.Email = origEmail[1] + configFile.Configs[IndexServerAddress()] = authConfig + } else { + for k, authConfig := range configFile.Configs { + authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) + if err != nil { + return nil, err + } + configFile.Configs[k] = authConfig + } } - authConfig.Email = origEmail[1] - authConfig.rootPath = rootPath - return authConfig, nil + return &configFile, nil } // save the auth config -func SaveConfig(authConfig *AuthConfig) error { - confFile := path.Join(authConfig.rootPath, CONFIGFILE) - if len(authConfig.Email) == 0 { +func SaveConfig(configFile *ConfigFile) error { + confFile := path.Join(configFile.rootPath, CONFIGFILE) + if len(configFile.Configs) == 0 { os.Remove(confFile) return nil } - lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n" - b := []byte(lines) - err := ioutil.WriteFile(confFile, b, 0600) + for k, authConfig := range configFile.Configs { + authConfig.Auth = encodeAuth(&authConfig) + authConfig.Username = "" + authConfig.Password = "" + configFile.Configs[k] = authConfig + } + + b, err := json.Marshal(configFile.Configs) + if err != nil { + return err + } + err = ioutil.WriteFile(confFile, b, 0600) if err != nil { return err } @@ -116,8 +134,7 @@ func SaveConfig(authConfig *AuthConfig) error { } // try to register/login to the registry server -func Login(authConfig *AuthConfig, store bool) (string, error) { - storeConfig := false +func Login(authConfig *AuthConfig) (string, error) { client := &http.Client{} reqStatusCode := 0 var status string @@ -143,7 +160,6 @@ 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." - storeConfig = true } else if reqStatusCode == 403 { return "", fmt.Errorf("Login: Your account hasn't been activated. " + "Please check your e-mail for a confirmation link.") @@ -162,14 +178,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { } if resp.StatusCode == 200 { status = "Login Succeeded" - storeConfig = true } else if resp.StatusCode == 401 { - if store { - authConfig.Email = "" - if err := SaveConfig(authConfig); err != nil { - return "", err - } - } return "", fmt.Errorf("Wrong login/password, please try again") } else { return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, @@ -181,10 +190,5 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { } else { return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) } - if storeConfig && store { - if err := SaveConfig(authConfig); err != nil { - return "", err - } - } return status, nil } diff --git a/commands.go b/commands.go index b0e32162e6e4e5952b3c73e0c05b675a9665212a..17597320b5c9191532e9f4c54935eb4f3e5a81d1 100644 --- a/commands.go +++ b/commands.go @@ -313,16 +313,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error { email string ) + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] + if !ok { + authconfig = auth.AuthConfig{} + } + if *flUsername == "" { - fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username) + fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) username = readAndEchoString(cli.in, cli.out) if username == "" { - username = cli.authConfig.Username + username = authconfig.Username } } else { username = *flUsername } - if username != cli.authConfig.Username { + if username != authconfig.Username { if *flPassword == "" { fmt.Fprintf(cli.out, "Password: ") password = readString(cli.in, cli.out) @@ -334,31 +339,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if *flEmail == "" { - fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email) + fmt.Fprintf(cli.out, "Email (%s): ", authconfig.Email) email = readAndEchoString(cli.in, cli.out) if email == "" { - email = cli.authConfig.Email + email = authconfig.Email } } else { email = *flEmail } } else { - password = cli.authConfig.Password - email = cli.authConfig.Email + password = authconfig.Password + email = authconfig.Email } if oldState != nil { term.RestoreTerminal(cli.terminalFd, oldState) } - cli.authConfig.Username = username - cli.authConfig.Password = password - cli.authConfig.Email = email + authconfig.Username = username + authconfig.Password = password + authconfig.Email = email + cli.configFile.Configs[auth.IndexServerAddress()] = authconfig - body, statusCode, err := cli.call("POST", "/auth", cli.authConfig) + body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()]) if statusCode == 401 { - cli.authConfig.Username = "" - cli.authConfig.Password = "" - cli.authConfig.Email = "" - auth.SaveConfig(cli.authConfig) + delete(cli.configFile.Configs, auth.IndexServerAddress()) + auth.SaveConfig(cli.configFile) return err } if err != nil { @@ -368,10 +372,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var out2 APIAuth err = json.Unmarshal(body, &out2) if err != nil { - auth.LoadConfig(os.Getenv("HOME")) + cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME")) return err } - auth.SaveConfig(cli.authConfig) + auth.SaveConfig(cli.configFile) if out2.Status != "" { fmt.Fprintf(cli.out, "%s\n", out2.Status) } @@ -802,10 +806,10 @@ func (cli *DockerCli) CmdPush(args ...string) error { // Custom repositories can have different rules, and we must also // allow pushing by image ID. if len(strings.SplitN(name, "/", 2)) == 1 { - return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.authConfig.Username, name) + return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name) } - buf, err := json.Marshal(cli.authConfig) + buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) if err != nil { return err } @@ -1410,11 +1414,11 @@ func (cli *DockerCli) CmdRun(args ...string) error { func (cli *DockerCli) checkIfLogged(action string) error { // If condition AND the login failed - if cli.authConfig.Username == "" { + if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { if err := cli.CmdLogin(""); err != nil { return err } - if cli.authConfig.Username == "" { + if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { return fmt.Errorf("Please login prior to %s. ('docker login')", action) } } @@ -1670,11 +1674,11 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc err = out } - authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) + configFile, _ := auth.LoadConfig(os.Getenv("HOME")) return &DockerCli{ proto: proto, addr: addr, - authConfig: authConfig, + configFile: configFile, in: in, out: out, err: err, @@ -1686,7 +1690,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc type DockerCli struct { proto string addr string - authConfig *auth.AuthConfig + configFile *auth.ConfigFile in io.ReadCloser out io.Writer err io.Writer From b5da816487d68853a8ac46630cb3118646f71d2d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 10 Jul 2013 12:55:05 +0000 Subject: [PATCH 054/101] basic version of the /events endpoint --- api.go | 28 +++++++++++++++++++++++++++- api_params.go | 15 ++++++++------- commands.go | 31 ++++++++++++++++++++++--------- runtime_test.go | 12 ++++++------ server.go | 34 ++++++++++++++++++++++++++-------- utils/utils.go | 15 +++++++++++++++ 6 files changed, 104 insertions(+), 31 deletions(-) diff --git a/api.go b/api.go index b6ab7badfa1ceaf1a615eccffbd520c0e3da3276..9d0348b6086f59fde264054443d1ce8d809e8e81 100644 --- a/api.go +++ b/api.go @@ -217,6 +217,31 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques return nil } +func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + events := make(chan utils.JSONMessage) + srv.Lock() + srv.events[r.RemoteAddr] = events + srv.Unlock() + w.Header().Set("Content-Type", "application/json") + wf := utils.NewWriteFlusher(w) + for { + event := <-events + b, err := json.Marshal(event) + if err != nil { + continue + } + _, err = wf.Write(b) + if err != nil { + utils.Debugf("%s", err) + srv.Lock() + delete(srv.events, r.RemoteAddr) + srv.Unlock() + return err + } + } + return nil +} + func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") @@ -855,8 +880,9 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ "GET": { "/auth": getAuth, - "/version": getVersion, + "/events": getEvents, "/info": getInfo, + "/version": getVersion, "/images/json": getImagesJSON, "/images/viz": getImagesViz, "/images/search": getImagesSearch, diff --git a/api_params.go b/api_params.go index b371ca314f4fd68ca15b75c5030057c8dbe40822..26d70711a1e0e5b79fec33feec35ca1ecb703cac 100644 --- a/api_params.go +++ b/api_params.go @@ -17,13 +17,14 @@ type APIImages struct { } type APIInfo struct { - Debug bool - Containers int - Images int - NFd int `json:",omitempty"` - NGoroutines int `json:",omitempty"` - MemoryLimit bool `json:",omitempty"` - SwapLimit bool `json:",omitempty"` + Debug bool + Containers int + Images int + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` + MemoryLimit bool `json:",omitempty"` + SwapLimit bool `json:",omitempty"` + NEventsListener int `json:",omitempty"` } type APITop struct { diff --git a/commands.go b/commands.go index b0e32162e6e4e5952b3c73e0c05b675a9665212a..12647feead723e2d431332f8a8f73003902d71ba 100644 --- a/commands.go +++ b/commands.go @@ -78,6 +78,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"build", "Build a container from a Dockerfile"}, {"commit", "Create a new image from a container's changes"}, {"diff", "Inspect changes on a container's filesystem"}, + {"events", "Get real time events from the server"}, {"export", "Stream the contents of a container as a tar archive"}, {"history", "Show the history of an image"}, {"images", "List images"}, @@ -466,6 +467,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd) fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines) + fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener) } if !out.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") @@ -1055,6 +1057,23 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return nil } +func (cli *DockerCli) CmdEvents(args ...string) error { + cmd := Subcmd("events", "", "Get real time events from the server") + if err := cmd.Parse(args); err != nil { + return nil + } + + if cmd.NArg() != 0 { + cmd.Usage() + return nil + } + + if err := cli.stream("GET", "/events", nil, cli.out); err != nil { + return err + } + return nil +} + func (cli *DockerCli) CmdExport(args ...string) error { cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive") if err := cmd.Parse(args); err != nil { @@ -1509,19 +1528,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if resp.Header.Get("Content-Type") == "application/json" { dec := json.NewDecoder(resp.Body) for { - var m utils.JSONMessage - if err := dec.Decode(&m); err == io.EOF { + var jm utils.JSONMessage + if err := dec.Decode(&jm); err == io.EOF { break } else if err != nil { return err } - if m.Progress != "" { - fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress) - } else if m.Error != "" { - return fmt.Errorf(m.Error) - } else { - fmt.Fprintf(out, "%s\n", m.Status) - } + jm.Display(out) } } else { if _, err := io.Copy(out, resp.Body); err != nil { diff --git a/runtime_test.go b/runtime_test.go index 66d92c8100f223410e2c7f41fdda39c31f94b926..807097404d0f0a6fd257be418bbaa91478b4ebdd 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -17,12 +17,12 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var globalRuntime *Runtime diff --git a/server.go b/server.go index b92ed8fd7340fd95c5e88c56a980500f94a55dcd..2499d64397ff2e37cee3f696026ba6c7fe5029bd 100644 --- a/server.go +++ b/server.go @@ -32,8 +32,9 @@ func (srv *Server) DockerVersion() APIVersion { func (srv *Server) ContainerKill(name string) error { if container := srv.runtime.Get(name); container != nil { if err := container.Kill(); err != nil { - return fmt.Errorf("Error restarting container %s: %s", name, err) + return fmt.Errorf("Error killing container %s: %s", name, err) } + srv.SendEvent("kill", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -52,6 +53,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { if _, err := io.Copy(out, data); err != nil { return err } + srv.SendEvent("export", name) return nil } return fmt.Errorf("No such container: %s", name) @@ -209,13 +211,14 @@ func (srv *Server) DockerInfo() *APIInfo { imgcount = len(images) } return &APIInfo{ - Containers: len(srv.runtime.List()), - Images: imgcount, - MemoryLimit: srv.runtime.capabilities.MemoryLimit, - SwapLimit: srv.runtime.capabilities.SwapLimit, - Debug: os.Getenv("DEBUG") != "", - NFd: utils.GetTotalUsedFds(), - NGoroutines: runtime.NumGoroutine(), + Containers: len(srv.runtime.List()), + Images: imgcount, + MemoryLimit: srv.runtime.capabilities.MemoryLimit, + SwapLimit: srv.runtime.capabilities.SwapLimit, + Debug: os.Getenv("DEBUG") != "", + NFd: utils.GetTotalUsedFds(), + NGoroutines: runtime.NumGoroutine(), + NEventsListener: len(srv.events), } } @@ -810,6 +813,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { } return "", err } + srv.SendEvent("create", container.ShortID()) return container.ShortID(), nil } @@ -818,6 +822,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { if err := container.Restart(t); err != nil { return fmt.Errorf("Error restarting container %s: %s", name, err) } + srv.SendEvent("restart", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -837,6 +842,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { if err := srv.runtime.Destroy(container); err != nil { return fmt.Errorf("Error destroying container %s: %s", name, err) } + srv.SendEvent("destroy", name) if removeVolume { // Retrieve all volumes from all remaining containers @@ -903,6 +909,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { return err } *imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)}) + srv.SendEvent("delete", utils.TruncateID(id)) return nil } return nil @@ -946,6 +953,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro } if tagDeleted { imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) + srv.SendEvent("untagged", img.ShortID()) } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { @@ -1018,6 +1026,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if err := container.Start(hostConfig); err != nil { return fmt.Errorf("Error starting container %s: %s", name, err) } + srv.SendEvent("start", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1029,6 +1038,7 @@ func (srv *Server) ContainerStop(name string, t int) error { if err := container.Stop(t); err != nil { return fmt.Errorf("Error stopping container %s: %s", name, err) } + srv.SendEvent("stop", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1162,15 +1172,23 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( enableCors: enableCors, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), + events: make(map[string]chan utils.JSONMessage), } runtime.srv = srv return srv, nil } +func (srv *Server) SendEvent(action, id string) { + for _, c := range srv.events { + c <- utils.JSONMessage{Status: action, ID: id} + } +} + type Server struct { sync.Mutex runtime *Runtime enableCors bool pullingPool map[string]struct{} pushingPool map[string]struct{} + events map[string]chan utils.JSONMessage } diff --git a/utils/utils.go b/utils/utils.go index 77b3f879cd698bfd0b53e70041785354340d7ba9..1523835f99b71eb0674cf2a3a71259e4cae245cd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -611,8 +611,23 @@ type JSONMessage struct { Status string `json:"status,omitempty"` Progress string `json:"progress,omitempty"` Error string `json:"error,omitempty"` + ID string `json:"id,omitempty"` } +func (jm *JSONMessage) Display(out io.Writer) (error) { + if jm.Progress != "" { + fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) + } else if jm.Error != "" { + return fmt.Errorf(jm.Error) + } else if jm.ID != "" { + fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status) + } else { + fmt.Fprintf(out, "%s\n", jm.Status) + } + return nil +} + + type StreamFormatter struct { json bool used bool From b8d52ec2669332988a972bff3b5f5d2e9d526b33 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 12 Jul 2013 15:09:20 +0000 Subject: [PATCH 055/101] add timestamp and change untagged -> untag --- server.go | 5 +++-- utils/utils.go | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 2499d64397ff2e37cee3f696026ba6c7fe5029bd..8ba90c69e3d6c5a5a2c1e7b91ad1b4d8efbf4cba 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,7 @@ import ( "runtime" "strings" "sync" + "time" ) func (srv *Server) DockerVersion() APIVersion { @@ -953,7 +954,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro } if tagDeleted { imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) - srv.SendEvent("untagged", img.ShortID()) + srv.SendEvent("untag", img.ShortID()) } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { @@ -1180,7 +1181,7 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( func (srv *Server) SendEvent(action, id string) { for _, c := range srv.events { - c <- utils.JSONMessage{Status: action, ID: id} + c <- utils.JSONMessage{Status: action, ID: id, Time: time.Now().Unix()} } } diff --git a/utils/utils.go b/utils/utils.go index 1523835f99b71eb0674cf2a3a71259e4cae245cd..acb015becdabec7b60a45641084fd7613f509f9c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -612,9 +612,13 @@ type JSONMessage struct { Progress string `json:"progress,omitempty"` Error string `json:"error,omitempty"` ID string `json:"id,omitempty"` + Time int64 `json:"time,omitempty"` } func (jm *JSONMessage) Display(out io.Writer) (error) { + if jm.Time != 0 { + fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) + } if jm.Progress != "" { fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress) } else if jm.Error != "" { From 2e4d4c9f60d0ad92ab0cc84c56c060678222c4db Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 12 Jul 2013 16:29:23 +0000 Subject: [PATCH 056/101] add since for polling, rename some vars --- api.go | 51 +++++++++++++++++++++++++++++++++++++++++---------- commands.go | 10 ++++++++-- server.go | 33 +++++++++++++++++++-------------- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/api.go b/api.go index 9d0348b6086f59fde264054443d1ce8d809e8e81..0a7f5744b153ecc2b6912f2b2304675217cfccaf 100644 --- a/api.go +++ b/api.go @@ -218,24 +218,55 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques } func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - events := make(chan utils.JSONMessage) - srv.Lock() - srv.events[r.RemoteAddr] = events - srv.Unlock() - w.Header().Set("Content-Type", "application/json") - wf := utils.NewWriteFlusher(w) - for { - event := <-events + sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (bool, error) { b, err := json.Marshal(event) if err != nil { - continue + return true, nil } _, err = wf.Write(b) if err != nil { utils.Debugf("%s", err) srv.Lock() - delete(srv.events, r.RemoteAddr) + delete(srv.listeners, r.RemoteAddr) srv.Unlock() + return false, err + } + return false, nil + } + + if err := parseForm(r); err != nil { + return err + } + listener := make(chan utils.JSONMessage) + srv.Lock() + srv.listeners[r.RemoteAddr] = listener + srv.Unlock() + since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0) + if err != nil { + since = 0 + } + w.Header().Set("Content-Type", "application/json") + wf := utils.NewWriteFlusher(w) + if since != 0 { + for _, event := range srv.events { + if event.Time >= since { + skip, err := sendEvent(wf, &event) + if skip { + continue + } + if err != nil { + return err + } + } + } + } + for { + event := <-listener + skip, err := sendEvent(wf, &event) + if skip { + continue + } + if err != nil { return err } } diff --git a/commands.go b/commands.go index 12647feead723e2d431332f8a8f73003902d71ba..2ab107bb774588d3a84f54a499df6406ca1243b3 100644 --- a/commands.go +++ b/commands.go @@ -1058,7 +1058,8 @@ func (cli *DockerCli) CmdCommit(args ...string) error { } func (cli *DockerCli) CmdEvents(args ...string) error { - cmd := Subcmd("events", "", "Get real time events from the server") + cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server") + since := cmd.String("since", "", "Show events previously created (used for polling).") if err := cmd.Parse(args); err != nil { return nil } @@ -1068,7 +1069,12 @@ func (cli *DockerCli) CmdEvents(args ...string) error { return nil } - if err := cli.stream("GET", "/events", nil, cli.out); err != nil { + v := url.Values{} + if *since != "" { + v.Set("since", *since) + } + + if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil { return err } return nil diff --git a/server.go b/server.go index 8ba90c69e3d6c5a5a2c1e7b91ad1b4d8efbf4cba..7efb850882ada16671e8ce327a9e29fd6bb2d6c2 100644 --- a/server.go +++ b/server.go @@ -35,7 +35,7 @@ func (srv *Server) ContainerKill(name string) error { if err := container.Kill(); err != nil { return fmt.Errorf("Error killing container %s: %s", name, err) } - srv.SendEvent("kill", name) + srv.LogEvent("kill", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -54,7 +54,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { if _, err := io.Copy(out, data); err != nil { return err } - srv.SendEvent("export", name) + srv.LogEvent("export", name) return nil } return fmt.Errorf("No such container: %s", name) @@ -814,7 +814,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { } return "", err } - srv.SendEvent("create", container.ShortID()) + srv.LogEvent("create", container.ShortID()) return container.ShortID(), nil } @@ -823,7 +823,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { if err := container.Restart(t); err != nil { return fmt.Errorf("Error restarting container %s: %s", name, err) } - srv.SendEvent("restart", name) + srv.LogEvent("restart", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -843,7 +843,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { if err := srv.runtime.Destroy(container); err != nil { return fmt.Errorf("Error destroying container %s: %s", name, err) } - srv.SendEvent("destroy", name) + srv.LogEvent("destroy", name) if removeVolume { // Retrieve all volumes from all remaining containers @@ -910,7 +910,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { return err } *imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)}) - srv.SendEvent("delete", utils.TruncateID(id)) + srv.LogEvent("delete", utils.TruncateID(id)) return nil } return nil @@ -954,7 +954,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro } if tagDeleted { imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) - srv.SendEvent("untag", img.ShortID()) + srv.LogEvent("untag", img.ShortID()) } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { @@ -1027,7 +1027,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if err := container.Start(hostConfig); err != nil { return fmt.Errorf("Error starting container %s: %s", name, err) } - srv.SendEvent("start", name) + srv.LogEvent("start", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1039,7 +1039,7 @@ func (srv *Server) ContainerStop(name string, t int) error { if err := container.Stop(t); err != nil { return fmt.Errorf("Error stopping container %s: %s", name, err) } - srv.SendEvent("stop", name) + srv.LogEvent("stop", name) } else { return fmt.Errorf("No such container: %s", name) } @@ -1173,15 +1173,19 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( enableCors: enableCors, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), - events: make(map[string]chan utils.JSONMessage), + events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events + listeners: make(map[string]chan utils.JSONMessage), } runtime.srv = srv return srv, nil } -func (srv *Server) SendEvent(action, id string) { - for _, c := range srv.events { - c <- utils.JSONMessage{Status: action, ID: id, Time: time.Now().Unix()} +func (srv *Server) LogEvent(action, id string) { + now := time.Now().Unix() + jm := utils.JSONMessage{Status: action, ID: id, Time: now} + srv.events = append(srv.events, jm) + for _, c := range srv.listeners { + c <- jm } } @@ -1191,5 +1195,6 @@ type Server struct { enableCors bool pullingPool map[string]struct{} pushingPool map[string]struct{} - events map[string]chan utils.JSONMessage + events []utils.JSONMessage + listeners map[string]chan utils.JSONMessage } From ec559c02b8dd70692821b3dc7f497d6fccaa88ad Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 15:44:06 +0200 Subject: [PATCH 057/101] add docs --- docs/sources/api/docker_remote_api.rst | 4 +++ docs/sources/api/docker_remote_api_v1.3.rst | 30 +++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index f8a1381c534f02d548dbc2772af8b216deb1304f..792d43f501e5d031540affa807b5ad4164057299 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -44,6 +44,10 @@ What's new **New!** List the processes running inside a container. +.. http:get:: /events: + + **New!** Monitor docker's events via streaming or via polling + Builder (/build): - Simplify the upload of the build context diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 273ec2e98d267e8402e54f52a27c79c3998d6635..69f480e45324cca846db60ebd2c344ff46b624ff 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -1059,6 +1059,36 @@ Create a new image from a container's changes :statuscode 500: server error +Monitor Docker's events +*********************** + +.. http:get:: /events + + Get events from docker, either in real time via streaming, or via polling (using `since`) + + **Example request**: + + .. sourcecode:: http + + POST /events?since=1374067924 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","time":1374067924} + {"status":"start","id":"dfdf82bd3881","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + + 3. Going further ================ From 8b3519c5f7540b99d19ed2c3163aabf9897dd5a4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 13:56:09 +0000 Subject: [PATCH 058/101] getEvents a bit simpler --- api.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index 0a7f5744b153ecc2b6912f2b2304675217cfccaf..dd3e8eed0d04449d534b2291635dfcbce7bf50d4 100644 --- a/api.go +++ b/api.go @@ -218,20 +218,21 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques } func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (bool, error) { + sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (error) { b, err := json.Marshal(event) if err != nil { - return true, nil + return fmt.Errorf("JSON error") } _, err = wf.Write(b) if err != nil { + // On error, evict the listener utils.Debugf("%s", err) srv.Lock() delete(srv.listeners, r.RemoteAddr) srv.Unlock() - return false, err + return err } - return false, nil + return nil } if err := parseForm(r); err != nil { @@ -248,10 +249,11 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ w.Header().Set("Content-Type", "application/json") wf := utils.NewWriteFlusher(w) if since != 0 { + // If since, send previous events that happened after the timestamp for _, event := range srv.events { if event.Time >= since { - skip, err := sendEvent(wf, &event) - if skip { + err := sendEvent(wf, &event) + if err != nil && err.Error() == "JSON error" { continue } if err != nil { @@ -262,8 +264,8 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } for { event := <-listener - skip, err := sendEvent(wf, &event) - if skip { + err := sendEvent(wf, &event) + if err != nil && err.Error() == "JSON error" { continue } if err != nil { From 040c3b50d0a56baf98bd1ec14ad7d59c55a4ab31 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 14:35:14 +0000 Subject: [PATCH 059/101] use non-blocking channel to prevent dead-lock and add test for server --- server.go | 5 ++++- server_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 7efb850882ada16671e8ce327a9e29fd6bb2d6c2..8eff17a947c64767baccd11bdbdac9ed117f2c44 100644 --- a/server.go +++ b/server.go @@ -1185,7 +1185,10 @@ func (srv *Server) LogEvent(action, id string) { jm := utils.JSONMessage{Status: action, ID: id, Time: now} srv.events = append(srv.events, jm) for _, c := range srv.listeners { - c <- jm + select { // non blocking channel + case c <- jm: + default: + } } } diff --git a/server_test.go b/server_test.go index 05a286aaa82b06365ab28778d452e8338bda5e29..8612b3fcea54980bd74dac3d6d748cc71455574f 100644 --- a/server_test.go +++ b/server_test.go @@ -1,7 +1,9 @@ package docker import ( + "github.com/dotcloud/docker/utils" "testing" + "time" ) func TestContainerTagImageDelete(t *testing.T) { @@ -163,3 +165,41 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) { } } + +func TestLogEvent(t *testing.T) { + runtime := mkRuntime(t) + srv := &Server{ + runtime: runtime, + events: make([]utils.JSONMessage, 0, 64), + listeners: make(map[string]chan utils.JSONMessage), + } + + srv.LogEvent("fakeaction", "fakeid") + + listener := make(chan utils.JSONMessage) + srv.Lock() + srv.listeners["test"] = listener + srv.Unlock() + + srv.LogEvent("fakeaction2", "fakeid") + + if len(srv.events) != 2 { + t.Fatalf("Expected 2 events, found %d", len(srv.events)) + } + go func() { + time.Sleep(200 * time.Millisecond) + srv.LogEvent("fakeaction3", "fakeid") + time.Sleep(200 * time.Millisecond) + srv.LogEvent("fakeaction4", "fakeid") + }() + + setTimeout(t, "Listening for events timed out", 2*time.Second, func() { + for i := 2; i < 4; i++ { + event := <-listener + if event != srv.events[i] { + t.Fatalf("Event received it different than expected") + } + } + }) + +} From ed7a4236b32f3f711c183dff8b70fbef17bae2d7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 14:54:58 +0000 Subject: [PATCH 060/101] Add tests for the api --- api_test.go | 38 ++++++++++++++++++++++++++++++++++++++ commands_test.go | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/api_test.go b/api_test.go index 17ada96eab3e486baf97ec45e1026ef73a805576..13731dbf9ef99e3b3b7bbd206c772889ed965735 100644 --- a/api_test.go +++ b/api_test.go @@ -89,6 +89,44 @@ func TestGetInfo(t *testing.T) { } } +func TestGetEvents(t *testing.T) { + runtime := mkRuntime(t) + srv := &Server{ + runtime: runtime, + events: make([]utils.JSONMessage, 0, 64), + listeners: make(map[string]chan utils.JSONMessage), + } + + srv.LogEvent("fakeaction", "fakeid") + srv.LogEvent("fakeaction2", "fakeid") + + req, err := http.NewRequest("GET", "/events?since=1", nil) + if err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + setTimeout(t, "", 500*time.Millisecond, func() { + if err := getEvents(srv, APIVERSION, r, req, nil); err != nil { + t.Fatal(err) + } + }) + + dec := json.NewDecoder(r.Body) + for i := 0; i < 2; i++ { + var jm utils.JSONMessage + if err := dec.Decode(&jm); err == io.EOF { + break + } else if err != nil { + t.Fatal(err) + } + if jm != srv.events[i] { + t.Fatalf("Event received it different than expected") + } + } + +} + func TestGetImagesJSON(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) diff --git a/commands_test.go b/commands_test.go index 233c6337d4298d1225b273c329f97ab7f9eb5df3..030fb29f955496e939599d66ce316b4f878e82ac 100644 --- a/commands_test.go +++ b/commands_test.go @@ -38,7 +38,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { f() c <- false }() - if <-c { + if <-c && msg != "" { t.Fatal(msg) } } From a41384ad7312a21fd8fe429637c8d6b5c883fa2a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 14:31:42 +0000 Subject: [PATCH 061/101] add die event --- container.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index f4a3c762ab5f7447aeeb4343c7d26bf3215a322a..1e7bfed299de8ff58667ab801530ccce3c6717d1 100644 --- a/container.go +++ b/container.go @@ -789,7 +789,9 @@ func (container *Container) monitor() { } } utils.Debugf("Process finished") - + if container.runtime != nil && container.runtime.srv != nil { + container.runtime.srv.LogEvent("die", container.ShortID()) + } exitCode := -1 if container.cmd != nil { exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() From 7aba68cd548d69e10e710029ca143b32fd291585 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 23 Jul 2013 19:55:38 +0000 Subject: [PATCH 062/101] update AUTHORS --- AUTHORS | 1 + runtime_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 7506cd885ce5e6e3dc6fe19d13f535bd70a0e924..86d03f6e12510dede68c86dba867925dccc587bd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,6 +76,7 @@ Shawn Siefkas Silas Sewell Solomon Hykes Sridhar Ratnakumar +Stefan Praszalowicz Thatcher Peskens Thomas Bikeev Thomas Hansen diff --git a/runtime_test.go b/runtime_test.go index 66d92c8100f223410e2c7f41fdda39c31f94b926..807097404d0f0a6fd257be418bbaa91478b4ebdd 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -17,12 +17,12 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var globalRuntime *Runtime From e701dce33978a0206627a02b258da6a0269c4b60 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Tue, 23 Jul 2013 13:05:06 -0700 Subject: [PATCH 063/101] Docs: Fixed navigaton links to about page and community page Website: Removed the website sources from the repo. The website sources are now hosted on github.com/dotcloud/www.docker.io/ --- docs/theme/MAINTAINERS | 2 +- docs/theme/docker/layout.html | 8 +- docs/website/MAINTAINERS | 1 - docs/website/dotcloud.yml | 2 - docs/website/gettingstarted/index.html | 220 --------------- docs/website/index.html | 359 ------------------------- docs/website/nginx.conf | 6 - docs/website/static | 1 - 8 files changed, 5 insertions(+), 594 deletions(-) delete mode 100644 docs/website/MAINTAINERS delete mode 100644 docs/website/dotcloud.yml delete mode 100644 docs/website/gettingstarted/index.html delete mode 100644 docs/website/index.html delete mode 100644 docs/website/nginx.conf delete mode 120000 docs/website/static diff --git a/docs/theme/MAINTAINERS b/docs/theme/MAINTAINERS index 6df367c0738e44ac542be025472afb70d689a56b..606a1dd746a71a329d6692fc8022548548d0e975 100644 --- a/docs/theme/MAINTAINERS +++ b/docs/theme/MAINTAINERS @@ -1 +1 @@ -Thatcher Penskens +Thatcher Peskens diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index 198cd5d7d81c3cd599feb463b5c218f354fc2d29..0b22f22fabdf85f4db0b31551ab7293d52929ef5 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -68,12 +68,12 @@ diff --git a/docs/website/MAINTAINERS b/docs/website/MAINTAINERS deleted file mode 100644 index 6df367c0738e44ac542be025472afb70d689a56b..0000000000000000000000000000000000000000 --- a/docs/website/MAINTAINERS +++ /dev/null @@ -1 +0,0 @@ -Thatcher Penskens diff --git a/docs/website/dotcloud.yml b/docs/website/dotcloud.yml deleted file mode 100644 index 5a8f50f9e9e3059f2993959edc1a8cbd440045b3..0000000000000000000000000000000000000000 --- a/docs/website/dotcloud.yml +++ /dev/null @@ -1,2 +0,0 @@ -www: - type: static \ No newline at end of file diff --git a/docs/website/gettingstarted/index.html b/docs/website/gettingstarted/index.html deleted file mode 100644 index de0cc3512da7c986898c2080951cd2b501bab59d..0000000000000000000000000000000000000000 --- a/docs/website/gettingstarted/index.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - - Docker - the Linux container runtime - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - -

GETTING STARTED

-
- -
- -
- -
-
- Docker is still under heavy development. It should not yet be used in production. Check the repo for recent progress. -
-
-
-
-

- - Installing on Ubuntu

- -

Requirements

-
    -
  • Ubuntu 12.04 (LTS) (64-bit)
  • -
  • or Ubuntu 12.10 (quantal) (64-bit)
  • -
  • The 3.8 Linux Kernel
  • -
-
    -
  1. -

    Install dependencies

    - The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module. -
    sudo apt-get install linux-image-extra-`uname -r`
    - - -
  2. -
  3. -

    Install Docker

    -

    Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.

    -

    This may import a new GPG key (key 63561DC6: public key "Launchpad PPA for dotcloud team" imported).

    -
    -
    sudo apt-get install software-properties-common
    -
    sudo add-apt-repository ppa:dotcloud/lxc-docker
    -
    sudo apt-get update
    -
    sudo apt-get install lxc-docker
    -
    - - -
  4. - -
  5. -

    Run!

    - -
    -
    docker run -i -t ubuntu /bin/bash
    -
    -
  6. - Continue with the Hello world example.
    - Or check more detailed installation instructions -
-
- -
-

Contributing to Docker

- -

Want to hack on Docker? Awesome! We have some instructions to get you started. They are probably not perfect, please let us know if anything feels wrong or incomplete.

-
- -
-
-
-

Quick install on other operating systems

-

For other operating systems we recommend and provide a streamlined install with virtualbox, - vagrant and an Ubuntu virtual machine.

- - - -
- -
-

Questions? Want to get in touch?

-

There are several ways to get in touch:

-

Join the discussion on IRC. We can be found in the #docker channel on chat.freenode.net

-

Discussions happen on our google group: docker-club at googlegroups.com

-

All our development and decisions are made out in the open on Github github.com/dotcloud/docker

-

Get help on using Docker by asking on Stackoverflow

-

And of course, tweet your tweets to twitter.com/getdocker

-
- - -
-
- Fill out my online form. -
- -
- -
-
-
- - -
- -
- - - - - - - - - - - diff --git a/docs/website/index.html b/docs/website/index.html deleted file mode 100644 index f6f4efbccba45c0f7fd6296a6f25722ea2dac146..0000000000000000000000000000000000000000 --- a/docs/website/index.html +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - - - - - - Docker - the Linux container engine - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-
- -
-
- docker letters - -

The Linux container engine

-
- -
- -
- Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider. -
- -
- - - - -
- -
-
- -
-
- -
-
-
-
-
- -
-
- -
-
-

Heterogeneous payloads

-

Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.

-

Any server

-

Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.

-

Isolation

-

Docker isolates processes from each other and from the underlying host, using lightweight containers.

-

Repeatability

-

Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.

-
-
-
-
- we're hiring -
-
-

Do you think it is cool to hack on docker? Join us!

-
    -
  • Work on open source
  • -
  • Program in Go
  • -
- read more -
-
- -
-
-
-
-

New! Docker Index

- On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them. - -

- -
- DOCKER index -
-
-   - - -
-
-
- Fill out my online form. -
- -
-
-
- -
- -
- -
-
-
- - Mitchell Hashimoto ‏@mitchellh: Docker launched today. It is incredible. They’re also working RIGHT NOW on a Vagrant provider. LXC is COMING!! -
-
-
-
- - Adam Jacob ‏@adamhjk: Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think. -
-
-
-
-
-
- - Matt Townsend ‏@mtownsend: I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego. -
-
-
-
- - Rob Harrop ‏@robertharrop: Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter. -
-
-
-
-
-
- - John Willis @botchagalupe: IMHO docker is to paas what chef was to Iaas 4 years ago -
-
-
-
- - John Feminella ‏@superninjarobot: So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam! -
-
-
-
-
-
- - David Romulan ‏@destructuring: I haven't had this much fun since AWS -
-
-
-
- - Ricardo Gladwell ‏@rgladwell: wow @getdocker is either amazing or totally stupid -
-
- -
-
- -
-
-
- -
- -

Notable features

- -
    -
  • Filesystem isolation: each process container runs in a completely separate root filesystem.
  • -
  • Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.
  • -
  • Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.
  • -
  • Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.
  • -
  • Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.
  • -
  • Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.
  • -
  • Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.
  • -
- -

Under the hood

- -

Under the hood, Docker is built on the following components:

- -
    -
  • The cgroup and namespacing capabilities of the Linux kernel;
  • -
  • AUFS, a powerful union filesystem with copy-on-write capabilities;
  • -
  • The Go programming language;
  • -
  • lxc, a set of convenience scripts to simplify the creation of linux containers.
  • -
- -

Who started it

-

- Docker is an open-source implementation of the deployment engine which powers dotCloud, a popular Platform-as-a-Service.

- -

It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands - of applications and databases. -

- -
-
- -
- - -
-

Twitter

- - -
- -
-
- -
- - -
- -
- - - - - - - - - - - - diff --git a/docs/website/nginx.conf b/docs/website/nginx.conf deleted file mode 100644 index 97ffd2c0e55c2c4ca792acf799fb7d22f91c5cd8..0000000000000000000000000000000000000000 --- a/docs/website/nginx.conf +++ /dev/null @@ -1,6 +0,0 @@ - -# rule to redirect original links created when hosted on github pages -rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent; - -# rewrite the stuff which was on the current page -rewrite ^/gettingstarted.html$ /gettingstarted/ permanent; diff --git a/docs/website/static b/docs/website/static deleted file mode 120000 index 95bc97aa105a4bb706aae43ce342827ab881714d..0000000000000000000000000000000000000000 --- a/docs/website/static +++ /dev/null @@ -1 +0,0 @@ -../theme/docker/static \ No newline at end of file From ede1e6d4754f3cffeac72f5d760fe4d87c5ae570 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Tue, 23 Jul 2013 17:05:13 -0400 Subject: [PATCH 064/101] Rename: VersionChecker->VersionInfo. --- registry/registry.go | 16 ++++++++-------- runtime_test.go | 12 ++++++------ server.go | 22 +++++++++++----------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 6ba80cbea53db9629be37f40cb8e51ce101ae32d..e6f4f592e23463ab10e845c76dc002d640a685fd 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -98,9 +98,9 @@ func ResolveRepositoryName(reposName string) (string, string, error) { return endpoint, reposName, err } -// VersionChecker is used to model entities which has a version. +// VersionInfo is used to model entities which has a version. // It is basically a tupple with name and version. -type VersionChecker interface { +type VersionInfo interface { Name() string Version() string } @@ -114,7 +114,7 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { // Set the user agent field in the header based on the versions provided // in NewRegistry() and extra. -func (r *Registry) setUserAgent(req *http.Request, extra ...VersionChecker) { +func (r *Registry) setUserAgent(req *http.Request, extra ...VersionInfo) { if len(r.baseVersions)+len(extra) == 0 { return } @@ -569,11 +569,11 @@ type ImgData struct { type Registry struct { client *http.Client authConfig *auth.AuthConfig - baseVersions []VersionChecker + baseVersions []VersionInfo baseVersionsStr string } -func validVersion(version VersionChecker) bool { +func validVersion(version VersionInfo) bool { stopChars := " \t\r\n/" if strings.ContainsAny(version.Name(), stopChars) { return false @@ -586,11 +586,11 @@ func validVersion(version VersionChecker) bool { // Convert versions to a string and append the string to the string base. // -// Each VersionChecker will be converted to a string in the format of +// Each VersionInfo will be converted to a string in the format of // "product/version", where the "product" is get from the Name() method, while // version is get from the Version() method. Several pieces of verson information // will be concatinated and separated by space. -func appendVersions(base string, versions ...VersionChecker) string { +func appendVersions(base string, versions ...VersionInfo) string { if len(versions) == 0 { return base } @@ -612,7 +612,7 @@ func appendVersions(base string, versions ...VersionChecker) string { return buf.String() } -func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...VersionChecker) (r *Registry, err error) { +func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...VersionInfo) (r *Registry, err error) { httpTransport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, diff --git a/runtime_test.go b/runtime_test.go index 6b94e5ce2c7f79fb5aee39ee55e884c455dafcb8..74ca09cc8debce55cb3d94135bc51a4cce5a3e3d 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -18,12 +18,12 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var globalRuntime *Runtime diff --git a/server.go b/server.go index 925e4e3386708fef9f4a203795e8650de721c10b..62c0243820971d5d3e9153a428a8924e914d46b9 100644 --- a/server.go +++ b/server.go @@ -26,21 +26,21 @@ func (srv *Server) DockerVersion() APIVersion { } } -// plainVersionChecker is a simple implementation of -// the interface VersionChecker, which is used +// simpleVersionInfo is a simple implementation of +// the interface VersionInfo, which is used // to provide version information for some product, // component, etc. It stores the product name and the version // in string and returns them on calls to Name() and Version(). -type plainVersionChecker struct { +type simpleVersionInfo struct { name string version string } -func (v *plainVersionChecker) Name() string { +func (v *simpleVersionInfo) Name() string { return v.name } -func (v *plainVersionChecker) Version() string { +func (v *simpleVersionInfo) Version() string { return v.version } @@ -48,20 +48,20 @@ func (v *plainVersionChecker) Version() string { // docker, go, git-commit (of the docker) and the host's kernel. // // Such information will be used on call to NewRegistry(). -func (srv *Server) versionCheckers() []registry.VersionChecker { +func (srv *Server) versionCheckers() []registry.VersionInfo { v := srv.DockerVersion() - ret := make([]registry.VersionChecker, 0, 4) - ret = append(ret, &plainVersionChecker{"docker", v.Version}) + ret := make([]registry.VersionInfo, 0, 4) + ret = append(ret, &simpleVersionInfo{"docker", v.Version}) if len(v.GoVersion) > 0 { - ret = append(ret, &plainVersionChecker{"go", v.GoVersion}) + ret = append(ret, &simpleVersionInfo{"go", v.GoVersion}) } if len(v.GitCommit) > 0 { - ret = append(ret, &plainVersionChecker{"git-commit", v.GitCommit}) + ret = append(ret, &simpleVersionInfo{"git-commit", v.GitCommit}) } kernelVersion, err := utils.GetKernelVersion() if err == nil { - ret = append(ret, &plainVersionChecker{"kernel", kernelVersion.String()}) + ret = append(ret, &simpleVersionInfo{"kernel", kernelVersion.String()}) } return ret From 1ae54707a0ee1f690a7dca17d83b5417e83704c3 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Tue, 23 Jul 2013 17:17:31 -0400 Subject: [PATCH 065/101] versionCheckers()->versionInfos(). --- server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 62c0243820971d5d3e9153a428a8924e914d46b9..b706ceda12cb1ab55d77ef3fa72a497c3250f65b 100644 --- a/server.go +++ b/server.go @@ -48,7 +48,7 @@ func (v *simpleVersionInfo) Version() string { // docker, go, git-commit (of the docker) and the host's kernel. // // Such information will be used on call to NewRegistry(). -func (srv *Server) versionCheckers() []registry.VersionInfo { +func (srv *Server) versionInfos() []registry.VersionInfo { v := srv.DockerVersion() ret := make([]registry.VersionInfo, 0, 4) ret = append(ret, &simpleVersionInfo{"docker", v.Version}) @@ -96,7 +96,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { } func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { - r, err := registry.NewRegistry(srv.runtime.root, nil, srv.versionCheckers()...) + r, err := registry.NewRegistry(srv.runtime.root, nil, srv.versionInfos()...) if err != nil { return nil, err } @@ -511,7 +511,7 @@ func (srv *Server) poolRemove(kind, key string) error { } func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionCheckers()...) + r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionInfos()...) if err != nil { return err } @@ -728,7 +728,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) - r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionCheckers()...) + r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionInfos()...) if err2 != nil { return err2 } From bc823acc255a786b00a7b2759b67fd7b3256fce3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Jul 2013 17:27:49 -0700 Subject: [PATCH 066/101] Reimplement old Commands unit tests in order to insure behavior --- commands.go | 4 +- commands_test.go | 223 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 225 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index b0e32162e6e4e5952b3c73e0c05b675a9665212a..44498b04da12f3c363432060b012d5318ad00bfd 100644 --- a/commands.go +++ b/commands.go @@ -1378,7 +1378,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if config.AttachStdin || config.AttachStdout || config.AttachStderr { if config.Tty { if err := cli.monitorTtySize(runResult.ID); err != nil { - return err + utils.Debugf("Error monitoring TTY size: %s\n", err) } } @@ -1555,6 +1555,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea receiveStdout := utils.Go(func() error { _, err := io.Copy(out, br) + utils.Debugf("[hijack] End of stdout") return err }) @@ -1569,6 +1570,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea sendStdin := utils.Go(func() error { if in != nil { io.Copy(rwc, in) + utils.Debugf("[hijack] End of stdin") } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { diff --git a/commands_test.go b/commands_test.go index 233c6337d4298d1225b273c329f97ab7f9eb5df3..88902b85030f6bd77b08dff66f8f770f031ae21e 100644 --- a/commands_test.go +++ b/commands_test.go @@ -73,7 +73,7 @@ func TestRunHostname(t *testing.T) { t.Fatal(err) } }() - utils.Debugf("--") + setTimeout(t, "Reading command output time out", 2*time.Second, func() { cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') if err != nil { @@ -90,6 +90,157 @@ func TestRunHostname(t *testing.T) { } +func TestRunExit(t *testing.T) { + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + c1 := make(chan struct{}) + go func() { + cli.CmdRun("-i", unitTestImageID, "/bin/cat") + close(c1) + }() + + setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + container := globalRuntime.List()[0] + + // Closing /bin/cat stdin, expect it to exit + if err := stdin.Close(); err != nil { + t.Fatal(err) + } + + // as the process exited, CmdRun must finish and unblock. Wait for it + setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() { + <-c1 + + go func() { + cli.CmdWait(container.ID) + }() + + if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil { + t.Fatal(err) + } + }) + + // Make sure that the client has been disconnected + setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() { + // Expecting pipe i/o error, just check that read does not block + stdin.Read([]byte{}) + }) + + // Cleanup pipes + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } +} + +// Expected behaviour: the process dies when the client disconnects +func TestRunDisconnect(t *testing.T) { + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + c1 := make(chan struct{}) + go func() { + // We're simulating a disconnect so the return value doesn't matter. What matters is the + // fact that CmdRun returns. + cli.CmdRun("-i", unitTestImageID, "/bin/cat") + close(c1) + }() + + setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + // Close pipes (simulate disconnect) + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } + + // as the pipes are close, we expect the process to die, + // therefore CmdRun to unblock. Wait for CmdRun + setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() { + <-c1 + }) + + // Client disconnect after run -i should cause stdin to be closed, which should + // cause /bin/cat to exit. + setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() { + container := globalRuntime.List()[0] + container.Wait() + if container.State.Running { + t.Fatalf("/bin/cat is still running after closing stdin") + } + }) +} + +// Expected behaviour: the process dies when the client disconnects +func TestRunDisconnectTty(t *testing.T) { + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + c1 := make(chan struct{}) + go func() { + // We're simulating a disconnect so the return value doesn't matter. What matters is the + // fact that CmdRun returns. + if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil { + utils.Debugf("Error CmdRun: %s\n", err) + } + + close(c1) + }() + + setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { + for { + // Client disconnect after run -i should keep stdin out in TTY mode + l := globalRuntime.List() + if len(l) == 1 && l[0].State.Running { + break + } + time.Sleep(10 * time.Millisecond) + } + }) + + // Client disconnect after run -i should keep stdin out in TTY mode + container := globalRuntime.List()[0] + + setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + // Close pipes (simulate disconnect) + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } + + // In tty mode, we expect the process to stay alive even after client's stdin closes. + // Do not wait for run to finish + + // Give some time to monitor to do his thing + container.WaitTimeout(500 * time.Millisecond) + if !container.State.Running { + t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)") + } +} + // TestAttachStdin checks attaching to stdin without stdout and stderr. // 'docker run -i -a stdin' should sends the client's stdin to the command, // then detach from it and print the container id. @@ -157,3 +308,73 @@ func TestRunAttachStdin(t *testing.T) { } } } + +// Expected behaviour, the process stays alive when the client disconnects +func TestAttachDisconnect(t *testing.T) { + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + go func() { + // Start a process in daemon mode + if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil { + utils.Debugf("Error CmdRun: %s\n", err) + } + }() + + setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() { + if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil { + t.Fatal(err) + } + }) + + setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { + for { + l := globalRuntime.List() + if len(l) == 1 && l[0].State.Running { + break + } + time.Sleep(10 * time.Millisecond) + } + }) + + container := globalRuntime.List()[0] + + // Attach to it + c1 := make(chan struct{}) + go func() { + // We're simulating a disconnect so the return value doesn't matter. What matters is the + // fact that CmdAttach returns. + cli.CmdAttach(container.ID) + close(c1) + }() + + setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + // Close pipes (client disconnects) + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } + + // Wait for attach to finish, the client disconnected, therefore, Attach finished his job + setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { + <-c1 + }) + + // We closed stdin, expect /bin/cat to still be running + // Wait a little bit to make sure container.monitor() did his thing + err := container.WaitTimeout(500 * time.Millisecond) + if err == nil || !container.State.Running { + t.Fatalf("/bin/cat is not running after closing stdin") + } + + // Try to avoid the timeoout in destroy. Best effort, don't check error + cStdin, _ := container.StdinPipe() + cStdin.Close() + container.Wait() +} From 78c02d038f90b111f7f8ad306f9573c0f64b370c Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 23 Jul 2013 18:13:53 -0700 Subject: [PATCH 067/101] Cleaned up long lines, switched graphic to Docker logo. General cleanup. --- README.md | 303 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 211 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 823e48496a938e3377e1bd468078e466e2dbfea4..96da13feaf1de0f4e15f0e93fc454703a0d3d8c7 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,129 @@ Docker: the Linux container engine ================================== -Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers. +Docker is an open-source engine which automates the deployment of +applications as highly portable, self-sufficient containers. -Docker containers are both *hardware-agnostic* and *platform-agnostic*. This means that they can run anywhere, from your -laptop to the largest EC2 compute instance and everything in between - and they don't require that you use a particular -language, framework or packaging system. That makes them great building blocks for deploying and scaling web apps, databases -and backend services without depending on a particular stack or provider. +Docker containers are both *hardware-agnostic* and +*platform-agnostic*. This means that they can run anywhere, from your +laptop to the largest EC2 compute instance and everything in between - +and they don't require that you use a particular language, framework +or packaging system. That makes them great building blocks for +deploying and scaling web apps, databases and backend services without +depending on a particular stack or provider. -Docker is an open-source implementation of the deployment engine which powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service. -It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands -of applications and databases. +Docker is an open-source implementation of the deployment engine which +powers [dotCloud](http://dotcloud.com), a popular +Platform-as-a-Service. It benefits directly from the experience +accumulated over several years of large-scale operation and support of +hundreds of thousands of applications and databases. -![Docker L](docs/sources/concepts/images/lego_docker.jpg "Docker") +![Docker L](docs/sources/concepts/images/dockerlogo-h.png "Docker") ## Better than VMs -A common method for distributing applications and sandbox their execution is to use virtual machines, or VMs. Typical VM formats -are VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory these formats should allow every developer to -automatically package their application into a "machine" for easy distribution and deployment. In practice, that almost never -happens, for a few reasons: - - * *Size*: VMs are very large which makes them impractical to store and transfer. - * *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and - large-scale deployment of cpu and memory-intensive applications on large numbers of machines. - * *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead. - * *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most: - building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery. - -By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization, -containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary -for containerization, including Linux with [openvz](http://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net), - Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html). - -Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves -all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead, -they are completely portable and are designed from the ground up with an application-centric design. - -The best part: because docker operates at the OS level, it can still be run inside a VM! +A common method for distributing applications and sandbox their +execution is to use virtual machines, or VMs. Typical VM formats are +VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In +theory these formats should allow every developer to automatically +package their application into a "machine" for easy distribution and +deployment. In practice, that almost never happens, for a few reasons: + + * *Size*: VMs are very large which makes them impractical to store + and transfer. + * *Performance*: running VMs consumes significant CPU and memory, + which makes them impractical in many scenarios, for example local + development of multi-tier applications, and large-scale deployment + of cpu and memory-intensive applications on large numbers of + machines. + * *Portability*: competing VM environments don't play well with each + other. Although conversion tools do exist, they are limited and + add even more overhead. + * *Hardware-centric*: VMs were designed with machine operators in + mind, not software developers. As a result, they offer very + limited tooling for what developers need most: building, testing + and running their software. For example, VMs offer no facilities + for application versioning, monitoring, configuration, logging or + service discovery. + +By contrast, Docker relies on a different sandboxing method known as +*containerization*. Unlike traditional virtualization, +containerization takes place at the kernel level. Most modern +operating system kernels now support the primitives necessary for +containerization, including Linux with [openvz](http://openvz.org), +[vserver](http://linux-vserver.org) and more recently +[lxc](http://lxc.sourceforge.net), Solaris with +[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) +and FreeBSD with +[Jails](http://www.freebsd.org/doc/handbook/jails.html). + +Docker builds on top of these low-level primitives to offer developers +a portable format and runtime environment that solves all 4 +problems. Docker containers are small (and their transfer can be +optimized with layers), they have basically zero memory and cpu +overhead, they are completely portable and are designed from the +ground up with an application-centric design. + +The best part: because ``docker`` operates at the OS level, it can +still be run inside a VM! ## Plays well with others -Docker does not require that you buy into a particular programming language, framework, packaging system or configuration language. +Docker does not require that you buy into a particular programming +language, framework, packaging system or configuration language. -Is your application a unix process? Does it use files, tcp connections, environment variables, standard unix streams and command-line -arguments as inputs and outputs? Then docker can run it. +Is your application a Unix process? Does it use files, tcp +connections, environment variables, standard Unix streams and +command-line arguments as inputs and outputs? Then ``docker`` can run +it. -Can your application's build be expressed as a sequence of such commands? Then docker can build it. +Can your application's build be expressed as a sequence of such +commands? Then ``docker`` can build it. ## Escape dependency hell -A common problem for developers is the difficulty of managing all their application's dependencies in a simple and automated way. +A common problem for developers is the difficulty of managing all +their application's dependencies in a simple and automated way. This is usually difficult for several reasons: - * *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules, - internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work - well with each other, requiring awkward custom integrations. - - * Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease - - but they all handle them in different and incompatible ways, which again forces the developer to do extra work. + * *Cross-platform dependencies*. Modern applications often depend on + a combination of system libraries and binaries, language-specific + packages, framework-specific modules, internal components + developed for another project, etc. These dependencies live in + different "worlds" and require different tools - these tools + typically don't work well with each other, requiring awkward + custom integrations. + + * Conflicting dependencies. Different applications may depend on + different versions of the same dependency. Packaging tools handle + these situations with various degrees of ease - but they all + handle them in different and incompatible ways, which again forces + the developer to do extra work. - * Custom dependencies. A developer may need to prepare a custom version of their application's dependency. Some packaging systems can handle custom versions of a dependency, - others can't - and all of them handle it differently. + * Custom dependencies. A developer may need to prepare a custom + version of their application's dependency. Some packaging systems + can handle custom versions of a dependency, others can't - and all + of them handle it differently. -Docker solves dependency hell by giving the developer a simple way to express *all* their application's dependencies in one place, -and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't -*replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers. +Docker solves dependency hell by giving the developer a simple way to +express *all* their application's dependencies in one place, and +streamline the process of assembling them. If this makes you think of +[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't +*replace* your favorite packaging systems. It simply orchestrates +their use in a simple and repeatable way. How does it do that? With +layers. -Docker defines a build as running a sequence of unix commands, one after the other, in the same container. Build commands modify the contents of the container -(usually by installing new files on the filesystem), the next command modifies it some more, etc. Since each build command inherits the result of the previous -commands, the *order* in which the commands are executed expresses *dependencies*. +Docker defines a build as running a sequence of Unix commands, one +after the other, in the same container. Build commands modify the +contents of the container (usually by installing new files on the +filesystem), the next command modifies it some more, etc. Since each +build command inherits the result of the previous commands, the +*order* in which the commands are executed expresses *dependencies*. -Here's a typical docker build process: +Here's a typical Docker build process: ```bash from ubuntu:12.10 @@ -87,7 +136,8 @@ run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xz run cd helloflask-master && pip install -r requirements.txt ``` -Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a unix command in a container. +Note that Docker doesn't care *how* dependencies are built - as long +as they can be built by running a Unix command in a container. Install instructions @@ -103,8 +153,9 @@ curl get.docker.io | sudo sh -x Binary installs ---------------- -Docker supports the following binary installation methods. -Note that some methods are community contributions and not yet officially supported. +Docker supports the following binary installation methods. Note that +some methods are community contributions and not yet officially +supported. * [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/) * [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/) @@ -115,15 +166,15 @@ Note that some methods are community contributions and not yet officially suppor Installing from source ---------------------- -1. Make sure you have a [Go language](http://golang.org/doc/install) compiler and [git](http://git-scm.com) installed. - +1. Make sure you have a [Go language](http://golang.org/doc/install) +compiler and [git](http://git-scm.com) installed. 2. Checkout the source code ```bash git clone http://github.com/dotcloud/docker ``` -3. Build the docker binary +3. Build the ``docker`` binary ```bash cd docker @@ -134,17 +185,20 @@ Installing from source Usage examples ============== -First run the docker daemon ---------------------------- +First run the ``docker`` daemon +------------------------------- -All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type: +All the examples assume your machine is running the ``docker`` +daemon. To run the ``docker`` daemon in the background, simply type: ```bash # On a production system you want this running in an init script sudo docker -d & ``` -Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client can run from any account. +Now you can run ``docker`` in client mode: all commands will be +forwarded to the ``docker`` daemon, so the client can run from any +account. ```bash # Now you can run docker commands from any account. @@ -152,7 +206,7 @@ docker help ``` -Throwaway shell in a base ubuntu image +Throwaway shell in a base Ubuntu image -------------------------------------- ```bash @@ -202,7 +256,8 @@ docker commit -m "Installed curl" $CONTAINER $USER/betterbase docker push $USER/betterbase ``` -A list of publicly available images is [available here](https://github.com/dotcloud/docker/wiki/Public-docker-images). +A list of publicly available images is [available +here](https://github.com/dotcloud/docker/wiki/Public-docker-images). Expose a service on a TCP port ------------------------------ @@ -229,32 +284,40 @@ Under the hood Under the hood, Docker is built on the following components: - -* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel; - -* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities; - +* The + [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) + and + [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) + capabilities of the Linux kernel; +* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union + filesystem with copy-on-write capabilities; * The [Go](http://golang.org) programming language; - -* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers. +* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to + simplify the creation of Linux containers. Contributing to Docker ====================== -Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docs.docker.io/en/latest/contributing/contributing/ +Want to hack on Docker? Awesome! There are instructions to get you +started on the website: +http://docs.docker.io/en/latest/contributing/contributing/ -They are probably not perfect, please let us know if anything feels wrong or incomplete. +They are probably not perfect, please let us know if anything feels +wrong or incomplete. Note ---- -We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources. -Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/tree/master/docs/README.md +We also keep the documentation in this repository. The website +documentation is generated using Sphinx using these sources. Please +find it under docs/sources/ and read more about it +https://github.com/dotcloud/docker/tree/master/docs/README.md -Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome. +Please feel free to fix / update the documentation and send us pull +requests. More tutorials are also welcome. Setting up a dev environment @@ -289,42 +352,96 @@ Run the `go install` command (above) to recompile docker. What is a Standard Container? ============================= -Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in -a format that is self-describing and portable, so that any compliant runtime can run it without extra dependencies, regardless of the underlying machine and the contents of the container. +Docker defines a unit of software delivery called a Standard +Container. The goal of a Standard Container is to encapsulate a +software component and all its dependencies in a format that is +self-describing and portable, so that any compliant runtime can run it +without extra dependencies, regardless of the underlying machine and +the contents of the container. -The spec for Standard Containers is currently a work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment. +The spec for Standard Containers is currently a work in progress, but +it is very straightforward. It mostly defines 1) an image format, 2) a +set of standard operations, and 3) an execution environment. -A great analogy for this is the shipping container. Just like how Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery. +A great analogy for this is the shipping container. Just like how +Standard Containers are a fundamental unit of software delivery, +shipping containers are a fundamental unit of physical delivery. ### 1. STANDARD OPERATIONS -Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged. +Just like shipping containers, Standard Containers define a set of +STANDARD OPERATIONS. Shipping containers can be lifted, stacked, +locked, loaded, unloaded and labelled. Similarly, Standard Containers +can be started, stopped, copied, snapshotted, downloaded, uploaded and +tagged. ### 2. CONTENT-AGNOSTIC -Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts. +Just like shipping containers, Standard Containers are +CONTENT-AGNOSTIC: all standard operations have the same effect +regardless of the contents. A shipping container will be stacked in +exactly the same way whether it contains Vietnamese powder coffee or +spare Maserati parts. Similarly, Standard Containers are started or +uploaded in the same way whether they contain a postgres database, a +php application with its dependencies and application server, or Java +build artifacts. ### 3. INFRASTRUCTURE-AGNOSTIC -Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions. +Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be +transported to thousands of facilities around the world, and +manipulated by a wide variety of equipment. A shipping container can +be packed in a factory in Ukraine, transported by truck to the nearest +routing center, stacked onto a train, loaded into a German boat by an +Australian-built crane, stored in a warehouse at a US facility, +etc. Similarly, a standard container can be bundled on my laptop, +uploaded to S3, downloaded, run and snapshotted by a build server at +Equinix in Virginia, uploaded to 10 staging servers in a home-made +Openstack cluster, then sent to 30 production instances across 3 EC2 +regions. ### 4. DESIGNED FOR AUTOMATION -Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon. - -Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods. - -Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider. +Because they offer the same standard operations regardless of content +and infrastructure, Standard Containers, just like their physical +counterparts, are extremely well-suited for automation. In fact, you +could say automation is their secret weapon. + +Many things that once required time-consuming and error-prone human +effort can now be programmed. Before shipping containers, a bag of +powder coffee was hauled, dragged, dropped, rolled and stacked by 10 +different people in 10 different locations by the time it reached its +destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The +process was slow, inefficient and cost a fortune - and was entirely +different depending on the facility and the type of goods. + +Similarly, before Standard Containers, by the time a software +component ran in production, it had been individually built, +configured, bundled, documented, patched, vendored, templated, tweaked +and instrumented by 10 different people on 10 different +computers. Builds failed, libraries conflicted, mirrors crashed, +post-it notes were lost, logs were misplaced, cluster updates were +half-broken. The process was slow, inefficient and cost a fortune - +and was entirely different depending on the language and +infrastructure provider. ### 5. INDUSTRIAL-GRADE DELIVERY -There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded onto the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away. +There are 17 million shipping containers in existence, packed with +every physical good imaginable. Every single one of them can be loaded +onto the same boats, by the same cranes, in the same facilities, and +sent anywhere in the World with incredible efficiency. It is +embarrassing to think that a 30 ton shipment of coffee can safely +travel half-way across the World in *less time* than it takes a +software team to deliver its code from one datacenter to another +sitting 10 miles away. -With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality. +With Standard Containers we can put an end to that embarrassment, by +making INDUSTRIAL-GRADE DELIVERY of software a reality. @@ -374,8 +491,10 @@ Standard Container Specification ### Legal -Transfers of Docker shall be in accordance with applicable export controls of any country and all other applicable -legal requirements. Docker shall not be distributed or downloaded to or in Cuba, Iran, North Korea, Sudan or Syria -and shall not be distributed or downloaded to any person on the Denied Persons List administered by the U.S. +Transfers of Docker shall be in accordance with applicable export +controls of any country and all other applicable legal requirements. +Docker shall not be distributed or downloaded to or in Cuba, Iran, +North Korea, Sudan or Syria and shall not be distributed or downloaded +to any person on the Denied Persons List administered by the U.S. Department of Commerce. From f4b41e1a6c2c5d531451bf2feeb3877e03eb8c1c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 12:28:22 +0000 Subject: [PATCH 068/101] fix tests --- api.go | 2 +- auth/auth.go | 1 + auth/auth_test.go | 14 ++++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api.go b/api.go index 94c1a3b8d28cfed212493c12d629185db02baacd..834c41a68cbacc77ee3fd99cea3e61f073752e5e 100644 --- a/api.go +++ b/api.go @@ -179,7 +179,7 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques } func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (error) { + sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error { b, err := json.Marshal(event) if err != nil { return fmt.Errorf("JSON error") diff --git a/auth/auth.go b/auth/auth.go index 8f4d09fb8fe7a1afa515e38ebfee85e722ee18e9..bffed498078d760e77a7bfc04cc10d6ca310d5e4 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -102,6 +102,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { if err != nil { return nil, err } + authConfig.Auth = "" configFile.Configs[k] = authConfig } } diff --git a/auth/auth_test.go b/auth/auth_test.go index d036de7364ba11afcd692e467ff8e9b6218a9003..458a505ea2f6256ac60d1942a245f764cd25c997 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -11,7 +11,9 @@ import ( func TestEncodeAuth(t *testing.T) { newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} authStr := encodeAuth(newAuthConfig) - decAuthConfig, err := decodeAuth(authStr) + decAuthConfig := &AuthConfig{} + var err error + decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) if err != nil { t.Fatal(err) } @@ -29,8 +31,8 @@ func TestEncodeAuth(t *testing.T) { func TestLogin(t *testing.T) { 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, false) + authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"} + status, err := Login(authConfig) if err != nil { t.Fatal(err) } @@ -49,8 +51,8 @@ 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, false) + authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+"+token+"@example.com"} + status, err := Login(authConfig) if err != nil { t.Fatal(err) } @@ -60,7 +62,7 @@ func TestCreateAccount(t *testing.T) { t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) } - status, err = Login(authConfig, false) + status, err = Login(authConfig) if err == nil { t.Fatalf("Expected error but found nil instead") } From 6057e6ad70ba4550df358e89b9a7e1da61666604 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 13:35:38 +0000 Subject: [PATCH 069/101] add kernel version --- api_params.go | 1 + commands.go | 1 + docs/sources/api/docker_remote_api_v1.3.rst | 5 ++++- server.go | 5 +++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/api_params.go b/api_params.go index fcffb0434651f59e451f8d8538e4208fd2751c1a..0214b89fdb0e13dc3eceb3df26571d2b04f16467 100644 --- a/api_params.go +++ b/api_params.go @@ -26,6 +26,7 @@ type APIInfo struct { SwapLimit bool `json:",omitempty"` LXCVersion string `json:",omitempty"` NEventsListener int `json:",omitempty"` + KernelVersion string `json:",omitempty"` } type APITop struct { diff --git a/commands.go b/commands.go index c0e349c8a31e3f9e54d8f937c93f3ea5891d6f21..a868b5e5b20121c4b36596a4a4d8e3942d821f5b 100644 --- a/commands.go +++ b/commands.go @@ -473,6 +473,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines) fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion) fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener) + fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion) } if !out.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 69f480e45324cca846db60ebd2c344ff46b624ff..401855365e34525a96bd1ad931a1ef11565fec27 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -989,7 +989,10 @@ Display system-wide information "NFd": 11, "NGoroutines":21, "MemoryLimit":true, - "SwapLimit":false + "SwapLimit":false, + "EventsListeners":"0", + "LXCVersion":"0.7.5", + "KernelVersion":"3.8.0-19-generic" } :statuscode 200: no error diff --git a/server.go b/server.go index 1395007911128bfc4429c9d79f62be42bb5dd49a..147c5fa4360bc42794cd4011142de7f4e4a0fe34 100644 --- a/server.go +++ b/server.go @@ -218,6 +218,10 @@ func (srv *Server) DockerInfo() *APIInfo { lxcVersion = strings.TrimSpace(strings.SplitN(string(output), ":", 2)[1]) } } + kernelVersion := "" + if kv, err := utils.GetKernelVersion(); err == nil { + kernelVersion = kv.String() + } return &APIInfo{ Containers: len(srv.runtime.List()), @@ -229,6 +233,7 @@ func (srv *Server) DockerInfo() *APIInfo { NGoroutines: runtime.NumGoroutine(), LXCVersion: lxcVersion, NEventsListener: len(srv.events), + KernelVersion: kernelVersion, } } From fd9ad1a19469d07944ca9b417861d63ecec2ef42 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 24 Jul 2013 15:48:18 -0700 Subject: [PATCH 070/101] Fixes #505 - Make sure all output is send on the network before closing --- api_test.go | 8 +++++++- container.go | 13 +++++++------ runtime_test.go | 19 +++++++++++++------ z_final_test.go | 7 ++++++- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/api_test.go b/api_test.go index 13731dbf9ef99e3b3b7bbd206c772889ed965735..a0724b0ba3c6861912796fc464de80eccfe8dd72 100644 --- a/api_test.go +++ b/api_test.go @@ -895,6 +895,12 @@ func TestPostContainersAttach(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() + // Try to avoid the timeoout in destroy. Best effort, don't check error + defer func() { + closeWrap(stdin, stdinPipe, stdout, stdoutPipe) + container.Kill() + }() + // Attach to it c1 := make(chan struct{}) go func() { @@ -934,7 +940,7 @@ func TestPostContainersAttach(t *testing.T) { } // Wait for attach to finish, the client disconnected, therefore, Attach finished his job - setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { + setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() { <-c1 }) diff --git a/container.go b/container.go index dea81b6def69208a5ccb78ce02e571f3c759232a..ec4abffc1b980890b4fbaec33eb0272e2ef953bb 100644 --- a/container.go +++ b/container.go @@ -379,14 +379,15 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s utils.Debugf("[start] attach stdin\n") defer utils.Debugf("[end] attach stdin\n") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr - if cStdout != nil { - defer cStdout.Close() - } - if cStderr != nil { - defer cStderr.Close() - } if container.Config.StdinOnce && !container.Config.Tty { defer cStdin.Close() + } else { + if cStdout != nil { + defer cStdout.Close() + } + if cStderr != nil { + defer cStderr.Close() + } } if container.Config.Tty { _, err = utils.CopyEscapable(cStdin, stdin) diff --git a/runtime_test.go b/runtime_test.go index 807097404d0f0a6fd257be418bbaa91478b4ebdd..0b0f62f1990cfd337763efc1487c81d60ae57f9b 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -8,6 +8,7 @@ import ( "log" "net" "os" + "runtime" "strconv" "strings" "sync" @@ -25,7 +26,11 @@ const ( testDaemonProto = "tcp" ) -var globalRuntime *Runtime +var ( + globalRuntime *Runtime + startFds int + startGoroutines int +) func nuke(runtime *Runtime) error { var wg sync.WaitGroup @@ -80,21 +85,21 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) - if err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil { panic(err) + } else { + globalRuntime = runtime } - globalRuntime = runtime // Create the "Server" srv := &Server{ - runtime: runtime, + runtime: globalRuntime, enableCors: false, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } // If the unit test is not found, try to download it. - if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { + if img, err := globalRuntime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { // Retrieve the Image if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil { panic(err) @@ -109,6 +114,8 @@ func init() { // Give some time to ListenAndServer to actually start time.Sleep(time.Second) + + startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() } // FIXME: test that ImagePull(json=true) send correct json output diff --git a/z_final_test.go b/z_final_test.go index 78a7acf6e7d30fbd5779a24ddd54e19fcf049dcb..08a180baaf86f138bd69b7ba2e475902d541f121 100644 --- a/z_final_test.go +++ b/z_final_test.go @@ -6,7 +6,12 @@ import ( "testing" ) +func displayFdGoroutines(t *testing.T) { + t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) +} + func TestFinal(t *testing.T) { cleanup(globalRuntime) - t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) + t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) + displayFdGoroutines(t) } From 9332c00ca562e97045490d3d45d8f805fae30330 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 25 Jul 2013 00:35:52 +0000 Subject: [PATCH 071/101] Copy authConfigs on save so data is not modified SaveConfig sets the Username and Password to an empty string on save. A copy of the authConfigs need to be made so that the in memory data is not modified. --- auth/auth.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index bffed498078d760e77a7bfc04cc10d6ca310d5e4..39de8768755beeb4d3983aae7685d99264ed5d82 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -116,14 +116,19 @@ func SaveConfig(configFile *ConfigFile) error { os.Remove(confFile) return nil } + + configs := make(map[string]AuthConfig, len(configFile.Configs)) for k, authConfig := range configFile.Configs { - authConfig.Auth = encodeAuth(&authConfig) - authConfig.Username = "" - authConfig.Password = "" - configFile.Configs[k] = authConfig + authCopy := authConfig + + authCopy.Auth = encodeAuth(&authCopy) + authCopy.Username = "" + authCopy.Password = "" + + configs[k] = authCopy } - b, err := json.Marshal(configFile.Configs) + b, err := json.Marshal(configs) if err != nil { return err } From 0fc11699ab26121e4f89808ffacb2becf536ff5d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 25 Jul 2013 03:25:16 +0000 Subject: [PATCH 072/101] Add regression test for authConfig overwrite --- auth/auth_test.go | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index 458a505ea2f6256ac60d1942a245f764cd25c997..d94d429da1ad8e950726037278e8b312c239c462 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -3,6 +3,7 @@ package auth import ( "crypto/rand" "encoding/hex" + "io/ioutil" "os" "strings" "testing" @@ -51,7 +52,7 @@ func TestCreateAccount(t *testing.T) { } token := hex.EncodeToString(tokenBuffer)[:12] username := "ut" + token - authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+"+token+"@example.com"} + authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"} status, err := Login(authConfig) if err != nil { t.Fatal(err) @@ -73,3 +74,39 @@ func TestCreateAccount(t *testing.T) { t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err) } } + +func TestSameAuthDataPostSave(t *testing.T) { + root, err := ioutil.TempDir("", "docker-test") + if err != nil { + t.Fatal(err) + } + configFile := &ConfigFile{ + rootPath: root, + Configs: make(map[string]AuthConfig, 1), + } + + configFile.Configs["testIndex"] = AuthConfig{ + Username: "docker-user", + Password: "docker-pass", + Email: "docker@docker.io", + } + + err = SaveConfig(configFile) + if err != nil { + t.Fatal(err) + } + + authConfig := configFile.Configs["testIndex"] + if authConfig.Username != "docker-user" { + t.Fail() + } + if authConfig.Password != "docker-pass" { + t.Fail() + } + if authConfig.Email != "docker@docker.io" { + t.Fail() + } + if authConfig.Auth != "" { + t.Fail() + } +} From 8f6b6d57840410d1491321d7681ef2a946e90bc9 Mon Sep 17 00:00:00 2001 From: Daniel YC Lin Date: Thu, 25 Jul 2013 15:36:32 +0800 Subject: [PATCH 073/101] Fixes #1286 --- commands.go | 2 +- docs/sources/commandline/command/import.rst | 7 ++++--- packaging/debian/lxc-docker.1 | 6 ++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index f0e1695b3f62d426949b02f3444720ed14a524ea..2e346e203432bef9376245f7445dd20441adf151 100644 --- a/commands.go +++ b/commands.go @@ -756,7 +756,7 @@ func (cli *DockerCli) CmdKill(args ...string) error { } func (cli *DockerCli) CmdImport(args ...string) error { - cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") + cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).") if err := cmd.Parse(args); err != nil { return nil diff --git a/docs/sources/commandline/command/import.rst b/docs/sources/commandline/command/import.rst index 66bcf5de52b48d74149d3c37972569b7c55d28b1..0083068e10d0b3b2cb9811e519f8b85cd72be2d1 100644 --- a/docs/sources/commandline/command/import.rst +++ b/docs/sources/commandline/command/import.rst @@ -12,8 +12,9 @@ Create a new filesystem image from the contents of a tarball -At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip) -containing a root filesystem. If you would like to import from a local directory or archive, +At this time, the URL must start with ``http`` and point to a single file archive +(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) +containing a root filesystem. If you would like to import from a local directory or archive, you can use the ``-`` parameter to take the data from standard in. Examples @@ -30,7 +31,7 @@ Import from a local file Import to docker via pipe and standard in ``$ cat exampleimage.tgz | docker import - exampleimagelocal`` - + Import from a local directory ............................. diff --git a/packaging/debian/lxc-docker.1 b/packaging/debian/lxc-docker.1 index 8f9878528570ffb06d1d0fadbe128ec43812ed28..cc20299fad3a851cf135f4ce0bb94317408c41b0 100644 --- a/packaging/debian/lxc-docker.1 +++ b/packaging/debian/lxc-docker.1 @@ -923,6 +923,12 @@ List images Usage: docker import [OPTIONS] URL|\- [REPOSITORY [TAG]] .sp Create a new filesystem image from the contents of a tarball + +At this time, the URL must start with ``http`` and point to a single file archive +(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) +containing a root filesystem. If you would like to import from a local directory or archive, +you can use the ``-`` parameter to take the data from standard in. + .SS info .sp .nf From f385f1860bdebe2434bb122bd5ac8fec85687970 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 25 Jul 2013 15:18:34 +0000 Subject: [PATCH 074/101] ensure mount in commit --- builder.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builder.go b/builder.go index ab07233cb8f8c6d9db1454b43350d03e9765cbfb..420370b1e6d1fb0db34f04f883c2a81f17d0ffe0 100644 --- a/builder.go +++ b/builder.go @@ -124,6 +124,10 @@ func (builder *Builder) Create(config *Config) (*Container, error) { func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) { // FIXME: freeze the container before copying it to avoid data corruption? // FIXME: this shouldn't be in commands. + if err := container.EnsureMounted(); err != nil { + return nil, err + } + rwTar, err := container.ExportRw() if err != nil { return nil, err From 48833c7b0784bc7055f29dd3448df3af93c462ec Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 25 Jul 2013 15:20:56 +0000 Subject: [PATCH 075/101] add regression test + go fmt --- server_test.go | 21 +++++++++++++++++++++ utils/utils.go | 7 +++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/server_test.go b/server_test.go index 8612b3fcea54980bd74dac3d6d748cc71455574f..f67b8fb57c7fcd7608140623cd91170b0c5d7a45 100644 --- a/server_test.go +++ b/server_test.go @@ -90,6 +90,27 @@ func TestCreateRm(t *testing.T) { } +func TestCommit(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil) + if err != nil { + t.Fatal(err) + } + + id, err := srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + + if _, err := srv.ContainerCommit(id, "testrepo", "testtag", "", "", config); err != nil { + t.Fatal(err) + } +} + func TestCreateStartRestartStopStartKillRm(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) diff --git a/utils/utils.go b/utils/utils.go index acb015becdabec7b60a45641084fd7613f509f9c..c70e80b72ec7d716df321a23974b0eed5d7c9245 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -611,11 +611,11 @@ type JSONMessage struct { Status string `json:"status,omitempty"` Progress string `json:"progress,omitempty"` Error string `json:"error,omitempty"` - ID string `json:"id,omitempty"` - Time int64 `json:"time,omitempty"` + ID string `json:"id,omitempty"` + Time int64 `json:"time,omitempty"` } -func (jm *JSONMessage) Display(out io.Writer) (error) { +func (jm *JSONMessage) Display(out io.Writer) error { if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } @@ -631,7 +631,6 @@ func (jm *JSONMessage) Display(out io.Writer) (error) { return nil } - type StreamFormatter struct { json bool used bool From 1c509f4350d943c6aa8b9bff8dcbed28ee803735 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 25 Jul 2013 15:45:15 +0000 Subject: [PATCH 076/101] use 0755 instead of 0700 --- image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image.go b/image.go index e1b1ac0418c8d86d725c832a80a064c65a145e07..5240ec776f8eea6537cedba4a4a25cfc11a9b21d 100644 --- a/image.go +++ b/image.go @@ -68,7 +68,7 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error { } // Store the layer layer := layerPath(root) - if err := os.MkdirAll(layer, 0700); err != nil { + if err := os.MkdirAll(layer, 0755); err != nil { return err } From 3425c1b84c3f58ac5bb2feb91c4901b61561c58c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Jul 2013 11:37:13 -0700 Subject: [PATCH 077/101] Make sure the cookie is used in all registry queries --- registry/registry.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index e6f4f592e23463ab10e845c76dc002d640a685fd..adef1c7baafbc4abc542129572f885bd0393af5b 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -109,7 +109,14 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { for _, cookie := range c.Jar.Cookies(req.URL) { req.AddCookie(cookie) } - return c.Do(req) + res, err := c.Do(req) + if err != nil { + return nil, err + } + if len(res.Cookies()) > 0 { + c.Jar.SetCookies(req.URL, res.Cookies()) + } + return res, err } // Set the user agent field in the header based on the versions provided @@ -135,7 +142,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) r.setUserAgent(req) - res, err := r.client.Do(req) + res, err := doWithCookies(r.client, req) if err != nil || res.StatusCode != 200 { if res != nil { return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID) @@ -182,7 +189,7 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) r.setUserAgent(req) - res, err := r.client.Do(req) + res, err := doWithCookies(r.client, req) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } @@ -210,7 +217,7 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) ( } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) r.setUserAgent(req) - res, err := r.client.Do(req) + res, err := doWithCookies(r.client, req) if err != nil { return nil, err } @@ -231,7 +238,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ } req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) r.setUserAgent(req) - res, err := r.client.Do(req) + res, err := doWithCookies(r.client, req) if err != nil { return nil, err } @@ -326,7 +333,7 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e // Push a local image to the registry func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error { // FIXME: try json with UTF8 - req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw))) + req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw)) if err != nil { return err } @@ -341,9 +348,6 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return fmt.Errorf("Failed to upload metadata: %s", err) } defer res.Body.Close() - if len(res.Cookies()) > 0 { - r.client.Jar.SetCookies(req.URL, res.Cookies()) - } if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { From d86898b0142f2f9a834aa0c727b10d62ef647262 Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Tue, 9 Jul 2013 07:01:45 +0000 Subject: [PATCH 078/101] Fixes 1136; Reopened from 1175 with latest changes. --- buildfile.go | 69 ++++++++++++++++++++++++++++++++++++++++------- buildfile_test.go | 42 +++++++++++++++++++++++++++-- utils/utils.go | 7 +++-- 3 files changed, 103 insertions(+), 15 deletions(-) diff --git a/buildfile.go b/buildfile.go index 75ebdd7a7c12d6325347fda81eb3eecafc3021e6..c5171aaa9139c3cb1f3f2af7e82c58540bfd281e 100644 --- a/buildfile.go +++ b/buildfile.go @@ -11,6 +11,7 @@ import ( "os" "path" "reflect" + "regexp" "strings" ) @@ -67,6 +68,9 @@ func (b *buildFile) CmdFrom(name string) error { } b.image = image.ID b.config = &Config{} + if b.config.Env == nil || len(b.config.Env) == 0 { + b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") + } return nil } @@ -112,6 +116,40 @@ func (b *buildFile) CmdRun(args string) error { return nil } +func (b *buildFile) FindEnvKey(key string) int { + for k, envVar := range b.config.Env { + envParts := strings.SplitN(envVar, "=", 2) + if key == envParts[0] { + return k + } + } + return -1 +} + +func (b *buildFile) ReplaceEnvMatches(value string) (string, error) { + exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)") + if err != nil { + return value, err + } + matches := exp.FindAllString(value, -1) + for _, match := range matches { + match = match[strings.Index(match, "$"):] + matchKey := strings.Trim(match, "${}") + + for _, envVar := range b.config.Env { + envParts := strings.SplitN(envVar, "=", 2) + envKey := envParts[0] + envValue := envParts[1] + + if envKey == matchKey { + value = strings.Replace(value, match, envValue, -1) + break + } + } + } + return value, nil +} + func (b *buildFile) CmdEnv(args string) error { tmp := strings.SplitN(args, " ", 2) if len(tmp) != 2 { @@ -120,14 +158,19 @@ func (b *buildFile) CmdEnv(args string) error { key := strings.Trim(tmp[0], " \t") value := strings.Trim(tmp[1], " \t") - for i, elem := range b.config.Env { - if strings.HasPrefix(elem, key+"=") { - b.config.Env[i] = key + "=" + value - return nil - } + envKey := b.FindEnvKey(key) + replacedValue, err := b.ReplaceEnvMatches(value) + if err != nil { + return err } - b.config.Env = append(b.config.Env, key+"="+value) - return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value)) + replacedVar := fmt.Sprintf("%s=%s", key, replacedValue) + + if envKey >= 0 { + b.config.Env[envKey] = replacedVar + return nil + } + b.config.Env = append(b.config.Env, replacedVar) + return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar)) } func (b *buildFile) CmdCmd(args string) error { @@ -260,8 +303,16 @@ func (b *buildFile) CmdAdd(args string) error { if len(tmp) != 2 { return fmt.Errorf("Invalid ADD format") } - orig := strings.Trim(tmp[0], " \t") - dest := strings.Trim(tmp[1], " \t") + + orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t")) + if err != nil { + return err + } + + dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t")) + if err != nil { + return err + } cmd := b.config.Cmd b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} diff --git a/buildfile_test.go b/buildfile_test.go index b7eca523364cc5ed17ed0d79d0e45edfbdd84b17..78e53b84197762927332b8cdd8006d72ccbc5f5a 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -129,6 +129,38 @@ CMD Hello world nil, nil, }, + + { + ` +from {IMAGE} +env FOO /foo/baz +env BAR /bar +env BAZ $BAR +env FOOPATH $PATH:$FOO +run [ "$BAR" = "$BAZ" ] +run [ "$FOOPATH" = "$PATH:/foo/baz" ] +`, + nil, + nil, + }, + + { + ` +from {IMAGE} +env FOO /bar +env TEST testdir +env BAZ /foobar +add testfile $BAZ/ +add $TEST $FOO +run [ "$(cat /foobar/testfile)" = "test1" ] +run [ "$(cat /bar/withfile)" = "test2" ] +`, + [][2]string{ + {"testfile", "test1"}, + {"testdir/withfile", "test2"}, + }, + nil, + }, } // FIXME: test building with 2 successive overlapping ADD commands @@ -242,8 +274,14 @@ func TestBuildEnv(t *testing.T) { env port 4243 `, nil, nil}, t) - - if img.Config.Env[0] != "port=4243" { + hasEnv := false + for _, envVar := range img.Config.Env { + if envVar == "port=4243" { + hasEnv = true + break + } + } + if !hasEnv { t.Fail() } } diff --git a/utils/utils.go b/utils/utils.go index acb015becdabec7b60a45641084fd7613f509f9c..c70e80b72ec7d716df321a23974b0eed5d7c9245 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -611,11 +611,11 @@ type JSONMessage struct { Status string `json:"status,omitempty"` Progress string `json:"progress,omitempty"` Error string `json:"error,omitempty"` - ID string `json:"id,omitempty"` - Time int64 `json:"time,omitempty"` + ID string `json:"id,omitempty"` + Time int64 `json:"time,omitempty"` } -func (jm *JSONMessage) Display(out io.Writer) (error) { +func (jm *JSONMessage) Display(out io.Writer) error { if jm.Time != 0 { fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0)) } @@ -631,7 +631,6 @@ func (jm *JSONMessage) Display(out io.Writer) (error) { return nil } - type StreamFormatter struct { json bool used bool From 4ebe2cf348915415c34503aca7a5663177e0002f Mon Sep 17 00:00:00 2001 From: Mike Gaffney Date: Fri, 26 Jul 2013 01:10:42 -0700 Subject: [PATCH 079/101] Change reserve-compatibility to reverse-compatibility --- container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container.go b/container.go index ec4abffc1b980890b4fbaec33eb0272e2ef953bb..d0b6ca4ce26d7ef88520e0ef886c0f9a740a5f9e 100644 --- a/container.go +++ b/container.go @@ -52,7 +52,7 @@ type Container struct { waitLock chan struct{} Volumes map[string]string - // Store rw/ro in a separate structure to preserve reserve-compatibility on-disk. + // Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. // Easier than migrating older container configs :) VolumesRW map[string]bool } From e608296bc62ceeaf41ebf2bc80b21c0a1883d4f0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 26 Jul 2013 09:19:26 +0000 Subject: [PATCH 080/101] fix wrong untag when using rmi via id --- server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server.go b/server.go index ce1fc8eaf803b48e57f26602ceb5f054282237bc..fa8d8a0262385658f6920113325d66e09d794a44 100644 --- a/server.go +++ b/server.go @@ -995,6 +995,9 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro parsedRepo := strings.Split(repoAndTag, ":")[0] if strings.Contains(img.ID, repoName) { repoName = parsedRepo + if len(strings.Split(repoAndTag, ":")) > 1 { + tag = strings.Split(repoAndTag, ":")[1] + } } else if repoName != parsedRepo { // the id belongs to multiple repos, like base:latest and user:test, // in that case return conflict From 513a5674831fd952fc8e7b8fbdbc4939393ecb5b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 26 Jul 2013 10:04:46 +0000 Subject: [PATCH 081/101] fix docs --- docs/sources/api/docker_remote_api.rst | 6 +++--- docs/sources/api/docker_remote_api_v1.4.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index d07c7634b9cbfe26bab1fa0bdb0a25d1310ca830..0a1ff21cf154e03aa50ccaa95e88738dde769721 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -26,15 +26,15 @@ Docker Remote API 2. Versions =========== -The current verson of the API is 1.3 +The current verson of the API is 1.4 Calling /images//insert is the same as calling -/v1.3/images//insert +/v1.4/images//insert You can still call an old version of the api using /v1.0/images//insert -:doc:`docker_remote_api_v1.3` +:doc:`docker_remote_api_v1.4` ***************************** What's new diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index c42adb286fc34ea1e6c5cb20f838b3cad9687d12..6ee0b35fa288f7a538b46fbc3eb9e0e4b8e5c8fe 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -1,9 +1,9 @@ -:title: Remote API v1.3 +:title: Remote API v1.4 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation ====================== -Docker Remote API v1.3 +Docker Remote API v1.4 ====================== .. contents:: Table of Contents From e592f1b298c778d0b9adfd6751f5fe1843a7001d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 26 Jul 2013 10:01:41 +0000 Subject: [PATCH 082/101] add regression test --- server.go | 2 +- server_test.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index fa8d8a0262385658f6920113325d66e09d794a44..56f738a5cd951b9a18b19d11591e227e03672e0c 100644 --- a/server.go +++ b/server.go @@ -995,7 +995,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro parsedRepo := strings.Split(repoAndTag, ":")[0] if strings.Contains(img.ID, repoName) { repoName = parsedRepo - if len(strings.Split(repoAndTag, ":")) > 1 { + if len(srv.runtime.repositories.ByID()[img.ID]) == 1 && len(strings.Split(repoAndTag, ":")) > 1 { tag = strings.Split(repoAndTag, ":")[1] } } else if repoName != parsedRepo { diff --git a/server_test.go b/server_test.go index 8612b3fcea54980bd74dac3d6d748cc71455574f..b6b21cc75fedd1fe99357c0adec5081704fdebd4 100644 --- a/server_test.go +++ b/server_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" + "strings" "testing" "time" ) @@ -203,3 +204,88 @@ func TestLogEvent(t *testing.T) { }) } + +func TestRmi(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + srv := &Server{runtime: runtime} + + initialImages, err := srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + containerID, err := srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + + //To remove + err = srv.ContainerStart(containerID, hostConfig) + if err != nil { + t.Fatal(err) + } + + imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil) + if err != nil { + t.Fatal(err) + } + + err = srv.ContainerTag(imageID, "test", "0.1", false) + if err != nil { + t.Fatal(err) + } + + containerID, err = srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + + //To remove + err = srv.ContainerStart(containerID, hostConfig) + if err != nil { + t.Fatal(err) + } + + _, err = srv.ContainerCommit(containerID, "test", "", "", "", nil) + if err != nil { + t.Fatal(err) + } + + images, err := srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images)-len(initialImages) != 2 { + t.Fatalf("Expected 2 new images, found %d.", len(images)-len(initialImages)) + } + + _, err = srv.ImageDelete(imageID, true) + if err != nil { + t.Fatal(err) + } + + images, err = srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images)-len(initialImages) != 1 { + t.Fatalf("Expected 1 new image, found %d.", len(images)-len(initialImages)) + } + + for _, image := range images { + if strings.Contains(unitTestImageID, image.ID) { + continue + } + if image.Repository == "" { + t.Fatalf("Expected tagged image, got untagged one.") + } + } +} From a97d858b2a0d6bc4d044a241036b97fd78f93022 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 26 Jul 2013 10:21:17 -0700 Subject: [PATCH 083/101] Clean up 'manifesto' in docs --- docs/sources/concepts/manifesto.rst | 61 ----------------------------- 1 file changed, 61 deletions(-) diff --git a/docs/sources/concepts/manifesto.rst b/docs/sources/concepts/manifesto.rst index ae09647094dca95c9114bf6082fccaf027428b91..7dd4b4bddae8c48e288e9a27484a66c81663ae45 100644 --- a/docs/sources/concepts/manifesto.rst +++ b/docs/sources/concepts/manifesto.rst @@ -4,10 +4,6 @@ .. _dockermanifesto: -*(This was our original Welcome page, but it is a bit forward-looking -for docs, and maybe not enough vision for a true manifesto. We'll -reveal more vision in the future to make it more Manifesto-y.)* - Docker Manifesto ---------------- @@ -131,60 +127,3 @@ sitting 10 miles away. With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality. - -Standard Container Specification -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(TODO) - -Image format -~~~~~~~~~~~~ - -Standard operations -~~~~~~~~~~~~~~~~~~~ - -- Copy -- Run -- Stop -- Wait -- Commit -- Attach standard streams -- List filesystem changes -- ... - -Execution environment -~~~~~~~~~~~~~~~~~~~~~ - -Root filesystem -^^^^^^^^^^^^^^^ - -Environment variables -^^^^^^^^^^^^^^^^^^^^^ - -Process arguments -^^^^^^^^^^^^^^^^^ - -Networking -^^^^^^^^^^ - -Process namespacing -^^^^^^^^^^^^^^^^^^^ - -Resource limits -^^^^^^^^^^^^^^^ - -Process monitoring -^^^^^^^^^^^^^^^^^^ - -Logging -^^^^^^^ - -Signals -^^^^^^^ - -Pseudo-terminal allocation -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Security -^^^^^^^^ - From bdc79ac8b2dfad302f9e144711067a566726cfa2 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 26 Jul 2013 15:40:55 -0400 Subject: [PATCH 084/101] Bind daemon to 0.0.0.0 in Vagrant. Fixes #1304 --- Vagrantfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index aadabb87116f5295a2d975843da0a1a1dae8c0dd..7258af5bf7fdd7202304a02415ef750adff649db 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -20,6 +20,8 @@ Vagrant::Config.run do |config| pkg_cmd = "apt-get update -qq; apt-get install -q -y python-software-properties; " \ "add-apt-repository -y ppa:dotcloud/lxc-docker; apt-get update -qq; " \ "apt-get install -q -y lxc-docker; " + # Listen on all interfaces so that the daemon is accessible from the host + pkg_cmd << "sed -i -E 's| /usr/bin/docker -d| /usr/bin/docker -d -H 0.0.0.0|' /etc/init/docker.conf;" # Add X.org Ubuntu backported 3.8 kernel pkg_cmd << "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \ "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; " From 5eb590e79d081ecfa82ba940ea856c4a2c0411ca Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 26 Jul 2013 15:44:06 -0400 Subject: [PATCH 085/101] Update AUTHORS --- .mailmap | 2 ++ AUTHORS | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 452ac41d8f6b8b4fd5b7df3ed09b5c8ff4b36865..11ff5357d8d32ee4315a1943c3d60a6b868d0822 100644 --- a/.mailmap +++ b/.mailmap @@ -23,3 +23,5 @@ Thatcher Peskens Walter Stanish Roberto Hashioka +Konstantin Pelykh +David Sissitka diff --git a/AUTHORS b/AUTHORS index 86d03f6e12510dede68c86dba867925dccc587bd..c811de6526195f9f7a61c6844c3cd537c533fbeb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,12 +4,15 @@ # For a list of active project maintainers, see the MAINTAINERS file. # Al Tobey +Alex Gaynor Alexey Shamrin Andrea Luzzardi Andreas Tiefenthaler Andrew Munsell +Andrews Medina Andy Rothfusz Andy Smith +Anthony Bishopric Antony Messerli Barry Allard Brandon Liu @@ -23,17 +26,22 @@ Daniel Gasienica Daniel Mizyrycki Daniel Robinson Daniel Von Fange +Daniel YC Lin +David Sissitka Dominik Honnef Don Spaulding Dr Nic Williams Elias Probst Eric Hanchrow -Evan Wies Eric Myhre +Erno Hopearuoho +Evan Wies ezbercih +Fabrizio Regini Flavio Castelli Francisco Souza Frederick F. Kautz IV +Gabriel Monroy Gareth Rushgrove Guillaume J. Charmes Harley Laue @@ -41,6 +49,7 @@ Hunter Blanks Jeff Lindsay Jeremy Grosser Joffrey F +Johan Euphrosine John Costa Jon Wedaman Jonas Pfenniger @@ -48,28 +57,39 @@ Jonathan Rudenberg Joseph Anthony Pasquale Holsten Julien Barbier Jérôme Petazzoni +Karan Lyons +Keli Hu Ken Cochrane Kevin J. Lynagh kim0 +Kimbro Staken Kiran Gangadharan +Konstantin Pelykh Louis Opter +Marco Hennings Marcus Farkas Mark McGranaghan Maxim Treskin meejah Michael Crosby +Mike Gaffney Mikhail Sobolev +Nan Monnand Deng Nate Jones Nelson Chen Niall O'Higgins +Nick Stenning +Nick Stinemates odk- Paul Bowsher Paul Hammond Phil Spitler Piotr Bogdan Renato Riccieri Santos Zannon +Rhys Hiltner Robert Obryk Roberto Hashioka +Ryan Fowler Sam Alba Sam J Sharpe Shawn Siefkas @@ -83,6 +103,8 @@ Thomas Hansen Tianon Gravi Tim Terhorst Tobias Bieniek +Tobias Schwab +Tom Hulihan unclejack Victor Vieux Vivek Agarwal From b15cfd3530cc228dc065746c9323758c8abb0481 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 26 Jul 2013 14:57:16 -0700 Subject: [PATCH 086/101] - Builder: Create directories with 755 instead of 700 within ADD instruction --- buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 75ebdd7a7c12d6325347fda81eb3eecafc3021e6..a25b86d794634e3edccbf9310a7aec03d8632bf2 100644 --- a/buildfile.go +++ b/buildfile.go @@ -242,7 +242,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error { } else if err := UntarPath(origPath, destPath); err != nil { utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err) // If that fails, just copy it as a regular file - if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil { + if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil { return err } if err := CopyWithTar(origPath, destPath); err != nil { From 2d85a20c71c9418301c3161c6853a07fca612676 Mon Sep 17 00:00:00 2001 From: Mike Gaffney Date: Fri, 26 Jul 2013 18:29:27 -0700 Subject: [PATCH 087/101] Add required go version for compilation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96da13feaf1de0f4e15f0e93fc454703a0d3d8c7..5e03b2993c24423b62589bf8faa63e8b12742a4c 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Installing from source ---------------------- 1. Make sure you have a [Go language](http://golang.org/doc/install) -compiler and [git](http://git-scm.com) installed. +compiler >= 1.1 and [git](http://git-scm.com) installed. 2. Checkout the source code ```bash From d4f70397930b15c8860a71df1abb9bc5ac9ab4ff Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 10:00:36 -0700 Subject: [PATCH 088/101] Do not show empty parenthesis if the default configuration is missing. --- commands.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 0fabaa385f9c20127d908083b518051463afad8c..a7df369e5fad02a76d01e93a8a218859d67ae630 100644 --- a/commands.go +++ b/commands.go @@ -314,13 +314,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error { email string ) + var promptDefault = func(stdout io.Writer, prompt string, configDefault string) { + if configDefault == "" { + fmt.Fprintf(cli.out, "%s: ", prompt) + } else { + fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) + } + } + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] if !ok { authconfig = auth.AuthConfig{} } if *flUsername == "" { - fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) + promptDefault(cli.out, "Username", authconfig.Username) username = readAndEchoString(cli.in, cli.out) if username == "" { username = authconfig.Username @@ -340,7 +348,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if *flEmail == "" { - fmt.Fprintf(cli.out, "Email (%s): ", authconfig.Email) + promptDefault(cli.out, "Email", authconfig.Email) email = readAndEchoString(cli.in, cli.out) if email == "" { email = authconfig.Email From 88b6ea993d76fb8891ee7a7fa8828b5c5753c7f5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 10:17:57 -0700 Subject: [PATCH 089/101] Remove unused argument. --- commands.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index a7df369e5fad02a76d01e93a8a218859d67ae630..7288b58229be9cf15190b40f1ed6e220bed5feb8 100644 --- a/commands.go +++ b/commands.go @@ -314,7 +314,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { email string ) - var promptDefault = func(stdout io.Writer, prompt string, configDefault string) { + var promptDefault = func(prompt string, configDefault string) { if configDefault == "" { fmt.Fprintf(cli.out, "%s: ", prompt) } else { @@ -328,7 +328,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if *flUsername == "" { - promptDefault(cli.out, "Username", authconfig.Username) + promptDefault("Username", authconfig.Username) username = readAndEchoString(cli.in, cli.out) if username == "" { username = authconfig.Username @@ -348,7 +348,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if *flEmail == "" { - promptDefault(cli.out, "Email", authconfig.Email) + promptDefault("Email", authconfig.Email) email = readAndEchoString(cli.in, cli.out) if email == "" { email = authconfig.Email From 97a2dc96f23b25bc7980d2a4514b7065ff4edb9e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 28 Jul 2013 12:57:09 -0700 Subject: [PATCH 090/101] Remove deprecated copy from README --- README.md | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/README.md b/README.md index 5e03b2993c24423b62589bf8faa63e8b12742a4c..5eee1a3db6d50ab6161ce800133d64b7ac737cae 100644 --- a/README.md +++ b/README.md @@ -444,51 +444,6 @@ With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality. - - -Standard Container Specification --------------------------------- - -(TODO) - -### Image format - - -### Standard operations - -* Copy -* Run -* Stop -* Wait -* Commit -* Attach standard streams -* List filesystem changes -* ... - -### Execution environment - -#### Root filesystem - -#### Environment variables - -#### Process arguments - -#### Networking - -#### Process namespacing - -#### Resource limits - -#### Process monitoring - -#### Logging - -#### Signals - -#### Pseudo-terminal allocation - -#### Security - ### Legal Transfers of Docker shall be in accordance with applicable export From c8ec36d1b9bfbe1e22acd0124409ecb5a109d406 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 29 Jul 2013 10:28:41 -0700 Subject: [PATCH 091/101] Remove unnecessary signal conditional. --- commands.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index 0fabaa385f9c20127d908083b518051463afad8c..b3e797699570907690e2a669063b282e3781b173 100644 --- a/commands.go +++ b/commands.go @@ -1658,14 +1658,11 @@ func (cli *DockerCli) monitorTtySize(id string) error { } cli.resizeTty(id) - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGWINCH) + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGWINCH) go func() { - for sig := range c { - if sig == syscall.SIGWINCH { - cli.resizeTty(id) - } - } + <-sigchan + cli.resizeTty(id) }() return nil } From 10e37198aa14cb3192fd0bf29572a5dce58c348f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 29 Jul 2013 11:13:59 -0700 Subject: [PATCH 092/101] Keep the loop to allow resizing more than once. --- commands.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index b3e797699570907690e2a669063b282e3781b173..b4853e967da016470fbb6d6045883995a2dc9afb 100644 --- a/commands.go +++ b/commands.go @@ -1661,8 +1661,10 @@ func (cli *DockerCli) monitorTtySize(id string) error { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, syscall.SIGWINCH) go func() { - <-sigchan - cli.resizeTty(id) + for { + <-sigchan + cli.resizeTty(id) + } }() return nil } From 8ca7b0646e6c4346075656f46847f53c2e868a3d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 17 Jul 2013 12:13:22 -0700 Subject: [PATCH 093/101] Refactor checksum --- auth/auth.go | 4 +- graph.go | 61 ++--------------- image.go | 99 --------------------------- registry/registry.go | 58 +++++++++++++--- server.go | 96 +++++---------------------- utils/tarsum.go | 155 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 226 insertions(+), 247 deletions(-) create mode 100644 utils/tarsum.go diff --git a/auth/auth.go b/auth/auth.go index 39de8768755beeb4d3983aae7685d99264ed5d82..e402031ca2d1cb9dfbdcc78d152462d68a6fd15e 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -16,9 +16,9 @@ import ( const CONFIGFILE = ".dockercfg" // Only used for user auth + account creation -const INDEXSERVER = "https://index.docker.io/v1/" +//const INDEXSERVER = "https://index.docker.io/v1/" -//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/" +const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" var ( ErrConfigFileMissing = errors.New("The Auth config file is missing") diff --git a/graph.go b/graph.go index 42d1bdbd4ccd2625b0f286b625116d3a2a189cb1..eea2cbec8acfadd1fc394751ca761f08dde66612 100644 --- a/graph.go +++ b/graph.go @@ -1,9 +1,7 @@ package docker import ( - "encoding/json" "fmt" - "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -11,17 +9,13 @@ import ( "path" "path/filepath" "strings" - "sync" "time" ) // A Graph is a store for versioned filesystem images and the relationship between them. type Graph struct { - Root string - idIndex *utils.TruncIndex - checksumLock map[string]*sync.Mutex - lockSumFile *sync.Mutex - lockSumMap *sync.Mutex + Root string + idIndex *utils.TruncIndex } // NewGraph instantiates a new graph at the given root path in the filesystem. @@ -36,11 +30,8 @@ func NewGraph(root string) (*Graph, error) { return nil, err } graph := &Graph{ - Root: abspath, - idIndex: utils.NewTruncIndex(), - checksumLock: make(map[string]*sync.Mutex), - lockSumFile: &sync.Mutex{}, - lockSumMap: &sync.Mutex{}, + Root: abspath, + idIndex: utils.NewTruncIndex(), } if err := graph.restore(); err != nil { return nil, err @@ -99,11 +90,6 @@ func (graph *Graph) Get(name string) (*Image, error) { return nil, err } } - graph.lockSumMap.Lock() - defer graph.lockSumMap.Unlock() - if _, exists := graph.checksumLock[img.ID]; !exists { - graph.checksumLock[img.ID] = &sync.Mutex{} - } return img, nil } @@ -126,7 +112,6 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut if err := graph.Register(layerData, layerData != nil, img); err != nil { return nil, err } - go img.Checksum() return img, nil } @@ -154,7 +139,6 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { } img.graph = graph graph.idIndex.Add(img.ID) - graph.checksumLock[img.ID] = &sync.Mutex{} return nil } @@ -311,40 +295,3 @@ func (graph *Graph) Heads() (map[string]*Image, error) { func (graph *Graph) imageRoot(id string) string { return path.Join(graph.Root, id) } - -func (graph *Graph) getStoredChecksums() (map[string]string, error) { - checksums := make(map[string]string) - // FIXME: Store the checksum in memory - - if checksumDict, err := ioutil.ReadFile(path.Join(graph.Root, "checksums")); err == nil { - if err := json.Unmarshal(checksumDict, &checksums); err != nil { - return nil, err - } - } - return checksums, nil -} - -func (graph *Graph) storeChecksums(checksums map[string]string) error { - checksumJSON, err := json.Marshal(checksums) - if err != nil { - return err - } - if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJSON, 0600); err != nil { - return err - } - return nil -} - -func (graph *Graph) UpdateChecksums(newChecksums map[string]*registry.ImgData) error { - graph.lockSumFile.Lock() - defer graph.lockSumFile.Unlock() - - localChecksums, err := graph.getStoredChecksums() - if err != nil { - return err - } - for id, elem := range newChecksums { - localChecksums[id] = elem.Checksum - } - return graph.storeChecksums(localChecksums) -} diff --git a/image.go b/image.go index 5240ec776f8eea6537cedba4a4a25cfc11a9b21d..dd066f88cdbf6ba709f92131ff6234adab849ccc 100644 --- a/image.go +++ b/image.go @@ -2,7 +2,6 @@ package docker import ( "crypto/rand" - "crypto/sha256" "encoding/hex" "encoding/json" "fmt" @@ -72,26 +71,6 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error { return err } - if store { - layerArchive := layerArchivePath(root) - file, err := os.OpenFile(layerArchive, os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - return err - } - // FIXME: Retrieve the image layer size from here? - if _, err := io.Copy(file, layerData); err != nil { - return err - } - // FIXME: Don't close/open, read/write instead of Copy - file.Close() - - file, err = os.Open(layerArchive) - if err != nil { - return err - } - defer file.Close() - layerData = file - } // If layerData is not nil, unpack it into the new layer if layerData != nil { start := time.Now() @@ -128,10 +107,6 @@ func layerPath(root string) string { return path.Join(root, "layer") } -func layerArchivePath(root string) string { - return path.Join(root, "layer.tar.xz") -} - func jsonPath(root string) string { return path.Join(root, "json") } @@ -308,80 +283,6 @@ func (img *Image) layer() (string, error) { return layerPath(root), nil } -func (img *Image) Checksum() (string, error) { - img.graph.checksumLock[img.ID].Lock() - defer img.graph.checksumLock[img.ID].Unlock() - - root, err := img.root() - if err != nil { - return "", err - } - - checksums, err := img.graph.getStoredChecksums() - if err != nil { - return "", err - } - if checksum, ok := checksums[img.ID]; ok { - return checksum, nil - } - - layer, err := img.layer() - if err != nil { - return "", err - } - jsonData, err := ioutil.ReadFile(jsonPath(root)) - if err != nil { - return "", err - } - - var layerData io.Reader - - if file, err := os.Open(layerArchivePath(root)); err != nil { - if os.IsNotExist(err) { - layerData, err = Tar(layer, Xz) - if err != nil { - return "", err - } - } else { - return "", err - } - } else { - defer file.Close() - layerData = file - } - - h := sha256.New() - if _, err := h.Write(jsonData); err != nil { - return "", err - } - if _, err := h.Write([]byte("\n")); err != nil { - return "", err - } - - if _, err := io.Copy(h, layerData); err != nil { - return "", err - } - hash := "sha256:" + hex.EncodeToString(h.Sum(nil)) - - // Reload the json file to make sure not to overwrite faster sums - img.graph.lockSumFile.Lock() - defer img.graph.lockSumFile.Unlock() - - checksums, err = img.graph.getStoredChecksums() - if err != nil { - return "", err - } - - checksums[img.ID] = hash - - // Dump the checksums to disc - if err := img.graph.storeChecksums(checksums); err != nil { - return hash, err - } - - return hash, nil -} - func (img *Image) getParentsSize(size int64) int64 { parentImage, err := img.GetParent() if err != nil || parentImage == nil { diff --git a/registry/registry.go b/registry/registry.go index adef1c7baafbc4abc542129572f885bd0393af5b..cac77ba0496fa78a035e2f19c1f51f6ec52ed898 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -330,16 +330,52 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e }, nil } +func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error { + + utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum") + + req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil) + if err != nil { + return err + } + req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) + req.Header.Set("X-Docker-Checksum", imgData.Checksum) + + res, err := doWithCookies(r.client, req) + if err != nil { + return fmt.Errorf("Failed to upload metadata: %s", err) + } + defer res.Body.Close() + if len(res.Cookies()) > 0 { + r.client.Jar.SetCookies(req.URL, res.Cookies()) + } + if res.StatusCode != 200 { + errBody, err := ioutil.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) + } + var jsonBody map[string]string + if err := json.Unmarshal(errBody, &jsonBody); err != nil { + errBody = []byte(err.Error()) + } else if jsonBody["error"] == "Image already exists" { + return ErrAlreadyExists + } + return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody) + } + return nil +} + // Push a local image to the registry func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error { - // FIXME: try json with UTF8 + + utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json") + req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw)) if err != nil { return err } req.Header.Add("Content-type", "application/json") req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) - req.Header.Set("X-Docker-Checksum", imgData.Checksum) r.setUserAgent(req) utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum) @@ -364,10 +400,14 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return nil } -func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) error { - req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", layer) +func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) (checksum string, err error) { + + utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") + + tarsumLayer := &utils.TarSum{Reader: layer} + req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) if err != nil { - return err + return "", err } req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} @@ -375,18 +415,18 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr r.setUserAgent(req) res, err := doWithCookies(r.client, req) if err != nil { - return fmt.Errorf("Failed to upload layer: %s", err) + return "", fmt.Errorf("Failed to upload layer: %s", err) } defer res.Body.Close() if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) + return "", fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) } - return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody) + return "", fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody) } - return nil + return tarsumLayer.Sum(), nil } func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) { diff --git a/server.go b/server.go index ce1fc8eaf803b48e57f26602ceb5f054282237bc..6944df315ff086c874b935db443a45fe65a85dcc 100644 --- a/server.go +++ b/server.go @@ -455,12 +455,6 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName return err } - utils.Debugf("Updating checksums") - // Reload the json file to make sure not to overwrite faster sums - if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil { - return err - } - utils.Debugf("Retrieving the tag list") tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) if err != nil { @@ -598,41 +592,6 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut return nil } -// Retrieve the checksum of an image -// Priority: -// - Check on the stored checksums -// - Check if the archive exists, if it does not, ask the registry -// - If the archive does exists, process the checksum from it -// - If the archive does not exists and not found on registry, process checksum from layer -func (srv *Server) getChecksum(imageID string) (string, error) { - // FIXME: Use in-memory map instead of reading the file each time - if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil { - return "", err - } else if checksum, exists := sums[imageID]; exists { - return checksum, nil - } - - img, err := srv.runtime.graph.Get(imageID) - if err != nil { - return "", err - } - - if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageID))); err != nil { - if os.IsNotExist(err) { - // TODO: Ask the registry for the checksum - // As the archive is not there, it is supposed to come from a pull. - } else { - return "", err - } - } - - checksum, err := img.Checksum() - if err != nil { - return "", err - } - return checksum, nil -} - // Retrieve the all the images to be uploaded in the correct order // Note: we can't use a map as it is not ordered func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgData, error) { @@ -649,14 +608,10 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat return nil } imageSet[img.ID] = struct{}{} - checksum, err := srv.getChecksum(img.ID) - if err != nil { - return err - } + imgList = append([]*registry.ImgData{{ - ID: img.ID, - Checksum: checksum, - Tag: tag, + ID: img.ID, + Tag: tag, }}, imgList...) return nil }) @@ -666,7 +621,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) - out.Write(sf.FormatStatus("Processing checksums")) + imgList, err := srv.getImageList(localRepo) if err != nil { return err @@ -716,14 +671,8 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } out.Write(sf.FormatStatus("Pushing %s", imgID)) - // Make sure we have the image's checksum - checksum, err := srv.getChecksum(imgID) - if err != nil { - return err - } imgData := ®istry.ImgData{ - ID: imgID, - Checksum: checksum, + ID: imgID, } // Send the json @@ -735,36 +684,23 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, return err } - // Retrieve the tarball to be sent - var layerData *TempArchive - // If the archive exists, use it - file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgID))) + layerData, err := srv.runtime.graph.TempLayerArchive(imgID, Uncompressed, sf, out) if err != nil { - if os.IsNotExist(err) { - // If the archive does not exist, create one from the layer - layerData, err = srv.runtime.graph.TempLayerArchive(imgID, Xz, sf, out) - if err != nil { - return fmt.Errorf("Failed to generate layer archive: %s", err) - } - } else { - return err - } - } else { - defer file.Close() - st, err := file.Stat() - if err != nil { - return err - } - layerData = &TempArchive{ - File: file, - Size: st.Size(), - } + return fmt.Errorf("Failed to generate layer archive: %s", err) } // Send the layer - if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { + if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { return err + } else { + imgData.Checksum = checksum } + + // Send the checksum + if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil { + return err + } + return nil } diff --git a/utils/tarsum.go b/utils/tarsum.go new file mode 100644 index 0000000000000000000000000000000000000000..0fd5ac106bf536d19ede8f4f0ecd234a04904b78 --- /dev/null +++ b/utils/tarsum.go @@ -0,0 +1,155 @@ +package utils + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/hex" + "hash" + "io" + "sort" + "strconv" +) + +type verboseHash struct { + hash.Hash +} + +func (h verboseHash) Write(buf []byte) (int, error) { + Debugf("--->%s<---", buf) + return h.Hash.Write(buf) +} + +type TarSum struct { + io.Reader + tarR *tar.Reader + tarW *tar.Writer + gz *gzip.Writer + bufTar *bytes.Buffer + bufGz *bytes.Buffer + h hash.Hash + h2 verboseHash + sums []string + finished bool + first bool +} + +func (ts *TarSum) encodeHeader(h *tar.Header) error { + for _, elem := range [][2]string{ + {"name", h.Name}, + {"mode", strconv.Itoa(int(h.Mode))}, + {"uid", strconv.Itoa(h.Uid)}, + {"gid", strconv.Itoa(h.Gid)}, + {"size", strconv.Itoa(int(h.Size))}, + {"mtime", strconv.Itoa(int(h.ModTime.UTC().Unix()))}, + {"typeflag", string([]byte{h.Typeflag})}, + {"linkname", h.Linkname}, + {"uname", h.Uname}, + {"gname", h.Gname}, + {"devmajor", strconv.Itoa(int(h.Devmajor))}, + {"devminor", strconv.Itoa(int(h.Devminor))}, + // {"atime", strconv.Itoa(int(h.AccessTime.UTC().Unix()))}, + // {"ctime", strconv.Itoa(int(h.ChangeTime.UTC().Unix()))}, + } { + // Debugf("-->%s<-- -->%s<--", elem[0], elem[1]) + if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil { + return err + } + } + return nil +} + +func (ts *TarSum) Read(buf []byte) (int, error) { + if ts.gz == nil { + ts.bufTar = bytes.NewBuffer([]byte{}) + ts.bufGz = bytes.NewBuffer([]byte{}) + ts.tarR = tar.NewReader(ts.Reader) + ts.tarW = tar.NewWriter(ts.bufTar) + ts.gz = gzip.NewWriter(ts.bufGz) + ts.h = sha256.New() + // ts.h = verboseHash{sha256.New()} + ts.h.Reset() + ts.first = true + } + + if ts.finished { + return ts.bufGz.Read(buf) + } + buf2 := make([]byte, len(buf), cap(buf)) + + n, err := ts.tarR.Read(buf2) + if err != nil { + if err == io.EOF { + if _, err := ts.h.Write(buf2[:n]); err != nil { + return 0, err + } + if !ts.first { + ts.sums = append(ts.sums, hex.EncodeToString(ts.h.Sum(nil))) + ts.h.Reset() + } else { + ts.first = false + } + + currentHeader, err := ts.tarR.Next() + if err != nil { + if err == io.EOF { + if err := ts.gz.Close(); err != nil { + return 0, err + } + ts.finished = true + return n, nil + } + return n, err + } + if err := ts.encodeHeader(currentHeader); err != nil { + return 0, err + } + if err := ts.tarW.WriteHeader(currentHeader); err != nil { + return 0, err + } + if _, err := ts.tarW.Write(buf2[:n]); err != nil { + return 0, err + } + ts.tarW.Flush() + if _, err := io.Copy(ts.gz, ts.bufTar); err != nil { + return 0, err + } + ts.gz.Flush() + + return ts.bufGz.Read(buf) + } + return n, err + } + + // Filling the hash buffer + if _, err = ts.h.Write(buf2[:n]); err != nil { + return 0, err + } + + // Filling the tar writter + if _, err = ts.tarW.Write(buf2[:n]); err != nil { + return 0, err + } + ts.tarW.Flush() + + // Filling the gz writter + if _, err = io.Copy(ts.gz, ts.bufTar); err != nil { + return 0, err + } + ts.gz.Flush() + + return ts.bufGz.Read(buf) +} + +func (ts *TarSum) Sum() string { + sort.Strings(ts.sums) + h := sha256.New() + for _, sum := range ts.sums { + Debugf("-->%s<--", sum) + h.Write([]byte(sum)) + } + checksum := "tarsum+sha256:" + hex.EncodeToString(ts.h.Sum(nil)) + Debugf("checksum processed: %s", checksum) + return checksum +} From e3f68b22d8f0635a8c08ab56721e56dbe570a49a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 14:50:32 -0700 Subject: [PATCH 094/101] Handle extra-paremeter within checksum calculations --- registry/registry.go | 17 +++++++++++------ server.go | 2 +- utils/tarsum.go | 7 +++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index cac77ba0496fa78a035e2f19c1f51f6ec52ed898..40b9872a4a2011f659960462adf22d792c436a69 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -17,8 +17,10 @@ import ( "strings" ) -var ErrAlreadyExists = errors.New("Image already exists") -var ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") +var ( + ErrAlreadyExists = errors.New("Image already exists") + ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") +) func pingRegistryEndpoint(endpoint string) error { if endpoint == auth.IndexServerAddress() { @@ -266,8 +268,11 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ } func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) { + repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) + utils.Debugf("[registry] Calling GET %s", repositoryTarget) + req, err := r.opaqueRequest("GET", repositoryTarget, nil) if err != nil { return nil, err @@ -378,7 +383,6 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis req.Header.Set("Authorization", "Token "+strings.Join(token, ",")) r.setUserAgent(req) - utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum) res, err := doWithCookies(r.client, req) if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) @@ -400,11 +404,12 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return nil } -func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) (checksum string, err error) { +func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, err error) { utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") tarsumLayer := &utils.TarSum{Reader: layer} + req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", tarsumLayer) if err != nil { return "", err @@ -426,7 +431,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr } return "", fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody) } - return tarsumLayer.Sum(), nil + return tarsumLayer.Sum(jsonRaw), nil } func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) { @@ -474,7 +479,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData } u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix) - utils.Debugf("PUT %s", u) + utils.Debugf("[registry] PUT %s", u) utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON) req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON)) if err != nil { diff --git a/server.go b/server.go index 6944df315ff086c874b935db443a45fe65a85dcc..7309279805152c3ca736fa6b94f845cac1d1c05e 100644 --- a/server.go +++ b/server.go @@ -690,7 +690,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // Send the layer - if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil { + if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token, jsonRaw); err != nil { return err } else { imgData.Checksum = checksum diff --git a/utils/tarsum.go b/utils/tarsum.go index 0fd5ac106bf536d19ede8f4f0ecd234a04904b78..015b9b3076745a9a828a0636ed079feb9f851de6 100644 --- a/utils/tarsum.go +++ b/utils/tarsum.go @@ -142,14 +142,17 @@ func (ts *TarSum) Read(buf []byte) (int, error) { return ts.bufGz.Read(buf) } -func (ts *TarSum) Sum() string { +func (ts *TarSum) Sum(extra []byte) string { sort.Strings(ts.sums) h := sha256.New() for _, sum := range ts.sums { Debugf("-->%s<--", sum) h.Write([]byte(sum)) } - checksum := "tarsum+sha256:" + hex.EncodeToString(ts.h.Sum(nil)) + if extra != nil { + h.Write(extra) + } + checksum := "tarsum+sha256:" + hex.EncodeToString(h.Sum(nil)) Debugf("checksum processed: %s", checksum) return checksum } From 0badda9f1587c11a13dca17c68b30addd757237c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 15:40:33 -0700 Subject: [PATCH 095/101] Refactor the image size storage --- graph.go | 6 +++--- image.go | 43 ++++++++++++++++++++++++++++++++++--------- server.go | 2 +- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/graph.go b/graph.go index eea2cbec8acfadd1fc394751ca761f08dde66612..1fe14f645869f43afc3694ff2964569068553a6c 100644 --- a/graph.go +++ b/graph.go @@ -109,7 +109,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut img.Container = container.ID img.ContainerConfig = *container.Config } - if err := graph.Register(layerData, layerData != nil, img); err != nil { + if err := graph.Register(nil, layerData, layerData != nil, img); err != nil { return nil, err } return img, nil @@ -117,7 +117,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut // Register imports a pre-existing image into the graph. // FIXME: pass img as first argument -func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { +func (graph *Graph) Register(jsonData []byte, layerData Archive, store bool, img *Image) error { if err := ValidateID(img.ID); err != nil { return err } @@ -130,7 +130,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { if err != nil { return fmt.Errorf("Mktemp failed: %s", err) } - if err := StoreImage(img, layerData, tmp, store); err != nil { + if err := StoreImage(img, jsonData, layerData, tmp, store); err != nil { return err } // Commit diff --git a/image.go b/image.go index dd066f88cdbf6ba709f92131ff6234adab849ccc..7f03cc4bf857f7fb85d41a3c2f3c686ed83fee79 100644 --- a/image.go +++ b/image.go @@ -13,6 +13,7 @@ import ( "os/exec" "path" "path/filepath" + "strconv" "strings" "time" ) @@ -46,6 +47,19 @@ func LoadImage(root string) (*Image, error) { if err := ValidateID(img.ID); err != nil { return nil, err } + + if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + } else { + if size, err := strconv.Atoi(string(buf)); err != nil { + return nil, err + } else { + img.Size = int64(size) + } + } + // Check that the filesystem layer exists if stat, err := os.Stat(layerPath(root)); err != nil { if os.IsNotExist(err) { @@ -58,7 +72,7 @@ func LoadImage(root string) (*Image, error) { return img, nil } -func StoreImage(img *Image, layerData Archive, root string, store bool) error { +func StoreImage(img *Image, jsonData []byte, layerData Archive, root string, store bool) error { // Check that root doesn't already exist if _, err := os.Stat(root); err == nil { return fmt.Errorf("Image %s already exists", img.ID) @@ -81,25 +95,36 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error { utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds()) } + // If raw json is provided, then use it + if jsonData != nil { + return ioutil.WriteFile(jsonPath(root), jsonData, 0600) + } else { // Otherwise, unmarshal the image + jsonData, err := json.Marshal(img) + if err != nil { + return err + } + if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { + return err + } + } + return StoreSize(img, root) } func StoreSize(img *Image, root string) error { layer := layerPath(root) + var totalSize int64 = 0 filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error { - img.Size += fileInfo.Size() + totalSize += fileInfo.Size() return nil }) + img.Size = totalSize - // Store the json ball - jsonData, err := json.Marshal(img) - if err != nil { - return err - } - if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { - return err + if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil { + return nil } + return nil } diff --git a/server.go b/server.go index 7309279805152c3ca736fa6b94f845cac1d1c05e..de73d6c8158874402505aba2d968c18e1c441ddd 100644 --- a/server.go +++ b/server.go @@ -439,7 +439,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return err } defer layer.Close() - if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { + if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { return err } } From 0f134b4bf81a4d0160932852854b190b7ee7e3b9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 15:44:55 -0700 Subject: [PATCH 096/101] Remove unused parameter --- graph.go | 6 +++--- image.go | 2 +- server.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/graph.go b/graph.go index 1fe14f645869f43afc3694ff2964569068553a6c..bf27f93ed4aaa3b1c3d96f36646443aeafe606d8 100644 --- a/graph.go +++ b/graph.go @@ -109,7 +109,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut img.Container = container.ID img.ContainerConfig = *container.Config } - if err := graph.Register(nil, layerData, layerData != nil, img); err != nil { + if err := graph.Register(nil, layerData, img); err != nil { return nil, err } return img, nil @@ -117,7 +117,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut // Register imports a pre-existing image into the graph. // FIXME: pass img as first argument -func (graph *Graph) Register(jsonData []byte, layerData Archive, store bool, img *Image) error { +func (graph *Graph) Register(jsonData []byte, layerData Archive, img *Image) error { if err := ValidateID(img.ID); err != nil { return err } @@ -130,7 +130,7 @@ func (graph *Graph) Register(jsonData []byte, layerData Archive, store bool, img if err != nil { return fmt.Errorf("Mktemp failed: %s", err) } - if err := StoreImage(img, jsonData, layerData, tmp, store); err != nil { + if err := StoreImage(img, jsonData, layerData, tmp); err != nil { return err } // Commit diff --git a/image.go b/image.go index 7f03cc4bf857f7fb85d41a3c2f3c686ed83fee79..220f1c70a52d70fbfff5733911e24a5113456702 100644 --- a/image.go +++ b/image.go @@ -72,7 +72,7 @@ func LoadImage(root string) (*Image, error) { return img, nil } -func StoreImage(img *Image, jsonData []byte, layerData Archive, root string, store bool) error { +func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) error { // Check that root doesn't already exist if _, err := os.Stat(root); err == nil { return fmt.Errorf("Image %s already exists", img.ID) diff --git a/server.go b/server.go index de73d6c8158874402505aba2d968c18e1c441ddd..aecc7307946d8ab096bab08166c754d037fcf156 100644 --- a/server.go +++ b/server.go @@ -439,7 +439,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return err } defer layer.Close() - if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil { + if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), img); err != nil { return err } } From 394941b6b0a30fecf8ae7b6de5880fa553141f93 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 16:16:31 -0700 Subject: [PATCH 097/101] Switch json/payload order --- auth/auth.go | 4 ++-- utils/tarsum.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index e402031ca2d1cb9dfbdcc78d152462d68a6fd15e..6dd6ceb62072bf9b8dec1902745bc847fbfdf514 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -16,9 +16,9 @@ import ( const CONFIGFILE = ".dockercfg" // Only used for user auth + account creation -//const INDEXSERVER = "https://index.docker.io/v1/" +const INDEXSERVER = "https://index.docker.io/v1/" -const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" +//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" var ( ErrConfigFileMissing = errors.New("The Auth config file is missing") diff --git a/utils/tarsum.go b/utils/tarsum.go index 015b9b3076745a9a828a0636ed079feb9f851de6..d3e1db61f1c540dd5eadf322cade79be05e8e5c6 100644 --- a/utils/tarsum.go +++ b/utils/tarsum.go @@ -145,13 +145,13 @@ func (ts *TarSum) Read(buf []byte) (int, error) { func (ts *TarSum) Sum(extra []byte) string { sort.Strings(ts.sums) h := sha256.New() + if extra != nil { + h.Write(extra) + } for _, sum := range ts.sums { Debugf("-->%s<--", sum) h.Write([]byte(sum)) } - if extra != nil { - h.Write(extra) - } checksum := "tarsum+sha256:" + hex.EncodeToString(h.Sum(nil)) Debugf("checksum processed: %s", checksum) return checksum From 5b27652ac6eaf1bc4c2a16e51919ec4272a58fd6 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 22 Jul 2013 16:44:34 -0700 Subject: [PATCH 098/101] Make sure the index also receives the checksums --- registry/registry.go | 14 +++++++++++++- server.go | 22 ++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 40b9872a4a2011f659960462adf22d792c436a69..4e9dd8895f7507fde8b0fa1dea845ae4e7fe0bbd 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -469,7 +469,19 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token } func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { - imgListJSON, err := json.Marshal(imgList) + cleanImgList := []*ImgData{} + + if validate { + for _, elem := range imgList { + if elem.Checksum != "" { + cleanImgList = append(cleanImgList, elem) + } + } + } else { + cleanImgList = imgList + } + + imgListJSON, err := json.Marshal(cleanImgList) if err != nil { return nil, err } diff --git a/server.go b/server.go index aecc7307946d8ab096bab08166c754d037fcf156..88ff2ac3a655f905e7fb901f497ac205b6f8d9f7 100644 --- a/server.go +++ b/server.go @@ -645,9 +645,11 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) continue } - if err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { + if checksum, err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { // FIXME: Continue on error? return err + } else { + elem.Checksum = checksum } out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { @@ -663,11 +665,11 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName return nil } -func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) error { +func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) { out = utils.NewWriteFlusher(out) jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgID, "json")) if err != nil { - return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err) + return "", fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err) } out.Write(sf.FormatStatus("Pushing %s", imgID)) @@ -679,29 +681,29 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { if err == registry.ErrAlreadyExists { out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID)) - return nil + return "", nil } - return err + return "", err } layerData, err := srv.runtime.graph.TempLayerArchive(imgID, Uncompressed, sf, out) if err != nil { - return fmt.Errorf("Failed to generate layer archive: %s", err) + return "", fmt.Errorf("Failed to generate layer archive: %s", err) } // Send the layer if checksum, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token, jsonRaw); err != nil { - return err + return "", err } else { imgData.Checksum = checksum } // Send the checksum if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil { - return err + return "", err } - return nil + return imgData.Checksum, nil } // FIXME: Allow to interupt current push when new push of same image is done. @@ -739,7 +741,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo var token []string out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName)) - if err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { + if _, err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { return err } return nil From 5dc86d7bca17c2996264a18cc26f06d30e532588 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 29 Jul 2013 14:17:15 -0700 Subject: [PATCH 099/101] Updated the description of run -d The goal is to make it more clear this will give you the container id after run completes. Since stdout is now standard on run, "docker run -d" is the best (or only) way to get the container ID returned from docker after a plain run, but the description (help) does not hint any such thing. --- container.go | 2 +- docs/sources/commandline/command/run.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/container.go b/container.go index d0b6ca4ce26d7ef88520e0ef886c0f9a740a5f9e..d610c3c7d4b5cb01f3d86c596158a6d8cbaf2136 100644 --- a/container.go +++ b/container.go @@ -100,7 +100,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flHostname := cmd.String("h", "", "Container host name") flUser := cmd.String("u", "", "Username or UID") - flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background") + flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id") flAttach := NewAttachOpts() cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached") diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index db67ef0705394273b6b1d125b480a63bbdfc29a2..db043c3b3fbcc23dab0576d7788d6a2731ea2c04 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -15,7 +15,7 @@ -a=map[]: Attach to stdin, stdout or stderr. -c=0: CPU shares (relative weight) -cidfile="": Write the container ID to the file - -d=false: Detached mode: leave the container running in the background + -d=false: Detached mode: Run container in the background, print new container id -e=[]: Set environment variables -h="": Container host name -i=false: Keep stdin open even if not attached From 9ba998312de5826f24f693c8518ecda700135b4b Mon Sep 17 00:00:00 2001 From: dsissitka Date: Tue, 30 Jul 2013 01:39:29 -0400 Subject: [PATCH 100/101] Fixed a couple of minor syntax errors. --- docs/sources/examples/couchdb_data_volumes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/examples/couchdb_data_volumes.rst b/docs/sources/examples/couchdb_data_volumes.rst index d6babe557f5dc2a696ae12045b32a6f2bbbf67de..97af733a82924ec1428f727c3ec9d7a6e499a025 100644 --- a/docs/sources/examples/couchdb_data_volumes.rst +++ b/docs/sources/examples/couchdb_data_volumes.rst @@ -39,7 +39,7 @@ This time, we're requesting shared access to $COUCH1's volumes. .. code-block:: bash - COUCH2=$(docker run -d -volumes-from $COUCH1) shykes/couchdb:2013-05-03) + COUCH2=$(docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03) Browse data on the second database ---------------------------------- @@ -48,6 +48,6 @@ Browse data on the second database HOST=localhost URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/" - echo "Navigate to $URL in your browser. You should see the same data as in the first database!" + echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!' Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data. From b14c251862021b2bc82aa8e0146e5e5e80f1c713 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Jul 2013 13:13:18 +0000 Subject: [PATCH 101/101] fix tests about refactor checksums --- graph_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph_test.go b/graph_test.go index 18682338d98134c9f1ac07fd3f2c0403fa8bee1c..2898fccf99ff123acc00f3c60a0ace3245f76bd2 100644 --- a/graph_test.go +++ b/graph_test.go @@ -38,7 +38,7 @@ func TestInterruptedRegister(t *testing.T) { Comment: "testing", Created: time.Now(), } - go graph.Register(badArchive, false, image) + go graph.Register(nil, badArchive, image) time.Sleep(200 * time.Millisecond) w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling) if _, err := graph.Get(image.ID); err == nil { @@ -49,7 +49,7 @@ func TestInterruptedRegister(t *testing.T) { if err != nil { t.Fatal(err) } - if err := graph.Register(goodArchive, false, image); err != nil { + if err := graph.Register(nil, goodArchive, image); err != nil { t.Fatal(err) } } @@ -95,7 +95,7 @@ func TestRegister(t *testing.T) { Comment: "testing", Created: time.Now(), } - err = graph.Register(archive, false, image) + err = graph.Register(nil, archive, image) if err != nil { t.Fatal(err) } @@ -225,7 +225,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } // Test delete twice (pull -> rm -> pull -> rm) - if err := graph.Register(archive, false, img1); err != nil { + if err := graph.Register(nil, archive, img1); err != nil { t.Fatal(err) } if err := graph.Delete(img1.ID); err != nil {