From dabdb2f88be6308917a27d58dcec853280c6226c Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Fri, 5 Jul 2013 16:49:55 -0700 Subject: [PATCH 01/67] testing, issue #1104: Make the test use static flags Upstream-commit: 4388bef996063b3b69e738082b6820d3a979921e Component: engine --- components/engine/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/Makefile b/components/engine/Makefile index 9b06df3d64..46d003d878 100644 --- a/components/engine/Makefile +++ b/components/engine/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 eb5095a47654ef7a38040a3ed1238ca26a7554be Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:12:12 -0400 Subject: [PATCH 02/67] Added version checker interface Upstream-commit: 1d01189f04f5187bd39e9212b7af3b3e83e86361 Component: engine --- components/engine/registry/registry.go | 42 +++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index fc84f19ec4..12ca3c4bfb 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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 4c0f88c1901c743206002a19815ab2b6abdff7b7 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:24:54 -0400 Subject: [PATCH 03/67] inserted setUserAgent in each HTTP request Upstream-commit: 1bb8f60d5ae3810b465dd3c79a7a572fb017d078 Component: engine --- components/engine/registry/registry.go | 43 ++++++++++++++++---------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 12ca3c4bfb..c51df1ac2d 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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 d233f1b215f2db02d3a0fcae7e70e81f8f0b503a Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:33:28 -0400 Subject: [PATCH 04/67] added APIVersion when call NewRegistry Upstream-commit: 65185a565b8e05a2dd58e10d1c1ad560f4a255cf Component: engine --- components/engine/api_params.go | 12 ++++++++++++ components/engine/server.go | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index b8af690c7f..217062b66f 100644 --- a/components/engine/api_params.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index f1c0909516..c1ea5670e0 100644 --- a/components/engine/server.go +++ b/components/engine/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 addc24cbb83e8eded667e3d1238e460512a67a92 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:42:04 -0400 Subject: [PATCH 05/67] Insert version checkers when call NewRegistry() Upstream-commit: 5705a493080ba5571a7929d37e7926678a982cb4 Component: engine --- components/engine/api_params.go | 12 ------------ components/engine/server.go | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index 217062b66f..b8af690c7f 100644 --- a/components/engine/api_params.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index c1ea5670e0..b943ef2985 100644 --- a/components/engine/server.go +++ b/components/engine/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 5042ef8b4a91c45026c048bf8cc37bd6b0552940 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 17:48:37 -0400 Subject: [PATCH 06/67] added client's kernel version Upstream-commit: d40efc4648af6bb5c60b37a789effd602af1f132 Component: engine --- components/engine/registry/registry.go | 24 +++++++++++++++--------- components/engine/server.go | 7 ++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index c51df1ac2d..683a64ab6a 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index b943ef2985..70bb0bb871 100644 --- a/components/engine/server.go +++ b/components/engine/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 9939aac9f56d4e9b38d2a64ccf374ea7a5b178ff Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 18:45:45 -0400 Subject: [PATCH 07/67] Removed an unnecessary error check. Upstream-commit: 26c8eae6fea53e9a78bd035614fff20086f00b17 Component: engine --- components/engine/registry/registry.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 683a64ab6a..26cefbbdef 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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 81ebd3a19cdb9bffe32e944be596042fb71ff38e Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 18:46:25 -0400 Subject: [PATCH 08/67] Removed an unnecessary nil assignment Upstream-commit: e832b01349fec2acee6ec3219dc9bfb61ad38764 Component: engine --- components/engine/registry/registry.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 26cefbbdef..920f945935 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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 98e450c93d6ab6c0872537e5aad94dd1d4c0b79d Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Fri, 28 Jun 2013 19:29:02 -0400 Subject: [PATCH 09/67] format in the user agent header should follow RFC 2616 Upstream-commit: 34cf976866f66bc77b961f4e66a9dd8aad1ffb00 Component: engine --- components/engine/registry/registry.go | 59 +++++++++++++++++--------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 920f945935..0840ffbb83 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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 ebb59e49d1ecaef26f87222c1295a07672b653eb Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Mon, 1 Jul 2013 17:57:56 -0400 Subject: [PATCH 10/67] reduce the number of string copy operations. Upstream-commit: 73e79a3310f3976b61a295f45e12aead9af41962 Component: engine --- components/engine/registry/registry.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 0840ffbb83..03a2890105 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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 4b7018962aac5e057cc6a5c674b6871dca26e954 Mon Sep 17 00:00:00 2001 From: Jonas Pfenniger Date: Mon, 15 Jul 2013 11:36:05 +0100 Subject: [PATCH 11/67] docker.upstart: avoid spawning a `sh` process start script / end script create an intermediate sh process. Upstream-commit: 24dd50490a027f01ea086eb90663d53348fa770e Component: engine --- components/engine/packaging/ubuntu/docker.upstart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/engine/packaging/ubuntu/docker.upstart b/components/engine/packaging/ubuntu/docker.upstart index 2bd5565ee7..1d35d7a493 100644 --- a/components/engine/packaging/ubuntu/docker.upstart +++ b/components/engine/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 632014a7bf21f6db9ed19062cb00f454e5359ac9 Mon Sep 17 00:00:00 2001 From: Jonas Pfenniger Date: Mon, 15 Jul 2013 11:41:19 +0100 Subject: [PATCH 12/67] docker.upstart: use the same start/stop events as sshd Is probably more solid Upstream-commit: 0900d3b7a6e94bfa42e3d4ac6dc6f5542f65a9b0 Component: engine --- components/engine/packaging/ubuntu/docker.upstart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/engine/packaging/ubuntu/docker.upstart b/components/engine/packaging/ubuntu/docker.upstart index 1d35d7a493..f4d2fbe922 100644 --- a/components/engine/packaging/ubuntu/docker.upstart +++ b/components/engine/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 3427bb07afb397f47e0027a900fdca6b493ce90f Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Thu, 18 Jul 2013 14:22:49 -0400 Subject: [PATCH 13/67] documentation. Upstream-commit: cd209f406e889feaba50103e5ce50f7dcd23767a Component: engine --- components/engine/registry/registry.go | 8 ++++++++ components/engine/server.go | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 03a2890105..6ba80cbea5 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index 70bb0bb871..925e4e3386 100644 --- a/components/engine/server.go +++ b/components/engine/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 a6f1fb98b1bf385b6169609ca602413ffa9d634a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 02:47:35 +0000 Subject: [PATCH 14/67] fix overwrites EXPOSE Upstream-commit: a0eec14c7da5b213302c2675801aaf788e84efed Component: engine --- components/engine/utils.go | 2 +- components/engine/utils_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/engine/utils.go b/components/engine/utils.go index 33cfbe506f..16efccd387 100644 --- a/components/engine/utils.go +++ b/components/engine/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/components/engine/utils_test.go b/components/engine/utils_test.go index 1bd18f4af0..5ea3682bac 100644 --- a/components/engine/utils_test.go +++ b/components/engine/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 31b43ff70e4fde323653f6bd55e31561697cace9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 03:01:39 +0000 Subject: [PATCH 15/67] add regression test from @crosbymichael Upstream-commit: 2b5386f039b5c99cf0f64fb3091cc14e4446dc64 Component: engine --- components/engine/utils_test.go | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 5ea3682bac..78f3afa66c 100644 --- a/components/engine/utils_test.go +++ b/components/engine/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 1ef21555e984d3354ea98f6970e76cf11857c1ef Mon Sep 17 00:00:00 2001 From: Ryan Fowler Date: Fri, 19 Jul 2013 10:11:21 -0500 Subject: [PATCH 16/67] 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. Upstream-commit: e8ad82f9ba126414e1813fadfb17167a34afa8d4 Component: engine --- components/engine/docs/sources/use/builder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/use/builder.rst b/components/engine/docs/sources/use/builder.rst index 9ea8033b98..3f7a8e8ef1 100644 --- a/components/engine/docs/sources/use/builder.rst +++ b/components/engine/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 744c5e76581069d10de0cecac5565f9359cc098b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 16:36:23 +0000 Subject: [PATCH 17/67] add LXC version to docker info in debug mode Upstream-commit: 921c6994b1ad41c940bdb08732225b8db74b68f2 Component: engine --- components/engine/api_params.go | 9 +++++---- components/engine/commands.go | 1 + components/engine/server.go | 9 +++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index b371ca314f..967b736bdc 100644 --- a/components/engine/api_params.go +++ b/components/engine/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/components/engine/commands.go b/components/engine/commands.go index f0e1695b3f..e2b26321f5 100644 --- a/components/engine/commands.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index b92ed8fd73..90d56f74a0 100644 --- a/components/engine/server.go +++ b/components/engine/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 edbf8f58a1d6cea66e7b6f74c474f415c59055e0 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 17:11:47 -0700 Subject: [PATCH 18/67] Support networkless containers with new docker run option '-n' Upstream-commit: 3342bdb33184b83cac66921807c5403168d13f6b Component: engine --- components/engine/container.go | 86 +++++++++++-------- components/engine/container_test.go | 38 ++++++++ .../docs/sources/commandline/command/run.rst | 1 + components/engine/lxc_template.go | 5 ++ 4 files changed, 93 insertions(+), 37 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index f18aa0fe74..6ae9ab5a44 100644 --- a/components/engine/container.go +++ b/components/engine/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/components/engine/container_test.go b/components/engine/container_test.go index 028c03a318..8d4bbf6c74 100644 --- a/components/engine/container_test.go +++ b/components/engine/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/components/engine/docs/sources/commandline/command/run.rst b/components/engine/docs/sources/commandline/command/run.rst index 19efd85821..db67ef0705 100644 --- a/components/engine/docs/sources/commandline/command/run.rst +++ b/components/engine/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/components/engine/lxc_template.go b/components/engine/lxc_template.go index 93b795e901..27670c8076 100644 --- a/components/engine/lxc_template.go +++ b/components/engine/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 ca9f0f0f1d03aa55725621064d1efbc2ee779ea9 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 17:49:09 -0700 Subject: [PATCH 19/67] Support completely disabling network configuration with docker -d -b none Upstream-commit: 49673fc45cc5cfc15219bf1eb6eaff7621696919 Component: engine --- components/engine/container.go | 8 ++++++-- components/engine/network.go | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 6ae9ab5a44..189e882c44 100644 --- a/components/engine/container.go +++ b/components/engine/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/components/engine/network.go b/components/engine/network.go index 0f98c899f1..c71ecfb3ae 100644 --- a/components/engine/network.go +++ b/components/engine/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 466341e775d86c7ccece302d97ed2f05bf14ac74 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Sun, 21 Jul 2013 18:01:52 -0700 Subject: [PATCH 20/67] Document -b none Upstream-commit: 964e826a9beb69a0246ed22c7935fd86df6712e1 Component: engine --- components/engine/docker/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index fb7c465369..2db50bf328 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/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 7d9d3be42244fbf9e7d1b3342dbb589bb6f98381 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Jul 2013 14:52:05 +0000 Subject: [PATCH 21/67] fix error message when invalid directory Upstream-commit: 74a2b13687787a33b0963f020f704d3cdde4b06d Component: engine --- components/engine/commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/commands.go b/components/engine/commands.go index 86385f4187..b0e32162e6 100644 --- a/components/engine/commands.go +++ b/components/engine/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 48287f8a5dee309c42e7f3ebf30e73a23e8577af Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Jul 2013 16:26:05 +0000 Subject: [PATCH 22/67] fix test env Upstream-commit: 5c1af383eb14121174b43fa706f524d4eedd5cad Component: engine --- components/engine/container_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/container_test.go b/components/engine/container_test.go index 028c03a318..a90018decc 100644 --- a/components/engine/container_test.go +++ b/components/engine/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 4f8c565f24443a5b9f513840c9b7ee5693378cd8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 12:06:24 -0700 Subject: [PATCH 23/67] Allocate a /16 IP range by default, with fallback to /24. Try a total of 12 ranges instead of 3. Upstream-commit: 4714f102d72f03159acd0f7be71cde3d169c06b8 Component: engine --- components/engine/network.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/components/engine/network.go b/components/engine/network.go index 0f98c899f1..d2d6668b26 100644 --- a/components/engine/network.go +++ b/components/engine/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 8d7759fbb0d4e9493e0a8011d873202361f9e518 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:32:55 -0700 Subject: [PATCH 24/67] Hack: first draft of the maintainer boot camp. Work in progress! Upstream-commit: ce43f4af1cf7c2692c4c6203bc5872053d089863 Component: engine --- components/engine/hack/bootcamp/README.md | 92 +++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 components/engine/hack/bootcamp/README.md diff --git a/components/engine/hack/bootcamp/README.md b/components/engine/hack/bootcamp/README.md new file mode 100644 index 0000000000..f4cc91f19e --- /dev/null +++ b/components/engine/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 4966c3626567c68210b05751507fe545fe1bb53e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:36:36 -0700 Subject: [PATCH 25/67] Hack: completed step 12 of the bootcamp Upstream-commit: 5714f0a74ef6f6b0be521d7e1e21c1cab1a9ad73 Component: engine --- components/engine/hack/bootcamp/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/engine/hack/bootcamp/README.md b/components/engine/hack/bootcamp/README.md index f4cc91f19e..88440e8c64 100644 --- a/components/engine/hack/bootcamp/README.md +++ b/components/engine/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 f428eaec77f1d151b3e7eaba7e7357c3838da163 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 22 Jul 2013 18:39:58 -0700 Subject: [PATCH 26/67] Typo in 3rd-party Upstream-commit: 6745bdd0b3a7944985614dae194538645eacb4e7 Component: engine --- components/engine/hack/bootcamp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/hack/bootcamp/README.md b/components/engine/hack/bootcamp/README.md index 88440e8c64..2c3d356daf 100644 --- a/components/engine/hack/bootcamp/README.md +++ b/components/engine/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 d56c0e2aac92069b0a1f5260782eea5ad2f1ea72 Mon Sep 17 00:00:00 2001 From: Stefan Praszalowicz Date: Mon, 22 Jul 2013 19:00:35 -0700 Subject: [PATCH 27/67] Invert network disable flag and logic (unbreaks TestAllocate*PortLocalhost) Upstream-commit: bc172e5e5f1f231f878727a180a8da46e653c0a7 Component: engine --- components/engine/container.go | 84 +++++++++++++++---------------- components/engine/lxc_template.go | 8 +-- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 189e882c44..f172604356 100644 --- a/components/engine/container.go +++ b/components/engine/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/components/engine/lxc_template.go b/components/engine/lxc_template.go index 27670c8076..d1f67e3376 100644 --- a/components/engine/lxc_template.go +++ b/components/engine/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 fab05b9aee075fdd63da7d772b3ee9af2754ae1f Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 22 Jul 2013 20:26:40 -0700 Subject: [PATCH 28/67] Added new docker logo to the documentation header, and added other links. Upstream-commit: 58a1c5720a42fde72c27532ce1122609225bc3bb Component: engine --- components/engine/docs/sources/use/basics.rst | 4 ++-- components/engine/docs/theme/docker/layout.html | 13 ++++++------- .../engine/docs/theme/docker/static/css/main.css | 10 +++++----- .../docs/theme/docker/static/css/main.less | 10 +++------- .../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 components/engine/docs/theme/docker/static/img/docker-top-logo.png create mode 100644 components/engine/docs/theme/docker/static/img/external-link-icon.png diff --git a/components/engine/docs/sources/use/basics.rst b/components/engine/docs/sources/use/basics.rst index 7c9e2e9055..7ce86416dd 100644 --- a/components/engine/docs/sources/use/basics.rst +++ b/components/engine/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/components/engine/docs/theme/docker/layout.html b/components/engine/docs/theme/docker/layout.html index f4f17f2dba..198cd5d7d8 100755 --- a/components/engine/docs/theme/docker/layout.html +++ b/components/engine/docs/theme/docker/layout.html @@ -68,19 +68,18 @@
- +
@@ -96,7 +95,7 @@ -

DOCUMENTATION

+

DOCUMENTATION

diff --git a/components/engine/docs/theme/docker/static/css/main.css b/components/engine/docs/theme/docker/static/css/main.css index c79d590e07..0b11890f71 100755 --- a/components/engine/docs/theme/docker/static/css/main.css +++ b/components/engine/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/components/engine/docs/theme/docker/static/css/main.less b/components/engine/docs/theme/docker/static/css/main.less index c8c38dc8ed..8c470518d8 100644 --- a/components/engine/docs/theme/docker/static/css/main.less +++ b/components/engine/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/components/engine/docs/theme/docker/static/img/docker-top-logo.png b/components/engine/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 001001e2115464013e9e11d414b0eedf42af0217 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 23 Jul 2013 15:04:31 +0000 Subject: [PATCH 29/67] change dockercfg to json and support multiple auth remote Upstream-commit: 3bae188b8dc51911a44ea1c7b5681f9f07f9d3af Component: engine --- components/engine/api.go | 58 ++---------------- components/engine/auth/auth.go | 104 +++++++++++++++++---------------- components/engine/commands.go | 52 +++++++++-------- 3 files changed, 87 insertions(+), 127 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index b6ab7badfa..975134f22d 100644 --- a/components/engine/api.go +++ b/components/engine/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/components/engine/auth/auth.go b/components/engine/auth/auth.go index 97df928b6b..8f4d09fb8f 100644 --- a/components/engine/auth/auth.go +++ b/components/engine/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") + + 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 + } } - origAuth := strings.Split(arr[0], " = ") - origEmail := strings.Split(arr[1], " = ") - authConfig, err := decodeAuth(origAuth[1]) - if err != nil { - return nil, err - } - 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/components/engine/commands.go b/components/engine/commands.go index b0e32162e6..17597320b5 100644 --- a/components/engine/commands.go +++ b/components/engine/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 d1efb7843b268ddf08b021ccf27946d7c59217d5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 10 Jul 2013 12:55:05 +0000 Subject: [PATCH 30/67] basic version of the /events endpoint Upstream-commit: b5da816487d68853a8ac46630cb3118646f71d2d Component: engine --- components/engine/api.go | 28 ++++++++++++++++++++++++- components/engine/api_params.go | 15 +++++++------- components/engine/commands.go | 31 ++++++++++++++++++++-------- components/engine/runtime_test.go | 12 +++++------ components/engine/server.go | 34 +++++++++++++++++++++++-------- components/engine/utils/utils.go | 15 ++++++++++++++ 6 files changed, 104 insertions(+), 31 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index b6ab7badfa..9d0348b608 100644 --- a/components/engine/api.go +++ b/components/engine/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/components/engine/api_params.go b/components/engine/api_params.go index b371ca314f..26d70711a1 100644 --- a/components/engine/api_params.go +++ b/components/engine/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/components/engine/commands.go b/components/engine/commands.go index b0e32162e6..12647feead 100644 --- a/components/engine/commands.go +++ b/components/engine/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/components/engine/runtime_test.go b/components/engine/runtime_test.go index 66d92c8100..807097404d 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index b92ed8fd73..2499d64397 100644 --- a/components/engine/server.go +++ b/components/engine/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/components/engine/utils/utils.go b/components/engine/utils/utils.go index 77b3f879cd..1523835f99 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 fd89a1c59f66d03ee2e809f4eb9d574af2d2e14d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 12 Jul 2013 15:09:20 +0000 Subject: [PATCH 31/67] add timestamp and change untagged -> untag Upstream-commit: b8d52ec2669332988a972bff3b5f5d2e9d526b33 Component: engine --- components/engine/server.go | 5 +++-- components/engine/utils/utils.go | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index 2499d64397..8ba90c69e3 100644 --- a/components/engine/server.go +++ b/components/engine/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/components/engine/utils/utils.go b/components/engine/utils/utils.go index 1523835f99..acb015becd 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 012e9440352ecf0138ba0c7b5e8b8e8bbec44fc5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 12 Jul 2013 16:29:23 +0000 Subject: [PATCH 32/67] add since for polling, rename some vars Upstream-commit: 2e4d4c9f60d0ad92ab0cc84c56c060678222c4db Component: engine --- components/engine/api.go | 51 ++++++++++++++++++++++++++++------- components/engine/commands.go | 10 +++++-- components/engine/server.go | 33 +++++++++++++---------- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 9d0348b608..0a7f5744b1 100644 --- a/components/engine/api.go +++ b/components/engine/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/components/engine/commands.go b/components/engine/commands.go index 12647feead..2ab107bb77 100644 --- a/components/engine/commands.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index 8ba90c69e3..7efb850882 100644 --- a/components/engine/server.go +++ b/components/engine/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 05d166e113cf82f6e67f7b0efeb00212a4f6597f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 15:44:06 +0200 Subject: [PATCH 33/67] add docs Upstream-commit: ec559c02b8dd70692821b3dc7f497d6fccaa88ad Component: engine --- .../docs/sources/api/docker_remote_api.rst | 4 +++ .../sources/api/docker_remote_api_v1.3.rst | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/components/engine/docs/sources/api/docker_remote_api.rst b/components/engine/docs/sources/api/docker_remote_api.rst index f8a1381c53..792d43f501 100644 --- a/components/engine/docs/sources/api/docker_remote_api.rst +++ b/components/engine/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/components/engine/docs/sources/api/docker_remote_api_v1.3.rst b/components/engine/docs/sources/api/docker_remote_api_v1.3.rst index 273ec2e98d..69f480e453 100644 --- a/components/engine/docs/sources/api/docker_remote_api_v1.3.rst +++ b/components/engine/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 35c9a9d01e288176a1302a294bb1d0a6a76fda5b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 17 Jul 2013 13:56:09 +0000 Subject: [PATCH 34/67] getEvents a bit simpler Upstream-commit: 8b3519c5f7540b99d19ed2c3163aabf9897dd5a4 Component: engine --- components/engine/api.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 0a7f5744b1..dd3e8eed0d 100644 --- a/components/engine/api.go +++ b/components/engine/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 ae5b19f00259a1ce4b1d33643e557640be5bd3cb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 14:35:14 +0000 Subject: [PATCH 35/67] use non-blocking channel to prevent dead-lock and add test for server Upstream-commit: 040c3b50d0a56baf98bd1ec14ad7d59c55a4ab31 Component: engine --- components/engine/server.go | 5 +++- components/engine/server_test.go | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/components/engine/server.go b/components/engine/server.go index 7efb850882..8eff17a947 100644 --- a/components/engine/server.go +++ b/components/engine/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/components/engine/server_test.go b/components/engine/server_test.go index 05a286aaa8..8612b3fcea 100644 --- a/components/engine/server_test.go +++ b/components/engine/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 211bbde153caf194c9c48898a3b815a886a1be50 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 14:54:58 +0000 Subject: [PATCH 36/67] Add tests for the api Upstream-commit: ed7a4236b32f3f711c183dff8b70fbef17bae2d7 Component: engine --- components/engine/api_test.go | 38 ++++++++++++++++++++++++++++++ components/engine/commands_test.go | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index 17ada96eab..13731dbf9e 100644 --- a/components/engine/api_test.go +++ b/components/engine/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/components/engine/commands_test.go b/components/engine/commands_test.go index 233c6337d4..030fb29f95 100644 --- a/components/engine/commands_test.go +++ b/components/engine/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 056ee7cfd5b6a8f0e395263cc735a943c9d11a7e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 14:31:42 +0000 Subject: [PATCH 37/67] add die event Upstream-commit: a41384ad7312a21fd8fe429637c8d6b5c883fa2a Component: engine --- components/engine/container.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index f4a3c762ab..1e7bfed299 100644 --- a/components/engine/container.go +++ b/components/engine/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 f64e80d22bc7277b49ed6ef9c74c235a29b4bd31 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 23 Jul 2013 19:55:38 +0000 Subject: [PATCH 38/67] update AUTHORS Upstream-commit: 7aba68cd548d69e10e710029ca143b32fd291585 Component: engine --- components/engine/AUTHORS | 1 + components/engine/runtime_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 7506cd885c..86d03f6e12 100644 --- a/components/engine/AUTHORS +++ b/components/engine/AUTHORS @@ -76,6 +76,7 @@ Shawn Siefkas Silas Sewell Solomon Hykes Sridhar Ratnakumar +Stefan Praszalowicz Thatcher Peskens Thomas Bikeev Thomas Hansen diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 66d92c8100..807097404d 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/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 ff1a26c4e5d8f0fb8115a6f03e3b9176f45a0cfd Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Tue, 23 Jul 2013 13:05:06 -0700 Subject: [PATCH 39/67] 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/ Upstream-commit: e701dce33978a0206627a02b258da6a0269c4b60 Component: engine --- components/engine/docs/theme/MAINTAINERS | 2 +- .../engine/docs/theme/docker/layout.html | 8 +- components/engine/docs/website/MAINTAINERS | 1 - components/engine/docs/website/dotcloud.yml | 2 - .../docs/website/gettingstarted/index.html | 220 ----------- components/engine/docs/website/index.html | 359 ------------------ components/engine/docs/website/nginx.conf | 6 - components/engine/docs/website/static | 1 - 8 files changed, 5 insertions(+), 594 deletions(-) delete mode 100644 components/engine/docs/website/MAINTAINERS delete mode 100644 components/engine/docs/website/dotcloud.yml delete mode 100644 components/engine/docs/website/gettingstarted/index.html delete mode 100644 components/engine/docs/website/index.html delete mode 100644 components/engine/docs/website/nginx.conf delete mode 120000 components/engine/docs/website/static diff --git a/components/engine/docs/theme/MAINTAINERS b/components/engine/docs/theme/MAINTAINERS index 6df367c073..606a1dd746 100644 --- a/components/engine/docs/theme/MAINTAINERS +++ b/components/engine/docs/theme/MAINTAINERS @@ -1 +1 @@ -Thatcher Penskens +Thatcher Peskens diff --git a/components/engine/docs/theme/docker/layout.html b/components/engine/docs/theme/docker/layout.html index 198cd5d7d8..0b22f22fab 100755 --- a/components/engine/docs/theme/docker/layout.html +++ b/components/engine/docs/theme/docker/layout.html @@ -68,12 +68,12 @@ diff --git a/components/engine/docs/website/MAINTAINERS b/components/engine/docs/website/MAINTAINERS deleted file mode 100644 index 6df367c073..0000000000 --- a/components/engine/docs/website/MAINTAINERS +++ /dev/null @@ -1 +0,0 @@ -Thatcher Penskens diff --git a/components/engine/docs/website/dotcloud.yml b/components/engine/docs/website/dotcloud.yml deleted file mode 100644 index 5a8f50f9e9..0000000000 --- a/components/engine/docs/website/dotcloud.yml +++ /dev/null @@ -1,2 +0,0 @@ -www: - type: static \ No newline at end of file diff --git a/components/engine/docs/website/gettingstarted/index.html b/components/engine/docs/website/gettingstarted/index.html deleted file mode 100644 index de0cc3512d..0000000000 --- a/components/engine/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/components/engine/docs/website/index.html b/components/engine/docs/website/index.html deleted file mode 100644 index f6f4efbccb..0000000000 --- a/components/engine/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/components/engine/docs/website/nginx.conf b/components/engine/docs/website/nginx.conf deleted file mode 100644 index 97ffd2c0e5..0000000000 --- a/components/engine/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/components/engine/docs/website/static b/components/engine/docs/website/static deleted file mode 120000 index 95bc97aa10..0000000000 --- a/components/engine/docs/website/static +++ /dev/null @@ -1 +0,0 @@ -../theme/docker/static \ No newline at end of file From 0da0fa300a8b0f3253d7ad1ca8809cc784f77313 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Tue, 23 Jul 2013 17:05:13 -0400 Subject: [PATCH 40/67] Rename: VersionChecker->VersionInfo. Upstream-commit: ede1e6d4754f3cffeac72f5d760fe4d87c5ae570 Component: engine --- components/engine/registry/registry.go | 16 ++++++++-------- components/engine/runtime_test.go | 12 ++++++------ components/engine/server.go | 22 +++++++++++----------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 6ba80cbea5..e6f4f592e2 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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/components/engine/runtime_test.go b/components/engine/runtime_test.go index 6b94e5ce2c..74ca09cc8d 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index 925e4e3386..62c0243820 100644 --- a/components/engine/server.go +++ b/components/engine/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 e919f6ad2f5da5ffc51cd6d835c6ed0e6e887810 Mon Sep 17 00:00:00 2001 From: Nan Monnand Deng Date: Tue, 23 Jul 2013 17:17:31 -0400 Subject: [PATCH 41/67] versionCheckers()->versionInfos(). Upstream-commit: 1ae54707a0ee1f690a7dca17d83b5417e83704c3 Component: engine --- components/engine/server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index 62c0243820..b706ceda12 100644 --- a/components/engine/server.go +++ b/components/engine/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 600442316a12c03b8e232ad3720a559e575d841e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Jul 2013 17:27:49 -0700 Subject: [PATCH 42/67] Reimplement old Commands unit tests in order to insure behavior Upstream-commit: bc823acc255a786b00a7b2759b67fd7b3256fce3 Component: engine --- components/engine/commands.go | 4 +- components/engine/commands_test.go | 223 ++++++++++++++++++++++++++++- 2 files changed, 225 insertions(+), 2 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index b0e32162e6..44498b04da 100644 --- a/components/engine/commands.go +++ b/components/engine/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/components/engine/commands_test.go b/components/engine/commands_test.go index 233c6337d4..88902b8503 100644 --- a/components/engine/commands_test.go +++ b/components/engine/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 5190220000386db2d54955aad25f7e37abdf97f0 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 23 Jul 2013 18:13:53 -0700 Subject: [PATCH 43/67] Cleaned up long lines, switched graphic to Docker logo. General cleanup. Upstream-commit: 78c02d038f90b111f7f8ad306f9573c0f64b370c Component: engine --- components/engine/README.md | 289 +++++++++++++++++++++++++----------- 1 file changed, 204 insertions(+), 85 deletions(-) diff --git a/components/engine/README.md b/components/engine/README.md index 823e48496a..96da13feaf 100644 --- a/components/engine/README.md +++ b/components/engine/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: +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. + * *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). +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. +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! +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. + * *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. + * 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. +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. +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. +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 f910d92eadbca2cbbfe5ba0a9c1b96125e0ac940 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 12:28:22 +0000 Subject: [PATCH 44/67] fix tests Upstream-commit: f4b41e1a6c2c5d531451bf2feeb3877e03eb8c1c Component: engine --- components/engine/api.go | 2 +- components/engine/auth/auth.go | 1 + components/engine/auth/auth_test.go | 14 ++++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/components/engine/api.go b/components/engine/api.go index 94c1a3b8d2..834c41a68c 100644 --- a/components/engine/api.go +++ b/components/engine/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/components/engine/auth/auth.go b/components/engine/auth/auth.go index 8f4d09fb8f..bffed49807 100644 --- a/components/engine/auth/auth.go +++ b/components/engine/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/components/engine/auth/auth_test.go b/components/engine/auth/auth_test.go index d036de7364..458a505ea2 100644 --- a/components/engine/auth/auth_test.go +++ b/components/engine/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 743adaf705fb847ff7dd4837752df2e65bfbc87a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Jul 2013 13:35:38 +0000 Subject: [PATCH 45/67] add kernel version Upstream-commit: 6057e6ad70ba4550df358e89b9a7e1da61666604 Component: engine --- components/engine/api_params.go | 1 + components/engine/commands.go | 1 + .../engine/docs/sources/api/docker_remote_api_v1.3.rst | 5 ++++- components/engine/server.go | 5 +++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index fcffb04346..0214b89fdb 100644 --- a/components/engine/api_params.go +++ b/components/engine/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/components/engine/commands.go b/components/engine/commands.go index c0e349c8a3..a868b5e5b2 100644 --- a/components/engine/commands.go +++ b/components/engine/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/components/engine/docs/sources/api/docker_remote_api_v1.3.rst b/components/engine/docs/sources/api/docker_remote_api_v1.3.rst index 69f480e453..401855365e 100644 --- a/components/engine/docs/sources/api/docker_remote_api_v1.3.rst +++ b/components/engine/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/components/engine/server.go b/components/engine/server.go index 1395007911..147c5fa436 100644 --- a/components/engine/server.go +++ b/components/engine/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 4350e3145d3ebd2877707937d6d0aa6725e8d37f Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 24 Jul 2013 15:48:18 -0700 Subject: [PATCH 46/67] Fixes #505 - Make sure all output is send on the network before closing Upstream-commit: fd9ad1a19469d07944ca9b417861d63ecec2ef42 Component: engine --- components/engine/api_test.go | 8 +++++++- components/engine/container.go | 13 +++++++------ components/engine/runtime_test.go | 19 +++++++++++++------ components/engine/z_final_test.go | 9 +++++++-- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index 13731dbf9e..a0724b0ba3 100644 --- a/components/engine/api_test.go +++ b/components/engine/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/components/engine/container.go b/components/engine/container.go index dea81b6def..ec4abffc1b 100644 --- a/components/engine/container.go +++ b/components/engine/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/components/engine/runtime_test.go b/components/engine/runtime_test.go index 807097404d..0b0f62f199 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/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/components/engine/z_final_test.go b/components/engine/z_final_test.go index 78a7acf6e7..08a180baaf 100644 --- a/components/engine/z_final_test.go +++ b/components/engine/z_final_test.go @@ -6,7 +6,12 @@ import ( "testing" ) -func TestFinal(t *testing.T) { - cleanup(globalRuntime) +func displayFdGoroutines(t *testing.T) { t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) } + +func TestFinal(t *testing.T) { + cleanup(globalRuntime) + t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) + displayFdGoroutines(t) +} From 164f76762a891b2a371d329a5efb2445ada505f1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 25 Jul 2013 00:35:52 +0000 Subject: [PATCH 47/67] 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. Upstream-commit: 9332c00ca562e97045490d3d45d8f805fae30330 Component: engine --- components/engine/auth/auth.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/components/engine/auth/auth.go b/components/engine/auth/auth.go index bffed49807..39de876875 100644 --- a/components/engine/auth/auth.go +++ b/components/engine/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 e6f304a5c604455cad0d51c8ad27367ee0c243e8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 25 Jul 2013 03:25:16 +0000 Subject: [PATCH 48/67] Add regression test for authConfig overwrite Upstream-commit: 0fc11699ab26121e4f89808ffacb2becf536ff5d Component: engine --- components/engine/auth/auth_test.go | 39 ++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/components/engine/auth/auth_test.go b/components/engine/auth/auth_test.go index 458a505ea2..d94d429da1 100644 --- a/components/engine/auth/auth_test.go +++ b/components/engine/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 c0c3fbc7b6cf7beefd4ddf99351940fb6db525ca Mon Sep 17 00:00:00 2001 From: Daniel YC Lin Date: Thu, 25 Jul 2013 15:36:32 +0800 Subject: [PATCH 49/67] Fixes #1286 Upstream-commit: 8f6b6d57840410d1491321d7681ef2a946e90bc9 Component: engine --- components/engine/commands.go | 2 +- .../engine/docs/sources/commandline/command/import.rst | 7 ++++--- components/engine/packaging/debian/lxc-docker.1 | 6 ++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index f0e1695b3f..2e346e2034 100644 --- a/components/engine/commands.go +++ b/components/engine/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/components/engine/docs/sources/commandline/command/import.rst b/components/engine/docs/sources/commandline/command/import.rst index 66bcf5de52..0083068e10 100644 --- a/components/engine/docs/sources/commandline/command/import.rst +++ b/components/engine/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/components/engine/packaging/debian/lxc-docker.1 b/components/engine/packaging/debian/lxc-docker.1 index 8f98785285..cc20299fad 100644 --- a/components/engine/packaging/debian/lxc-docker.1 +++ b/components/engine/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 15664f41616d48853dd8fd425b707f61160446d9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 25 Jul 2013 15:45:15 +0000 Subject: [PATCH 50/67] use 0755 instead of 0700 Upstream-commit: 1c509f4350d943c6aa8b9bff8dcbed28ee803735 Component: engine --- components/engine/image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/image.go b/components/engine/image.go index e1b1ac0418..5240ec776f 100644 --- a/components/engine/image.go +++ b/components/engine/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 795426f1f2b90b3ddabcbe8be4af37ee44737831 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 23 Jul 2013 11:37:13 -0700 Subject: [PATCH 51/67] Make sure the cookie is used in all registry queries Upstream-commit: 3425c1b84c3f58ac5bb2feb91c4901b61561c58c Component: engine --- components/engine/registry/registry.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index e6f4f592e2..adef1c7baa 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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 7c51899dd52c23c8ae59b5660314c2d0df5c5e7a Mon Sep 17 00:00:00 2001 From: Fareed Dudhia Date: Tue, 9 Jul 2013 07:01:45 +0000 Subject: [PATCH 52/67] Fixes 1136; Reopened from 1175 with latest changes. Upstream-commit: d86898b0142f2f9a834aa0c727b10d62ef647262 Component: engine --- components/engine/buildfile.go | 69 +++++++++++++++++++++++++---- components/engine/buildfile_test.go | 42 +++++++++++++++++- components/engine/utils/utils.go | 7 ++- 3 files changed, 103 insertions(+), 15 deletions(-) diff --git a/components/engine/buildfile.go b/components/engine/buildfile.go index 75ebdd7a7c..c5171aaa91 100644 --- a/components/engine/buildfile.go +++ b/components/engine/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/components/engine/buildfile_test.go b/components/engine/buildfile_test.go index b7eca52336..78e53b8419 100644 --- a/components/engine/buildfile_test.go +++ b/components/engine/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/components/engine/utils/utils.go b/components/engine/utils/utils.go index acb015becd..c70e80b72e 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 4118c3f05998e5880d5d5d1e9a878e3400e96760 Mon Sep 17 00:00:00 2001 From: Mike Gaffney Date: Fri, 26 Jul 2013 01:10:42 -0700 Subject: [PATCH 53/67] Change reserve-compatibility to reverse-compatibility Upstream-commit: 4ebe2cf348915415c34503aca7a5663177e0002f Component: engine --- components/engine/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index ec4abffc1b..d0b6ca4ce2 100644 --- a/components/engine/container.go +++ b/components/engine/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 8f7401af36bf513edf440d779a1be4e7f7177b44 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 26 Jul 2013 09:19:26 +0000 Subject: [PATCH 54/67] fix wrong untag when using rmi via id Upstream-commit: e608296bc62ceeaf41ebf2bc80b21c0a1883d4f0 Component: engine --- components/engine/server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/server.go b/components/engine/server.go index ce1fc8eaf8..fa8d8a0262 100644 --- a/components/engine/server.go +++ b/components/engine/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 cdbe046a767d82cc86c585545ff89f52fd3f59f0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 26 Jul 2013 10:01:41 +0000 Subject: [PATCH 55/67] add regression test Upstream-commit: e592f1b298c778d0b9adfd6751f5fe1843a7001d Component: engine --- components/engine/server.go | 2 +- components/engine/server_test.go | 86 ++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/components/engine/server.go b/components/engine/server.go index fa8d8a0262..56f738a5cd 100644 --- a/components/engine/server.go +++ b/components/engine/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/components/engine/server_test.go b/components/engine/server_test.go index 8612b3fcea..b6b21cc75f 100644 --- a/components/engine/server_test.go +++ b/components/engine/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 d391806121d62d6538dab5ad1305a6169a11ed36 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 26 Jul 2013 10:21:17 -0700 Subject: [PATCH 56/67] Clean up 'manifesto' in docs Upstream-commit: a97d858b2a0d6bc4d044a241036b97fd78f93022 Component: engine --- .../docs/sources/concepts/manifesto.rst | 61 ------------------- 1 file changed, 61 deletions(-) diff --git a/components/engine/docs/sources/concepts/manifesto.rst b/components/engine/docs/sources/concepts/manifesto.rst index ae09647094..7dd4b4bdda 100644 --- a/components/engine/docs/sources/concepts/manifesto.rst +++ b/components/engine/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 b0bd5c4e73bce08fe90dcb32da06427da5ac82b5 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 26 Jul 2013 15:40:55 -0400 Subject: [PATCH 57/67] Bind daemon to 0.0.0.0 in Vagrant. Fixes #1304 Upstream-commit: bdc79ac8b2dfad302f9e144711067a566726cfa2 Component: engine --- components/engine/Vagrantfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/engine/Vagrantfile b/components/engine/Vagrantfile index aadabb8711..7258af5bf7 100644 --- a/components/engine/Vagrantfile +++ b/components/engine/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 cd3a1b485585f13de208d23b5e546adec9603ab3 Mon Sep 17 00:00:00 2001 From: Jonathan Rudenberg Date: Fri, 26 Jul 2013 15:44:06 -0400 Subject: [PATCH 58/67] Update AUTHORS Upstream-commit: 5eb590e79d081ecfa82ba940ea856c4a2c0411ca Component: engine --- components/engine/.mailmap | 2 ++ components/engine/AUTHORS | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/components/engine/.mailmap b/components/engine/.mailmap index 452ac41d8f..11ff5357d8 100644 --- a/components/engine/.mailmap +++ b/components/engine/.mailmap @@ -23,3 +23,5 @@ Thatcher Peskens Walter Stanish Roberto Hashioka +Konstantin Pelykh +David Sissitka diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 86d03f6e12..c811de6526 100644 --- a/components/engine/AUTHORS +++ b/components/engine/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 bdd8af79196724d7ddfbef579bd46ec292c6dd3e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 26 Jul 2013 14:57:16 -0700 Subject: [PATCH 59/67] - Builder: Create directories with 755 instead of 700 within ADD instruction Upstream-commit: b15cfd3530cc228dc065746c9323758c8abb0481 Component: engine --- components/engine/buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/buildfile.go b/components/engine/buildfile.go index 75ebdd7a7c..a25b86d794 100644 --- a/components/engine/buildfile.go +++ b/components/engine/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 3fa2b3c80a598c17233759282718060e16740f84 Mon Sep 17 00:00:00 2001 From: Mike Gaffney Date: Fri, 26 Jul 2013 18:29:27 -0700 Subject: [PATCH 60/67] Add required go version for compilation Upstream-commit: 2d85a20c71c9418301c3161c6853a07fca612676 Component: engine --- components/engine/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/README.md b/components/engine/README.md index 96da13feaf..5e03b2993c 100644 --- a/components/engine/README.md +++ b/components/engine/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 fba18c65a3185bea2bb32ef461b57e3aff48c25c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 10:00:36 -0700 Subject: [PATCH 61/67] Do not show empty parenthesis if the default configuration is missing. Upstream-commit: d4f70397930b15c8860a71df1abb9bc5ac9ab4ff Component: engine --- components/engine/commands.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index 0fabaa385f..a7df369e5f 100644 --- a/components/engine/commands.go +++ b/components/engine/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 86b74909af3248f1cb0bcf103445df71baed118c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 10:17:57 -0700 Subject: [PATCH 62/67] Remove unused argument. Upstream-commit: 88b6ea993d76fb8891ee7a7fa8828b5c5753c7f5 Component: engine --- components/engine/commands.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index a7df369e5f..7288b58229 100644 --- a/components/engine/commands.go +++ b/components/engine/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 730b8810f90e162ce6969e977be10fcee945aae5 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 28 Jul 2013 12:57:09 -0700 Subject: [PATCH 63/67] Remove deprecated copy from README Upstream-commit: 97a2dc96f23b25bc7980d2a4514b7065ff4edb9e Component: engine --- components/engine/README.md | 45 ------------------------------------- 1 file changed, 45 deletions(-) diff --git a/components/engine/README.md b/components/engine/README.md index 5e03b2993c..5eee1a3db6 100644 --- a/components/engine/README.md +++ b/components/engine/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 4b37d9ec34f816bd7fb8a1ad22ea0a2703016091 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 29 Jul 2013 10:28:41 -0700 Subject: [PATCH 64/67] Remove unnecessary signal conditional. Upstream-commit: c8ec36d1b9bfbe1e22acd0124409ecb5a109d406 Component: engine --- components/engine/commands.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index 0fabaa385f..b3e7976995 100644 --- a/components/engine/commands.go +++ b/components/engine/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 4dd9531b175f79d5bfaa9cbb9582027850ec9b7e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 29 Jul 2013 11:13:59 -0700 Subject: [PATCH 65/67] Keep the loop to allow resizing more than once. Upstream-commit: 10e37198aa14cb3192fd0bf29572a5dce58c348f Component: engine --- components/engine/commands.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index b3e7976995..b4853e967d 100644 --- a/components/engine/commands.go +++ b/components/engine/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 7e99f4ac78242ffb92e6778514ff7d85a83832c3 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Mon, 29 Jul 2013 14:17:15 -0700 Subject: [PATCH 66/67] 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. Upstream-commit: 5dc86d7bca17c2996264a18cc26f06d30e532588 Component: engine --- components/engine/container.go | 2 +- components/engine/docs/sources/commandline/command/run.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index d0b6ca4ce2..d610c3c7d4 100644 --- a/components/engine/container.go +++ b/components/engine/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/components/engine/docs/sources/commandline/command/run.rst b/components/engine/docs/sources/commandline/command/run.rst index db67ef0705..db043c3b3f 100644 --- a/components/engine/docs/sources/commandline/command/run.rst +++ b/components/engine/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 86d430ea320a9d5c165d430544ba05f90b2305bb Mon Sep 17 00:00:00 2001 From: dsissitka Date: Tue, 30 Jul 2013 01:39:29 -0400 Subject: [PATCH 67/67] Fixed a couple of minor syntax errors. Upstream-commit: 9ba998312de5826f24f693c8518ecda700135b4b Component: engine --- .../engine/docs/sources/examples/couchdb_data_volumes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/examples/couchdb_data_volumes.rst b/components/engine/docs/sources/examples/couchdb_data_volumes.rst index d6babe557f..97af733a82 100644 --- a/components/engine/docs/sources/examples/couchdb_data_volumes.rst +++ b/components/engine/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.