diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 1599a1d0e1..eb90cd02d9 100644 --- a/components/engine/AUTHORS +++ b/components/engine/AUTHORS @@ -42,6 +42,7 @@ Ken Cochrane Kevin J. Lynagh Louis Opter Maxim Treskin +Michael Crosby Mikhail Sobolev Nate Jones Nelson Chen diff --git a/components/engine/FIXME b/components/engine/FIXME new file mode 100644 index 0000000000..e252fb2589 --- /dev/null +++ b/components/engine/FIXME @@ -0,0 +1,18 @@ + +## FIXME + +This file is a loose collection of things to improve in the codebase, for the internal +use of the maintainers. + +They are not big enough to be in the roadmap, not user-facing enough to be github issues, +and not important enough to be discussed in the mailing list. + +They are just like FIXME comments in the source code, except we're not sure where in the source +to put them - so we put them here :) + + +* Merge Runtime, Server and Builder into Runtime +* Run linter on codebase +* Unify build commands and regular commands +* Move source code into src/ subdir for clarity +* Clean up the Makefile, it's a mess diff --git a/components/engine/NOTICE b/components/engine/NOTICE index f55cc6950a..a11ff94049 100644 --- a/components/engine/NOTICE +++ b/components/engine/NOTICE @@ -3,4 +3,9 @@ Copyright 2012-2013 dotCloud, inc. This product includes software developed at dotCloud, inc. (http://www.dotcloud.com). -This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License. \ No newline at end of file +This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License. + +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. diff --git a/components/engine/README.md b/components/engine/README.md index 918fdaade2..1c909e5431 100644 --- a/components/engine/README.md +++ b/components/engine/README.md @@ -251,7 +251,7 @@ 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/master/docs/README.md +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. @@ -371,4 +371,10 @@ Standard Container Specification #### Security +### 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. +Department of Commerce. diff --git a/components/engine/api.go b/components/engine/api.go index 7666b79a5f..fe40fbe016 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -13,7 +13,7 @@ import ( "strings" ) -const API_VERSION = 1.1 +const APIVERSION = 1.1 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -45,6 +45,8 @@ func httpError(w http.ResponseWriter, err error) { http.Error(w, err.Error(), http.StatusNotFound) } else if strings.HasPrefix(err.Error(), "Bad parameter") { http.Error(w, err.Error(), http.StatusBadRequest) + } else if strings.HasPrefix(err.Error(), "Conflict") { + http.Error(w, err.Error(), http.StatusConflict) } else if strings.HasPrefix(err.Error(), "Impossible") { http.Error(w, err.Error(), http.StatusNotAcceptable) } else { @@ -52,7 +54,7 @@ func httpError(w http.ResponseWriter, err error) { } } -func writeJson(w http.ResponseWriter, b []byte) { +func writeJSON(w http.ResponseWriter, b []byte) { w.Header().Set("Content-Type", "application/json") w.Write(b) } @@ -82,7 +84,7 @@ func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reques if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -111,11 +113,11 @@ func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reque } if status != "" { - b, err := json.Marshal(&ApiAuth{Status: status}) + b, err := json.Marshal(&APIAuth{Status: status}) if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } w.WriteHeader(http.StatusNoContent) @@ -128,7 +130,7 @@ func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Req if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -157,7 +159,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r return nil } -func getImagesJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -176,7 +178,7 @@ func getImagesJson(srv *Server, version float64, w http.ResponseWriter, r *http. if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -193,7 +195,7 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -210,7 +212,7 @@ func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *ht if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -227,11 +229,11 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } -func getContainersJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -251,7 +253,7 @@ func getContainersJson(srv *Server, version float64, w http.ResponseWriter, r *h if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -294,12 +296,12 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req if err != nil { return err } - b, err := json.Marshal(&ApiId{id}) + b, err := json.Marshal(&APIID{id}) if err != nil { return err } w.WriteHeader(http.StatusCreated) - writeJson(w, b) + writeJSON(w, b) return nil } @@ -353,7 +355,7 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -372,18 +374,18 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht w.Header().Set("Content-Type", "application/json") } sf := utils.NewStreamFormatter(version > 1.0) - imgId, err := srv.ImageInsert(name, url, path, w, sf) + imgID, err := srv.ImageInsert(name, url, path, w, sf) if err != nil { if sf.Used() { w.Write(sf.FormatError(err)) return nil } } - b, err := json.Marshal(&ApiId{Id: imgId}) + b, err := json.Marshal(&APIID{ID: imgID}) if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -421,8 +423,8 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r return err } - out := &ApiRun{ - Id: id, + out := &APIRun{ + ID: id, } if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") @@ -437,7 +439,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r return err } w.WriteHeader(http.StatusCreated) - writeJson(w, b) + writeJSON(w, b) return nil } @@ -481,14 +483,30 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht } func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - if err := srv.ImageDelete(name); err != nil { + imgs, err := srv.ImageDelete(name, version > 1.0) + if err != nil { return err } - w.WriteHeader(http.StatusNoContent) + if imgs != nil { + if len(*imgs) != 0 { + b, err := json.Marshal(imgs) + if err != nil { + return err + } + writeJSON(w, b) + } else { + return fmt.Errorf("Conflict, %s wasn't deleted", name) + } + } else { + w.WriteHeader(http.StatusNoContent) + } return nil } @@ -534,11 +552,11 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * if err != nil { return err } - b, err := json.Marshal(&ApiWait{StatusCode: status}) + b, err := json.Marshal(&APIWait{StatusCode: status}) if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -625,7 +643,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -643,17 +661,17 @@ func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *htt if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - apiConfig := &ApiImageConfig{} + apiConfig := &APIImageConfig{} if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil { return err } - image, err := srv.ImageGetCached(apiConfig.Id, apiConfig.Config) + image, err := srv.ImageGetCached(apiConfig.ID, apiConfig.Config) if err != nil { return err } @@ -661,12 +679,12 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r * w.WriteHeader(http.StatusNotFound) return nil } - apiId := &ApiId{Id: image.Id} - b, err := json.Marshal(apiId) + apiID := &APIID{ID: image.ID} + b, err := json.Marshal(apiID) if err != nil { return err } - writeJson(w, b) + writeJSON(w, b) return nil } @@ -703,22 +721,31 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ return nil } -func ListenAndServe(addr string, srv *Server, logging bool) error { +func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + w.WriteHeader(http.StatusOK) + return nil +} +func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") + w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") +} + +func createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() - log.Printf("Listening for HTTP on %s\n", addr) 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, + "/images/json": getImagesJSON, "/images/viz": getImagesViz, "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, "/images/{name:.*}/json": getImagesByName, - "/containers/ps": getContainersJson, - "/containers/json": getContainersJson, + "/containers/ps": getContainersJSON, + "/containers/json": getContainersJSON, "/containers/{name:.*}/export": getContainersExport, "/containers/{name:.*}/changes": getContainersChanges, "/containers/{name:.*}/json": getContainersByName, @@ -745,6 +772,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { "/containers/{name:.*}": deleteContainers, "/images/{name:.*}": deleteImages, }, + "OPTIONS": { + "": optionsHandler, + }, } for method, routes := range m { @@ -767,9 +797,12 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { } version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64) if err != nil { - version = API_VERSION + version = APIVERSION } - if version == 0 || version > API_VERSION { + if srv.enableCors { + writeCorsHeaders(w, r) + } + if version == 0 || version > APIVERSION { w.WriteHeader(http.StatusNotFound) return } @@ -777,9 +810,24 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { httpError(w, err) } } - r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) - r.Path(localRoute).Methods(localMethod).HandlerFunc(f) + + if localRoute == "" { + r.Methods(localMethod).HandlerFunc(f) + } else { + r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f) + r.Path(localRoute).Methods(localMethod).HandlerFunc(f) + } } } + return r, nil +} + +func ListenAndServe(addr string, srv *Server, logging bool) error { + log.Printf("Listening for HTTP on %s\n", addr) + + r, err := createRouter(srv, logging) + if err != nil { + return err + } return http.ListenAndServe(addr, r) } diff --git a/components/engine/api_params.go b/components/engine/api_params.go index e18238b735..33b915cea5 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -1,19 +1,19 @@ package docker -type ApiHistory struct { - Id string +type APIHistory struct { + ID string `json:"Id"` Created int64 CreatedBy string `json:",omitempty"` } -type ApiImages struct { +type APIImages struct { Repository string `json:",omitempty"` Tag string `json:",omitempty"` - Id string + ID string `json:"Id"` Created int64 } -type ApiInfo struct { +type APIInfo struct { Debug bool Containers int Images int @@ -23,8 +23,13 @@ type ApiInfo struct { SwapLimit bool `json:",omitempty"` } -type ApiContainers struct { - Id string +type APIRmi struct { + Deleted string `json:",omitempty"` + Untagged string `json:",omitempty"` +} + +type APIContainers struct { + ID string `json:"Id"` Image string Command string Created int64 @@ -32,39 +37,39 @@ type ApiContainers struct { Ports string } -type ApiSearch struct { +type APISearch struct { Name string Description string } -type ApiId struct { - Id string +type APIID struct { + ID string `json:"Id"` } -type ApiRun struct { - Id string +type APIRun struct { + ID string `json:"Id"` Warnings []string `json:",omitempty"` } -type ApiPort struct { +type APIPort struct { Port string } -type ApiVersion struct { +type APIVersion struct { Version string GitCommit string `json:",omitempty"` GoVersion string `json:",omitempty"` } -type ApiWait struct { +type APIWait struct { StatusCode int } -type ApiAuth struct { +type APIAuth struct { Status string } -type ApiImageConfig struct { - Id string +type APIImageConfig struct { + ID string `json:"Id"` *Config } diff --git a/components/engine/api_test.go b/components/engine/api_test.go index 9121167e10..26fd229379 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -37,17 +37,17 @@ func TestGetAuth(t *testing.T) { Email: "utest@yopmail.com", } - authConfigJson, err := json.Marshal(authConfig) + authConfigJSON, err := json.Marshal(authConfig) if err != nil { t.Fatal(err) } - req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJson)) + req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJSON)) if err != nil { t.Fatal(err) } - if err := postAuth(srv, API_VERSION, r, req, nil); err != nil { + if err := postAuth(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } @@ -73,11 +73,11 @@ func TestGetVersion(t *testing.T) { r := httptest.NewRecorder() - if err := getVersion(srv, API_VERSION, r, nil, nil); err != nil { + if err := getVersion(srv, APIVERSION, r, nil, nil); err != nil { t.Fatal(err) } - v := &ApiVersion{} + v := &APIVersion{} if err = json.Unmarshal(r.Body.Bytes(), v); err != nil { t.Fatal(err) } @@ -97,11 +97,11 @@ func TestGetInfo(t *testing.T) { r := httptest.NewRecorder() - if err := getInfo(srv, API_VERSION, r, nil, nil); err != nil { + if err := getInfo(srv, APIVERSION, r, nil, nil); err != nil { t.Fatal(err) } - infos := &ApiInfo{} + infos := &APIInfo{} err = json.Unmarshal(r.Body.Bytes(), infos) if err != nil { t.Fatal(err) @@ -111,7 +111,7 @@ func TestGetInfo(t *testing.T) { } } -func TestGetImagesJson(t *testing.T) { +func TestGetImagesJSON(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -128,11 +128,11 @@ func TestGetImagesJson(t *testing.T) { r := httptest.NewRecorder() - if err := getImagesJson(srv, API_VERSION, r, req, nil); err != nil { + if err := getImagesJSON(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } - images := []ApiImages{} + images := []APIImages{} if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil { t.Fatal(err) } @@ -153,11 +153,11 @@ func TestGetImagesJson(t *testing.T) { t.Fatal(err) } - if err := getImagesJson(srv, API_VERSION, r2, req2, nil); err != nil { + if err := getImagesJSON(srv, APIVERSION, r2, req2, nil); err != nil { t.Fatal(err) } - images2 := []ApiImages{} + images2 := []APIImages{} if err := json.Unmarshal(r2.Body.Bytes(), &images2); err != nil { t.Fatal(err) } @@ -166,8 +166,8 @@ func TestGetImagesJson(t *testing.T) { t.Errorf("Excepted 1 image, %d found", len(images2)) } - if images2[0].Id != GetTestImage(runtime).Id { - t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).Id, images2[0].Id) + if images2[0].ID != GetTestImage(runtime).ID { + t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).ID, images2[0].ID) } r3 := httptest.NewRecorder() @@ -178,11 +178,11 @@ func TestGetImagesJson(t *testing.T) { t.Fatal(err) } - if err := getImagesJson(srv, API_VERSION, r3, req3, nil); err != nil { + if err := getImagesJSON(srv, APIVERSION, r3, req3, nil); err != nil { t.Fatal(err) } - images3 := []ApiImages{} + images3 := []APIImages{} if err := json.Unmarshal(r3.Body.Bytes(), &images3); err != nil { t.Fatal(err) } @@ -199,7 +199,7 @@ func TestGetImagesJson(t *testing.T) { t.Fatal(err) } - err = getImagesJson(srv, API_VERSION, r4, req4, nil) + err = getImagesJSON(srv, APIVERSION, r4, req4, nil) if err == nil { t.Fatalf("Error expected, received none") } @@ -220,7 +220,7 @@ func TestGetImagesViz(t *testing.T) { srv := &Server{runtime: runtime} r := httptest.NewRecorder() - if err := getImagesViz(srv, API_VERSION, r, nil, nil); err != nil { + if err := getImagesViz(srv, APIVERSION, r, nil, nil); err != nil { t.Fatal(err) } @@ -256,11 +256,11 @@ func TestGetImagesSearch(t *testing.T) { t.Fatal(err) } - if err := getImagesSearch(srv, API_VERSION, r, req, nil); err != nil { + if err := getImagesSearch(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } - results := []ApiSearch{} + results := []APISearch{} if err := json.Unmarshal(r.Body.Bytes(), &results); err != nil { t.Fatal(err) } @@ -280,11 +280,11 @@ func TestGetImagesHistory(t *testing.T) { r := httptest.NewRecorder() - if err := getImagesHistory(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil { + if err := getImagesHistory(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil { t.Fatal(err) } - history := []ApiHistory{} + history := []APIHistory{} if err := json.Unmarshal(r.Body.Bytes(), &history); err != nil { t.Fatal(err) } @@ -303,7 +303,7 @@ func TestGetImagesByName(t *testing.T) { srv := &Server{runtime: runtime} r := httptest.NewRecorder() - if err := getImagesByName(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil { + if err := getImagesByName(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil { t.Fatal(err) } @@ -311,12 +311,12 @@ func TestGetImagesByName(t *testing.T) { if err := json.Unmarshal(r.Body.Bytes(), img); err != nil { t.Fatal(err) } - if img.Id != GetTestImage(runtime).Id || img.Comment != "Imported from http://get.docker.io/images/busybox" { + if img.ID != GetTestImage(runtime).ID || img.Comment != "Imported from http://get.docker.io/images/busybox" { t.Errorf("Error inspecting image") } } -func TestGetContainersJson(t *testing.T) { +func TestGetContainersJSON(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -326,7 +326,7 @@ func TestGetContainersJson(t *testing.T) { srv := &Server{runtime: runtime} container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, }) if err != nil { @@ -340,18 +340,18 @@ func TestGetContainersJson(t *testing.T) { } r := httptest.NewRecorder() - if err := getContainersJson(srv, API_VERSION, r, req, nil); err != nil { + if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } - containers := []ApiContainers{} + containers := []APIContainers{} if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil { t.Fatal(err) } if len(containers) != 1 { t.Fatalf("Excepted %d container, %d found", 1, len(containers)) } - if containers[0].Id != container.Id { - t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.Id, containers[0].Id) + if containers[0].ID != container.ID { + t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID) } } @@ -369,7 +369,7 @@ func TestGetContainersExport(t *testing.T) { // Create a container and remove a file container, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }, ) @@ -383,7 +383,7 @@ func TestGetContainersExport(t *testing.T) { } r := httptest.NewRecorder() - if err = getContainersExport(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil { + if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } @@ -424,7 +424,7 @@ func TestGetContainersChanges(t *testing.T) { // Create a container and remove a file container, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/rm", "/etc/passwd"}, }, ) @@ -438,7 +438,7 @@ func TestGetContainersChanges(t *testing.T) { } r := httptest.NewRecorder() - if err := getContainersChanges(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil { + if err := getContainersChanges(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } changes := []Change{} @@ -472,7 +472,7 @@ func TestGetContainersByName(t *testing.T) { // Create a container and remove a file container, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, }, ) @@ -482,15 +482,15 @@ func TestGetContainersByName(t *testing.T) { defer runtime.Destroy(container) r := httptest.NewRecorder() - if err := getContainersByName(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil { + if err := getContainersByName(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } outContainer := &Container{} if err := json.Unmarshal(r.Body.Bytes(), outContainer); err != nil { t.Fatal(err) } - if outContainer.Id != container.Id { - t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.Id, outContainer.Id) + if outContainer.ID != container.ID { + t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.ID, outContainer.ID) } } @@ -514,7 +514,7 @@ func TestPostAuth(t *testing.T) { auth.SaveConfig(runtime.root, authStr, config.Email) r := httptest.NewRecorder() - if err := getAuth(srv, API_VERSION, r, nil, nil); err != nil { + if err := getAuth(srv, APIVERSION, r, nil, nil); err != nil { t.Fatal(err) } @@ -542,7 +542,7 @@ func TestPostCommit(t *testing.T) { // Create a container and remove a file container, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }, ) @@ -555,24 +555,24 @@ func TestPostCommit(t *testing.T) { t.Fatal(err) } - req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.Id, bytes.NewReader([]byte{})) + req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.ID, bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() - if err := postCommit(srv, API_VERSION, r, req, nil); err != nil { + if err := postCommit(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } if r.Code != http.StatusCreated { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } - apiId := &ApiId{} - if err := json.Unmarshal(r.Body.Bytes(), apiId); err != nil { + apiID := &APIID{} + if err := json.Unmarshal(r.Body.Bytes(), apiID); err != nil { t.Fatal(err) } - if _, err := runtime.graph.Get(apiId.Id); err != nil { + if _, err := runtime.graph.Get(apiID.ID); err != nil { t.Fatalf("The image has not been commited") } } @@ -715,7 +715,7 @@ func TestPostImagesInsert(t *testing.T) { // t.Fatalf("The test file has not been found") // } - // if err := srv.runtime.graph.Delete(img.Id); err != nil { + // if err := srv.runtime.graph.Delete(img.ID); err != nil { // t.Fatal(err) // } } @@ -824,8 +824,8 @@ func TestPostContainersCreate(t *testing.T) { srv := &Server{runtime: runtime} - configJson, err := json.Marshal(&Config{ - Image: GetTestImage(runtime).Id, + configJSON, err := json.Marshal(&Config{ + Image: GetTestImage(runtime).ID, Memory: 33554432, Cmd: []string{"touch", "/test"}, }) @@ -833,25 +833,25 @@ func TestPostContainersCreate(t *testing.T) { t.Fatal(err) } - req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJson)) + req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON)) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() - if err := postContainersCreate(srv, API_VERSION, r, req, nil); err != nil { + if err := postContainersCreate(srv, APIVERSION, r, req, nil); err != nil { t.Fatal(err) } if r.Code != http.StatusCreated { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } - apiRun := &ApiRun{} + apiRun := &APIRun{} if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil { t.Fatal(err) } - container := srv.runtime.Get(apiRun.Id) + container := srv.runtime.Get(apiRun.ID) if container == nil { t.Fatalf("Container not created") } @@ -880,7 +880,7 @@ func TestPostContainersKill(t *testing.T) { container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, @@ -902,7 +902,7 @@ func TestPostContainersKill(t *testing.T) { } r := httptest.NewRecorder() - if err := postContainersKill(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil { + if err := postContainersKill(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { @@ -924,7 +924,7 @@ func TestPostContainersRestart(t *testing.T) { container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, @@ -945,12 +945,12 @@ func TestPostContainersRestart(t *testing.T) { t.Errorf("Container should be running") } - req, err := http.NewRequest("POST", "/containers/"+container.Id+"/restart?t=1", bytes.NewReader([]byte{})) + req, err := http.NewRequest("POST", "/containers/"+container.ID+"/restart?t=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() - if err := postContainersRestart(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil { + if err := postContainersRestart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { @@ -980,7 +980,7 @@ func TestPostContainersStart(t *testing.T) { container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, @@ -991,7 +991,7 @@ func TestPostContainersStart(t *testing.T) { defer runtime.Destroy(container) r := httptest.NewRecorder() - if err := postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil { + if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { @@ -1006,7 +1006,7 @@ func TestPostContainersStart(t *testing.T) { } r = httptest.NewRecorder() - if err = postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err == nil { + if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil { t.Fatalf("A running containter should be able to be started") } @@ -1026,7 +1026,7 @@ func TestPostContainersStop(t *testing.T) { container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, @@ -1048,12 +1048,12 @@ func TestPostContainersStop(t *testing.T) { } // Note: as it is a POST request, it requires a body. - req, err := http.NewRequest("POST", "/containers/"+container.Id+"/stop?t=1", bytes.NewReader([]byte{})) + req, err := http.NewRequest("POST", "/containers/"+container.ID+"/stop?t=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() - if err := postContainersStop(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil { + if err := postContainersStop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { @@ -1075,7 +1075,7 @@ func TestPostContainersWait(t *testing.T) { container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sleep", "1"}, OpenStdin: true, }, @@ -1091,10 +1091,10 @@ func TestPostContainersWait(t *testing.T) { setTimeout(t, "Wait timed out", 3*time.Second, func() { r := httptest.NewRecorder() - if err := postContainersWait(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil { + if err := postContainersWait(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } - apiWait := &ApiWait{} + apiWait := &APIWait{} if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil { t.Fatal(err) } @@ -1119,7 +1119,7 @@ func TestPostContainersAttach(t *testing.T) { container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, OpenStdin: true, }, @@ -1148,12 +1148,12 @@ func TestPostContainersAttach(t *testing.T) { out: stdoutPipe, } - req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) + req, err := http.NewRequest("POST", "/containers/"+container.ID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } - if err := postContainersAttach(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil { + if err := postContainersAttach(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } }() @@ -1206,7 +1206,7 @@ func TestDeleteContainers(t *testing.T) { srv := &Server{runtime: runtime} container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }) if err != nil { @@ -1218,19 +1218,19 @@ func TestDeleteContainers(t *testing.T) { t.Fatal(err) } - req, err := http.NewRequest("DELETE", "/containers/"+container.Id, nil) + req, err := http.NewRequest("DELETE", "/containers/"+container.ID, nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() - if err := deleteContainers(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil { + if err := deleteContainers(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } - if c := runtime.Get(container.Id); c != nil { + if c := runtime.Get(container.ID); c != nil { t.Fatalf("The container as not been deleted") } @@ -1239,9 +1239,131 @@ func TestDeleteContainers(t *testing.T) { } } +func TestOptionsRoute(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime, enableCors: true} + + r := httptest.NewRecorder() + router, err := createRouter(srv, false) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("OPTIONS", "/", nil) + if err != nil { + t.Fatal(err) + } + + router.ServeHTTP(r, req) + if r.Code != http.StatusOK { + t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) + } +} + +func TestGetEnabledCors(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime, enableCors: true} + + r := httptest.NewRecorder() + + router, err := createRouter(srv, false) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("GET", "/version", nil) + if err != nil { + t.Fatal(err) + } + + router.ServeHTTP(r, req) + if r.Code != http.StatusOK { + t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) + } + + allowOrigin := r.Header().Get("Access-Control-Allow-Origin") + allowHeaders := r.Header().Get("Access-Control-Allow-Headers") + allowMethods := r.Header().Get("Access-Control-Allow-Methods") + + if allowOrigin != "*" { + t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin) + } + if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept" { + t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept\", %s found.", allowHeaders) + } + if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" { + t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) + } +} + func TestDeleteImages(t *testing.T) { - //FIXME: Implement this test - t.Log("Test not implemented") + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + if err := srv.runtime.repositories.Set("test", "test", unitTestImageName, true); err != nil { + t.Fatal(err) + } + + images, err := srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images) != 2 { + t.Errorf("Excepted 2 images, %d found", len(images)) + } + + req, err := http.NewRequest("DELETE", "/images/test:test", nil) + if err != nil { + t.Fatal(err) + } + + r := httptest.NewRecorder() + if err := deleteImages(srv, APIVERSION, r, req, map[string]string{"name": "test:test"}); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) + } + + var outs []APIRmi + if err := json.Unmarshal(r.Body.Bytes(), &outs); err != nil { + t.Fatal(err) + } + if len(outs) != 1 { + t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs)) + } + images, err = srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Errorf("Excepted 1 image, %d found", len(images)) + } + + /* if c := runtime.Get(container.Id); c != nil { + t.Fatalf("The container as not been deleted") + } + + if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { + t.Fatalf("The test file has not been deleted") + } */ } // Mocked types for tests diff --git a/components/engine/auth/auth.go b/components/engine/auth/auth.go index 9c34604419..72cf36efa5 100644 --- a/components/engine/auth/auth.go +++ b/components/engine/auth/auth.go @@ -16,12 +16,12 @@ import ( const CONFIGFILE = ".dockercfg" // the registry server we want to login against -const INDEX_SERVER = "https://index.docker.io/v1" +const INDEXSERVER = "https://index.docker.io/v1" -//const INDEX_SERVER = "http://indexstaging-docker.dotcloud.com/" +//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/" var ( - ErrConfigFileMissing error = errors.New("The Auth config file is missing") + ErrConfigFileMissing = errors.New("The Auth config file is missing") ) type AuthConfig struct { @@ -44,7 +44,7 @@ func IndexServerAddress() string { if os.Getenv("DOCKER_INDEX_URL") != "" { return os.Getenv("DOCKER_INDEX_URL") + "/v1" } - return INDEX_SERVER + return INDEXSERVER } // create a base64 encoded auth string to store in config diff --git a/components/engine/builder.go b/components/engine/builder.go index 5f56f65d05..808b7efcab 100644 --- a/components/engine/builder.go +++ b/components/engine/builder.go @@ -40,7 +40,7 @@ func (builder *Builder) Create(config *Config) (*Container, error) { } // Generate id - id := GenerateId() + id := GenerateID() // Generate default hostname // FIXME: the lxc template no longer needs to set a default hostname if config.Hostname == "" { @@ -49,17 +49,17 @@ func (builder *Builder) Create(config *Config) (*Container, error) { container := &Container{ // FIXME: we should generate the ID here instead of receiving it as an argument - Id: id, + ID: id, Created: time.Now(), Path: config.Cmd[0], Args: config.Cmd[1:], //FIXME: de-duplicate from config Config: config, - Image: img.Id, // Always use the resolved image id + Image: img.ID, // Always use the resolved image id NetworkSettings: &NetworkSettings{}, // FIXME: do we need to store this in the container? SysInitPath: sysInitPath, } - container.root = builder.runtime.containerRoot(container.Id) + container.root = builder.runtime.containerRoot(container.ID) // Step 1: create the container directory. // This doubles as a barrier to avoid race conditions. if err := os.Mkdir(container.root, 0700); err != nil { @@ -110,7 +110,7 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a } // Register the image if needed if repository != "" { - if err := builder.repositories.Set(repository, tag, img.Id, true); err != nil { + if err := builder.repositories.Set(repository, tag, img.ID, true); err != nil { return img, err } } diff --git a/components/engine/builder_client.go b/components/engine/builder_client.go index 144e11b415..dc9528ff41 100644 --- a/components/engine/builder_client.go +++ b/components/engine/builder_client.go @@ -63,11 +63,11 @@ func (b *builderClient) CmdFrom(name string) error { return err } - img := &ApiId{} + img := &APIID{} if err := json.Unmarshal(obj, img); err != nil { return err } - b.image = img.Id + b.image = img.ID utils.Debugf("Using image %s", b.image) return nil } @@ -91,19 +91,19 @@ func (b *builderClient) CmdRun(args string) error { b.config.Cmd = nil MergeConfig(b.config, config) - body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config}) + body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config}) if err != nil { if statusCode != 404 { return err } } if statusCode != 404 { - apiId := &ApiId{} - if err := json.Unmarshal(body, apiId); err != nil { + apiID := &APIID{} + if err := json.Unmarshal(body, apiID); err != nil { return err } utils.Debugf("Use cached version") - b.image = apiId.Id + b.image = apiID.ID return nil } cid, err := b.run() @@ -163,7 +163,7 @@ func (b *builderClient) CmdInsert(args string) error { // return err // } - // apiId := &ApiId{} + // apiId := &APIId{} // if err := json.Unmarshal(body, apiId); err != nil { // return err // } @@ -182,7 +182,7 @@ func (b *builderClient) run() (string, error) { return "", err } - apiRun := &ApiRun{} + apiRun := &APIRun{} if err := json.Unmarshal(body, apiRun); err != nil { return "", err } @@ -191,18 +191,18 @@ func (b *builderClient) run() (string, error) { } //start the container - _, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/start", nil) + _, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil) if err != nil { return "", err } - b.tmpContainers[apiRun.Id] = struct{}{} + b.tmpContainers[apiRun.ID] = struct{}{} // Wait for it to finish - body, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/wait", nil) + body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil) if err != nil { return "", err } - apiWait := &ApiWait{} + apiWait := &APIWait{} if err := json.Unmarshal(body, apiWait); err != nil { return "", err } @@ -210,7 +210,7 @@ func (b *builderClient) run() (string, error) { return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode) } - return apiRun.Id, nil + return apiRun.ID, nil } func (b *builderClient) commit(id string) error { @@ -222,11 +222,11 @@ func (b *builderClient) commit(id string) error { if id == "" { cmd := b.config.Cmd b.config.Cmd = []string{"true"} - if cid, err := b.run(); err != nil { + cid, err := b.run() + if err != nil { return err - } else { - id = cid } + id = cid b.config.Cmd = cmd } @@ -239,12 +239,12 @@ func (b *builderClient) commit(id string) error { if err != nil { return err } - apiId := &ApiId{} - if err := json.Unmarshal(body, apiId); err != nil { + apiID := &APIID{} + if err := json.Unmarshal(body, apiID); err != nil { return err } - b.tmpImages[apiId.Id] = struct{}{} - b.image = apiId.Id + b.tmpImages[apiID.ID] = struct{}{} + b.image = apiID.ID b.needCommit = false return nil } diff --git a/components/engine/buildfile.go b/components/engine/buildfile.go index a015830e0d..e0c5f794a8 100644 --- a/components/engine/buildfile.go +++ b/components/engine/buildfile.go @@ -73,7 +73,7 @@ func (b *buildFile) CmdFrom(name string) error { return err } } - b.image = image.Id + b.image = image.ID b.config = &Config{} return nil } @@ -102,7 +102,7 @@ func (b *buildFile) CmdRun(args string) error { return err } else if cache != nil { utils.Debugf("[BUILDER] Use cached version") - b.image = cache.Id + b.image = cache.ID return nil } else { utils.Debugf("[BUILDER] Cache miss") @@ -238,7 +238,7 @@ func (b *buildFile) run() (string, error) { if err != nil { return "", err } - b.tmpContainers[c.Id] = struct{}{} + b.tmpContainers[c.ID] = struct{}{} //start the container if err := c.Start(); err != nil { @@ -250,7 +250,7 @@ func (b *buildFile) run() (string, error) { return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret) } - return c.Id, nil + return c.ID, nil } // Commit the container with the autorun command @@ -266,17 +266,17 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { return err } else if cache != nil { utils.Debugf("[BUILDER] Use cached version") - b.image = cache.Id + b.image = cache.ID return nil } else { utils.Debugf("[BUILDER] Cache miss") } - if cid, err := b.run(); err != nil { + cid, err := b.run() + if err != nil { return err - } else { - id = cid } + id = cid } container := b.runtime.Get(id) @@ -292,8 +292,8 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { if err != nil { return err } - b.tmpImages[image.Id] = struct{}{} - b.image = image.Id + b.tmpImages[image.ID] = struct{}{} + b.image = image.ID return nil } @@ -313,10 +313,11 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { for { line, err := file.ReadString('\n') if err != nil { - if err == io.EOF { + if err == io.EOF && line == "" { break + } else if err != io.EOF { + return "", err } - return "", err } line = strings.Replace(strings.TrimSpace(line), " ", " ", 1) // Skip comments and empty line diff --git a/components/engine/buildfile_test.go b/components/engine/buildfile_test.go index b6f4e62ae9..d9c60a70d5 100644 --- a/components/engine/buildfile_test.go +++ b/components/engine/buildfile_test.go @@ -15,58 +15,69 @@ run sh -c 'echo root:testpass > /tmp/passwd' run mkdir -p /var/run/sshd ` +const DockerfileNoNewLine = ` +# VERSION 0.1 +# DOCKER-VERSION 0.2 + +from ` + unitTestImageName + ` +run sh -c 'echo root:testpass > /tmp/passwd' +run mkdir -p /var/run/sshd` + func TestBuild(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } - defer nuke(runtime) + dockerfiles := []string{Dockerfile, DockerfileNoNewLine} + for _, Dockerfile := range dockerfiles { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) - srv := &Server{runtime: runtime} + srv := &Server{runtime: runtime} - buildfile := NewBuildFile(srv, &utils.NopWriter{}) + buildfile := NewBuildFile(srv, &utils.NopWriter{}) - imgId, err := buildfile.Build(strings.NewReader(Dockerfile), nil) - if err != nil { - t.Fatal(err) - } + imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil) + if err != nil { + t.Fatal(err) + } - builder := NewBuilder(runtime) - container, err := builder.Create( - &Config{ - Image: imgId, - Cmd: []string{"cat", "/tmp/passwd"}, - }, - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) + builder := NewBuilder(runtime) + container, err := builder.Create( + &Config{ + Image: imgID, + Cmd: []string{"cat", "/tmp/passwd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) - output, err := container.Output() - if err != nil { - t.Fatal(err) - } - if string(output) != "root:testpass\n" { - t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n") - } + output, err := container.Output() + if err != nil { + t.Fatal(err) + } + if string(output) != "root:testpass\n" { + t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n") + } - container2, err := builder.Create( - &Config{ - Image: imgId, - Cmd: []string{"ls", "-d", "/var/run/sshd"}, - }, - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container2) + container2, err := builder.Create( + &Config{ + Image: imgID, + Cmd: []string{"ls", "-d", "/var/run/sshd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container2) - output, err = container2.Output() - if err != nil { - t.Fatal(err) - } - if string(output) != "/var/run/sshd\n" { - t.Fatal("/var/run/sshd has not been created") + output, err = container2.Output() + if err != nil { + t.Fatal(err) + } + if string(output) != "/var/run/sshd\n" { + t.Fatal("/var/run/sshd has not been created") + } } } diff --git a/components/engine/commands.go b/components/engine/commands.go index c38c18a04c..eacbd388f0 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -31,7 +31,7 @@ import ( const VERSION = "0.4.0" var ( - GIT_COMMIT string + GITCOMMIT string ) func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { @@ -159,11 +159,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { file = os.Stdin } else { // Send Dockerfile from arg/Dockerfile (deprecate later) - if f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")); err != nil { + f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")) + if err != nil { return err - } else { - file = f } + file = f // Send context from arg // Create a FormFile multipart for the context if needed // FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage? @@ -176,21 +176,21 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if err != nil { return err } - if wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension()); err != nil { + wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension()) + if err != nil { return err - } else { - // FIXME: Find a way to have a progressbar for the upload too - sf := utils.NewStreamFormatter(false) - io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf)) } + // FIXME: Find a way to have a progressbar for the upload too + sf := utils.NewStreamFormatter(false) + io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf)) multipartBody = io.MultiReader(multipartBody, boundary) } // Create a FormFile multipart for the Dockerfile - if wField, err := w.CreateFormFile("Dockerfile", "Dockerfile"); err != nil { + wField, err := w.CreateFormFile("Dockerfile", "Dockerfile") + if err != nil { return err - } else { - io.Copy(wField, file) } + io.Copy(wField, file) multipartBody = io.MultiReader(multipartBody, boundary) v := &url.Values{} @@ -218,7 +218,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if err != nil { return err } - return fmt.Errorf("error: %s", body) + if len(body) == 0 { + return fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode)) + } + return fmt.Errorf("Error: %s", body) } // Output the result @@ -276,9 +279,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error { oldState, err := term.SetRawTerminal() if err != nil { return err - } else { - defer term.RestoreTerminal(oldState) } + defer term.RestoreTerminal(oldState) cmd := Subcmd("login", "", "Register or Login to the docker registry server") if err := cmd.Parse(args); err != nil { @@ -331,7 +333,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { return err } - var out2 ApiAuth + var out2 APIAuth err = json.Unmarshal(body, &out2) if err != nil { return err @@ -358,7 +360,7 @@ func (cli *DockerCli) CmdWait(args ...string) error { if err != nil { fmt.Printf("%s", err) } else { - var out ApiWait + var out APIWait err = json.Unmarshal(body, &out) if err != nil { return err @@ -386,7 +388,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { return err } - var out ApiVersion + var out APIVersion err = json.Unmarshal(body, &out) if err != nil { utils.Debugf("Error unmarshal: body: %s, err: %s\n", body, err) @@ -419,7 +421,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { return err } - var out ApiInfo + var out APIInfo if err := json.Unmarshal(body, &out); err != nil { return err } @@ -458,7 +460,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { for _, name := range cmd.Args() { _, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) if err != nil { - fmt.Printf("%s", err) + fmt.Fprintf(os.Stderr, "%s", err) } else { fmt.Println(name) } @@ -483,7 +485,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error { for _, name := range cmd.Args() { _, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) if err != nil { - fmt.Printf("%s", err) + fmt.Fprintf(os.Stderr, "%s", err) } else { fmt.Println(name) } @@ -504,7 +506,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { for _, name := range args { _, _, err := cli.call("POST", "/containers/"+name+"/start", nil) if err != nil { - fmt.Printf("%s", err) + fmt.Fprintf(os.Stderr, "%s", err) } else { fmt.Println(name) } @@ -513,29 +515,38 @@ func (cli *DockerCli) CmdStart(args ...string) error { } func (cli *DockerCli) CmdInspect(args ...string) error { - cmd := Subcmd("inspect", "CONTAINER|IMAGE", "Return low-level information on a container/image") + cmd := Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() != 1 { + if cmd.NArg() < 1 { cmd.Usage() return nil } - obj, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) - if err != nil { - obj, _, err = cli.call("GET", "/images/"+cmd.Arg(0)+"/json", nil) + fmt.Printf("[") + for i, name := range args { + if i > 0 { + fmt.Printf(",") + } + obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil) if err != nil { - return err + obj, _, err = cli.call("GET", "/images/"+name+"/json", nil) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + continue + } + } + + indented := new(bytes.Buffer) + if err = json.Indent(indented, obj, "", " "); err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + continue + } + if _, err := io.Copy(os.Stdout, indented); err != nil { + fmt.Fprintf(os.Stderr, "%s", err) } } - - indented := new(bytes.Buffer) - if err = json.Indent(indented, obj, "", " "); err != nil { - return err - } - if _, err := io.Copy(os.Stdout, indented); err != nil { - return err - } + fmt.Printf("]") return nil } @@ -562,7 +573,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { if frontend, exists := out.NetworkSettings.PortMapping[cmd.Arg(1)]; exists { fmt.Println(frontend) } else { - return fmt.Errorf("error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0)) + return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0)) } return nil } @@ -579,11 +590,22 @@ func (cli *DockerCli) CmdRmi(args ...string) error { } for _, name := range cmd.Args() { - _, _, err := cli.call("DELETE", "/images/"+name, nil) + body, _, err := cli.call("DELETE", "/images/"+name, nil) if err != nil { - fmt.Printf("%s", err) + fmt.Fprintf(os.Stderr, "%s", err) } else { - fmt.Println(name) + var outs []APIRmi + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + for _, out := range outs { + if out.Deleted != "" { + fmt.Println("Deleted:", out.Deleted) + } else { + fmt.Println("Untagged:", out.Untagged) + } + } } } return nil @@ -604,7 +626,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { return err } - var outs []ApiHistory + var outs []APIHistory err = json.Unmarshal(body, &outs) if err != nil { return err @@ -613,7 +635,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") for _, out := range outs { - fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.Id, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy) + fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy) } w.Flush() return nil @@ -786,7 +808,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { return err } - var outs []ApiImages + var outs []APIImages err = json.Unmarshal(body, &outs) if err != nil { return err @@ -808,16 +830,16 @@ func (cli *DockerCli) CmdImages(args ...string) error { if !*quiet { fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag) if *noTrunc { - fmt.Fprintf(w, "%s\t", out.Id) + fmt.Fprintf(w, "%s\t", out.ID) } else { - fmt.Fprintf(w, "%s\t", utils.TruncateId(out.Id)) + fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID)) } fmt.Fprintf(w, "%s ago\n", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) } else { if *noTrunc { - fmt.Fprintln(w, out.Id) + fmt.Fprintln(w, out.ID) } else { - fmt.Fprintln(w, utils.TruncateId(out.Id)) + fmt.Fprintln(w, utils.TruncateID(out.ID)) } } } @@ -864,7 +886,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { return err } - var outs []ApiContainers + var outs []APIContainers err = json.Unmarshal(body, &outs) if err != nil { return err @@ -877,15 +899,15 @@ func (cli *DockerCli) CmdPs(args ...string) error { for _, out := range outs { if !*quiet { if *noTrunc { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.Id, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) } } else { if *noTrunc { - fmt.Fprintln(w, out.Id) + fmt.Fprintln(w, out.ID) } else { - fmt.Fprintln(w, utils.TruncateId(out.Id)) + fmt.Fprintln(w, utils.TruncateID(out.ID)) } } } @@ -928,13 +950,13 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return err } - apiId := &ApiId{} - err = json.Unmarshal(body, apiId) + apiID := &APIID{} + err = json.Unmarshal(body, apiID) if err != nil { return err } - fmt.Println(apiId.Id) + fmt.Println(apiID.ID) return nil } @@ -1071,7 +1093,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { return err } - outs := []ApiSearch{} + outs := []APISearch{} err = json.Unmarshal(body, &outs) if err != nil { return err @@ -1080,7 +1102,12 @@ func (cli *DockerCli) CmdSearch(args ...string) error { w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\n") for _, out := range outs { - fmt.Fprintf(w, "%s\t%s\n", out.Name, out.Description) + desc := strings.Replace(out.Description, "\n", " ", -1) + desc = strings.Replace(desc, "\r", " ", -1) + if len(desc) > 45 { + desc = utils.Trunc(desc, 42) + "..." + } + fmt.Fprintf(w, "%s\t%s\n", out.Name, desc) } w.Flush() return nil @@ -1203,7 +1230,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { return err } - out := &ApiRun{} + out := &APIRun{} err = json.Unmarshal(body, out) if err != nil { return err @@ -1224,18 +1251,21 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //start the container - _, _, err = cli.call("POST", "/containers/"+out.Id+"/start", nil) + _, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil) if err != nil { return err } + if !config.AttachStdout && !config.AttachStderr { + fmt.Println(out.ID) + } if connections > 0 { chErrors := make(chan error, connections) - cli.monitorTtySize(out.Id) + cli.monitorTtySize(out.ID) if splitStderr && config.AttachStderr { go func() { - chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr) + chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr) }() } @@ -1253,7 +1283,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { v.Set("stderr", "1") } go func() { - chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout) + chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout) }() for connections > 0 { err := <-chErrors @@ -1263,9 +1293,6 @@ func (cli *DockerCli) CmdRun(args ...string) error { connections -= 1 } } - if !config.AttachStdout && !config.AttachStderr { - fmt.Println(out.Id) - } return nil } @@ -1313,7 +1340,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, params = bytes.NewBuffer(buf) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params) if err != nil { return nil, -1, err } @@ -1336,7 +1363,10 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, return nil, -1, err } if resp.StatusCode < 200 || resp.StatusCode >= 400 { - return nil, resp.StatusCode, fmt.Errorf("error: %s", body) + if len(body) == 0 { + return nil, resp.StatusCode, fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode)) + } + return nil, resp.StatusCode, fmt.Errorf("Error: %s", body) } return body, resp.StatusCode, nil } @@ -1345,7 +1375,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader([]byte{}) } - req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), in) + req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in) if err != nil { return err } @@ -1366,20 +1396,23 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e if err != nil { return err } - return fmt.Errorf("error: %s", body) + if len(body) == 0 { + return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode)) + } + return fmt.Errorf("Error: %s", body) } if resp.Header.Get("Content-Type") == "application/json" { dec := json.NewDecoder(resp.Body) for { - var m utils.JsonMessage + var m utils.JSONMessage if err := dec.Decode(&m); err == io.EOF { break } else if err != nil { return err } if m.Progress != "" { - fmt.Fprintf(out, "Downloading %s\r", m.Progress) + fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress) } else if m.Error != "" { return fmt.Errorf(m.Error) } else { @@ -1395,7 +1428,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e } func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error { - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) if err != nil { return err } @@ -1417,11 +1450,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi }) if in != nil && setRawTerminal && term.IsTerminal(in.Fd()) && os.Getenv("NORAW") == "" { - if oldState, err := term.SetRawTerminal(); err != nil { + oldState, err := term.SetRawTerminal() + if err != nil { return err - } else { - defer term.RestoreTerminal(oldState) } + defer term.RestoreTerminal(oldState) } sendStdin := utils.Go(func() error { _, err := io.Copy(rwc, in) @@ -1435,7 +1468,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi return err } - if !term.IsTerminal(os.Stdin.Fd()) { + if !term.IsTerminal(in.Fd()) { if err := <-sendStdin; err != nil { return err } diff --git a/components/engine/container.go b/components/engine/container.go index c6b7c8a51c..4d2032af2e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -23,7 +23,7 @@ import ( type Container struct { root string - Id string + ID string Created time.Time @@ -167,8 +167,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet } type NetworkSettings struct { - IpAddress string - IpPrefixLen int + IPAddress string + IPPrefixLen int Gateway string Bridge string PortMapping map[string]string @@ -355,6 +355,18 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s errors <- err }() } + } else { + go func() { + if stdinCloser != nil { + defer stdinCloser.Close() + } + + if cStdout, err := container.StdoutPipe(); err != nil { + utils.Debugf("Error stdout pipe") + } else { + io.Copy(&utils.NopWriter{}, cStdout) + } + }() } if stderr != nil { nJobs += 1 @@ -381,7 +393,20 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s errors <- err }() } + } else { + go func() { + if stdinCloser != nil { + defer stdinCloser.Close() + } + + if cStderr, err := container.StderrPipe(); err != nil { + utils.Debugf("Error stdout pipe") + } else { + io.Copy(&utils.NopWriter{}, cStderr) + } + }() } + return utils.Go(func() error { if cStdout != nil { defer cStdout.Close() @@ -409,7 +434,7 @@ func (container *Container) Start() error { defer container.State.unlock() if container.State.Running { - return fmt.Errorf("The container %s is already running.", container.Id) + return fmt.Errorf("The container %s is already running.", container.ID) } if err := container.EnsureMounted(); err != nil { return err @@ -431,24 +456,24 @@ func (container *Container) Start() error { // Create the requested volumes volumes for volPath := range container.Config.Volumes { - if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil { + c, err := container.runtime.volumes.Create(nil, container, "", "", nil) + if err != nil { return err - } else { - if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { - return nil - } - container.Volumes[volPath] = c.Id } + if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { + return nil + } + container.Volumes[volPath] = c.ID } if container.Config.VolumesFrom != "" { c := container.runtime.Get(container.Config.VolumesFrom) if c == nil { - return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id) + return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID) } for volPath, id := range c.Volumes { if _, exists := container.Volumes[volPath]; exists { - return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id) + return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.ID) } if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { return nil @@ -462,7 +487,7 @@ func (container *Container) Start() error { } params := []string{ - "-n", container.Id, + "-n", container.ID, "-f", container.lxcConfigPath(), "--", "/sbin/init", @@ -573,17 +598,17 @@ func (container *Container) allocateNetwork() error { } container.NetworkSettings.PortMapping = make(map[string]string) for _, spec := range container.Config.PortSpecs { - if nat, err := iface.AllocatePort(spec); err != nil { + nat, err := iface.AllocatePort(spec) + if err != nil { iface.Release() return err - } else { - container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend) } + container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend) } container.network = iface container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface - container.NetworkSettings.IpAddress = iface.IPNet.IP.String() - container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size() + container.NetworkSettings.IPAddress = iface.IPNet.IP.String() + container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size() container.NetworkSettings.Gateway = iface.Gateway.String() return nil } @@ -597,16 +622,16 @@ func (container *Container) releaseNetwork() { // FIXME: replace this with a control socket within docker-init func (container *Container) waitLxc() error { for { - if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil { + output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput() + if err != nil { return err - } else { - if !strings.Contains(string(output), "RUNNING") { - return nil - } + } + if !strings.Contains(string(output), "RUNNING") { + return nil } time.Sleep(500 * time.Millisecond) } - return nil + panic("Unreachable") } func (container *Container) monitor() { @@ -616,17 +641,17 @@ func (container *Container) monitor() { // If the command does not exists, try to wait via lxc if container.cmd == nil { if err := container.waitLxc(); err != nil { - utils.Debugf("%s: Process: %s", container.Id, err) + utils.Debugf("%s: Process: %s", container.ID, err) } } else { if err := container.cmd.Wait(); err != nil { // Discard the error as any signals or non 0 returns will generate an error - utils.Debugf("%s: Process: %s", container.Id, err) + utils.Debugf("%s: Process: %s", container.ID, err) } } utils.Debugf("Process finished") - var exitCode int = -1 + exitCode := -1 if container.cmd != nil { exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() } @@ -635,24 +660,24 @@ func (container *Container) monitor() { container.releaseNetwork() if container.Config.OpenStdin { if err := container.stdin.Close(); err != nil { - utils.Debugf("%s: Error close stdin: %s", container.Id, err) + utils.Debugf("%s: Error close stdin: %s", container.ID, err) } } if err := container.stdout.CloseWriters(); err != nil { - utils.Debugf("%s: Error close stdout: %s", container.Id, err) + utils.Debugf("%s: Error close stdout: %s", container.ID, err) } if err := container.stderr.CloseWriters(); err != nil { - utils.Debugf("%s: Error close stderr: %s", container.Id, err) + utils.Debugf("%s: Error close stderr: %s", container.ID, err) } if container.ptyMaster != nil { if err := container.ptyMaster.Close(); err != nil { - utils.Debugf("%s: Error closing Pty master: %s", container.Id, err) + utils.Debugf("%s: Error closing Pty master: %s", container.ID, err) } } if err := container.Unmount(); err != nil { - log.Printf("%v: Failed to umount filesystem: %v", container.Id, err) + log.Printf("%v: Failed to umount filesystem: %v", container.ID, err) } // Re-create a brand new stdin pipe once the container exited @@ -673,7 +698,7 @@ func (container *Container) monitor() { // This is because State.setStopped() has already been called, and has caused Wait() // to return. // FIXME: why are we serializing running state to disk in the first place? - //log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err) + //log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err) } } @@ -683,17 +708,17 @@ func (container *Container) kill() error { } // Sending SIGKILL to the process via lxc - output, err := exec.Command("lxc-kill", "-n", container.Id, "9").CombinedOutput() + output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput() if err != nil { - log.Printf("error killing container %s (%s, %s)", container.Id, output, err) + log.Printf("error killing container %s (%s, %s)", container.ID, output, err) } // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { if container.cmd == nil { - return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.Id) + return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID) } - log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.Id) + log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID) if err := container.cmd.Process.Kill(); err != nil { return err } @@ -721,7 +746,7 @@ func (container *Container) Stop(seconds int) error { } // 1. Send a SIGTERM - if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { + if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil { log.Print(string(output)) log.Print("Failed to send SIGTERM to the process, force killing") if err := container.kill(); err != nil { @@ -731,7 +756,7 @@ func (container *Container) Stop(seconds int) error { // 2. Wait for the process to exit on its own if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil { - log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.Id, seconds) + log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds) if err := container.kill(); err != nil { return err } @@ -795,7 +820,8 @@ func (container *Container) WaitTimeout(timeout time.Duration) error { case <-done: return nil } - panic("unreachable") + + panic("Unreachable") } func (container *Container) EnsureMounted() error { @@ -838,16 +864,16 @@ func (container *Container) Unmount() error { return Unmount(container.RootfsPath()) } -// ShortId returns a shorthand version of the container's id for convenience. +// ShortID returns a shorthand version of the container's id for convenience. // A collision with other container shorthands is very unlikely, but possible. // In case of a collision a lookup with Runtime.Get() will fail, and the caller // will need to use a langer prefix, or the full-length container Id. -func (container *Container) ShortId() string { - return utils.TruncateId(container.Id) +func (container *Container) ShortID() string { + return utils.TruncateID(container.ID) } func (container *Container) logPath(name string) string { - return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name)) + return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name)) } func (container *Container) ReadLog(name string) (io.Reader, error) { @@ -887,7 +913,7 @@ func (container *Container) rwPath() string { return path.Join(container.root, "rw") } -func validateId(id string) error { +func validateID(id string) error { if id == "" { return fmt.Errorf("Invalid empty id") } diff --git a/components/engine/container_test.go b/components/engine/container_test.go index 117f0f3eb3..8ec1fa40ee 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -14,7 +14,7 @@ import ( "time" ) -func TestIdFormat(t *testing.T) { +func TestIDFormat(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -22,19 +22,19 @@ func TestIdFormat(t *testing.T) { defer nuke(runtime) container1, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "echo hello world"}, }, ) if err != nil { t.Fatal(err) } - match, err := regexp.Match("^[0-9a-f]{64}$", []byte(container1.Id)) + match, err := regexp.Match("^[0-9a-f]{64}$", []byte(container1.ID)) if err != nil { t.Fatal(err) } if !match { - t.Fatalf("Invalid container ID: %s", container1.Id) + t.Fatalf("Invalid container ID: %s", container1.ID) } } @@ -46,7 +46,7 @@ func TestMultipleAttachRestart(t *testing.T) { defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"}, }, @@ -153,7 +153,7 @@ func TestDiff(t *testing.T) { // Create a container and remove a file container1, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/rm", "/etc/passwd"}, }, ) @@ -194,7 +194,7 @@ func TestDiff(t *testing.T) { // Create a new container from the commited image container2, err := builder.Create( &Config{ - Image: img.Id, + Image: img.ID, Cmd: []string{"cat", "/etc/passwd"}, }, ) @@ -221,7 +221,7 @@ func TestDiff(t *testing.T) { // Create a new containere container3, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"rm", "/bin/httpd"}, }, ) @@ -260,7 +260,7 @@ func TestCommitAutoRun(t *testing.T) { builder := NewBuilder(runtime) container1, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, }, ) @@ -291,7 +291,7 @@ func TestCommitAutoRun(t *testing.T) { // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world container2, err := builder.Create( &Config{ - Image: img.Id, + Image: img.ID, }, ) if err != nil { @@ -340,7 +340,7 @@ func TestCommitRun(t *testing.T) { container1, err := builder.Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, }, ) @@ -372,7 +372,7 @@ func TestCommitRun(t *testing.T) { container2, err := builder.Create( &Config{ - Image: img.Id, + Image: img.ID, Cmd: []string{"cat", "/world"}, }, ) @@ -419,7 +419,7 @@ func TestStart(t *testing.T) { defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Memory: 33554432, CpuShares: 1000, Cmd: []string{"/bin/cat"}, @@ -463,7 +463,7 @@ func TestRun(t *testing.T) { defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, ) @@ -491,7 +491,7 @@ func TestOutput(t *testing.T) { defer nuke(runtime) container, err := NewBuilder(runtime).Create( &Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, ) @@ -515,7 +515,7 @@ func TestKillDifferentUser(t *testing.T) { } defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"tail", "-f", "/etc/resolv.conf"}, User: "daemon", }, @@ -563,7 +563,7 @@ func TestKill(t *testing.T) { } defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"cat", "/dev/zero"}, }, ) @@ -611,7 +611,7 @@ func TestExitCode(t *testing.T) { builder := NewBuilder(runtime) trueContainer, err := builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true", ""}, }) if err != nil { @@ -626,7 +626,7 @@ func TestExitCode(t *testing.T) { } falseContainer, err := builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/false", ""}, }) if err != nil { @@ -648,7 +648,7 @@ func TestRestart(t *testing.T) { } defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, ) @@ -681,7 +681,7 @@ func TestRestartStdin(t *testing.T) { } defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, OpenStdin: true, @@ -763,7 +763,7 @@ func TestUser(t *testing.T) { // Default user must be root container, err := builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, }, ) @@ -781,7 +781,7 @@ func TestUser(t *testing.T) { // Set a username container, err = builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, User: "root", @@ -801,7 +801,7 @@ func TestUser(t *testing.T) { // Set a UID container, err = builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, User: "0", @@ -821,7 +821,7 @@ func TestUser(t *testing.T) { // Set a different user by uid container, err = builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, User: "1", @@ -843,7 +843,7 @@ func TestUser(t *testing.T) { // Set a different user by username container, err = builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, User: "daemon", @@ -872,7 +872,7 @@ func TestMultipleContainers(t *testing.T) { builder := NewBuilder(runtime) container1, err := builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"cat", "/dev/zero"}, }, ) @@ -882,7 +882,7 @@ func TestMultipleContainers(t *testing.T) { defer runtime.Destroy(container1) container2, err := builder.Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"cat", "/dev/zero"}, }, ) @@ -928,7 +928,7 @@ func TestStdin(t *testing.T) { } defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, OpenStdin: true, @@ -975,7 +975,7 @@ func TestTty(t *testing.T) { } defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, OpenStdin: true, @@ -1022,7 +1022,7 @@ func TestEnv(t *testing.T) { } defer nuke(runtime) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/usr/bin/env"}, }, ) @@ -1100,7 +1100,7 @@ func TestLXCConfig(t *testing.T) { cpuMax := 10000 cpu := cpuMin + rand.Intn(cpuMax-cpuMin) container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true"}, Hostname: "foobar", @@ -1128,7 +1128,7 @@ func BenchmarkRunSequencial(b *testing.B) { defer nuke(runtime) for i := 0; i < b.N; i++ { container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, ) @@ -1163,7 +1163,7 @@ func BenchmarkRunParallel(b *testing.B) { tasks = append(tasks, complete) go func(i int, complete chan error) { container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, + Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, ) diff --git a/components/engine/contrib/crashTest.go b/components/engine/contrib/crashTest.go index d4a889e8d3..b3dbacaf03 100644 --- a/components/engine/contrib/crashTest.go +++ b/components/engine/contrib/crashTest.go @@ -11,13 +11,13 @@ import ( "time" ) -var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker") +var DOCKERPATH = path.Join(os.Getenv("DOCKERPATH"), "docker") // WARNING: this crashTest will 1) crash your host, 2) remove all containers func runDaemon() (*exec.Cmd, error) { os.Remove("/var/run/docker.pid") exec.Command("rm", "-rf", "/var/lib/docker/containers").Run() - cmd := exec.Command(DOCKER_PATH, "-d") + cmd := exec.Command(DOCKERPATH, "-d") outPipe, err := cmd.StdoutPipe() if err != nil { return nil, err @@ -77,7 +77,7 @@ func crashTest() error { stop = false for i := 0; i < 100 && !stop; { func() error { - cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount)) + cmd := exec.Command(DOCKERPATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount)) i++ totalTestCount++ outPipe, err := cmd.StdoutPipe() diff --git a/components/engine/contrib/docker-build/MAINTAINERS b/components/engine/contrib/docker-build/MAINTAINERS deleted file mode 100644 index e1c6f2ccfc..0000000000 --- a/components/engine/contrib/docker-build/MAINTAINERS +++ /dev/null @@ -1 +0,0 @@ -Solomon Hykes diff --git a/components/engine/contrib/docker-build/README b/components/engine/contrib/docker-build/README deleted file mode 100644 index f648753b90..0000000000 --- a/components/engine/contrib/docker-build/README +++ /dev/null @@ -1,68 +0,0 @@ -# docker-build: build your software with docker - -## Description - -docker-build is a script to build docker images from source. It will be deprecated once the 'build' feature is incorporated into docker itself (See https://github.com/dotcloud/docker/issues/278) - -Author: Solomon Hykes - - -## Install - -docker-builder requires: - -1) A reasonably recent Python setup (tested on 2.7.2). - -2) A running docker daemon at version 0.1.4 or more recent (http://www.docker.io/gettingstarted) - - -## Usage - -First create a valid Changefile, which defines a sequence of changes to apply to a base image. - - $ cat Changefile - # Start build from a know base image - from base:ubuntu-12.10 - # Update ubuntu sources - run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list - run apt-get update - # Install system packages - run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git - run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl - run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang - # Insert files from the host (./myscript must be present in the current directory) - copy myscript /usr/local/bin/myscript - - -Run docker-build, and pass the contents of your Changefile as standard input. - - $ IMG=$(./docker-build < Changefile) - -This will take a while: for each line of the changefile, docker-build will: - -1. Create a new container to execute the given command or insert the given file -2. Wait for the container to complete execution -3. Commit the resulting changes as a new image -4. Use the resulting image as the input of the next step - - -If all the steps succeed, the result will be an image containing the combined results of each build step. -You can trace back those build steps by inspecting the image's history: - - $ docker history $IMG - ID CREATED CREATED BY - 1e9e2045de86 A few seconds ago /bin/sh -c cat > /usr/local/bin/myscript; chmod +x /usr/local/bin/git - 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang - 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl - 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q git - 83e85d155451 A few seconds ago /bin/sh -c apt-get update - bfd53b36d9d3 A few seconds ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list - base 2 weeks ago /bin/bash - 27cf78414709 2 weeks ago - - -Note that your build started from 'base', as instructed by your Changefile. But that base image itself seems to have been built in 2 steps - hence the extra step in the history. - - -You can use this build technique to create any image you want: a database, a web application, or anything else that can be build by a sequence of unix commands - in other words, anything else. - diff --git a/components/engine/contrib/docker-build/docker-build b/components/engine/contrib/docker-build/docker-build deleted file mode 100755 index 654cc89793..0000000000 --- a/components/engine/contrib/docker-build/docker-build +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env python - -# docker-build is a script to build docker images from source. -# It will be deprecated once the 'build' feature is incorporated into docker itself. -# (See https://github.com/dotcloud/docker/issues/278) -# -# Author: Solomon Hykes - - - -# First create a valid Changefile, which defines a sequence of changes to apply to a base image. -# -# $ cat Changefile -# # Start build from a know base image -# from base:ubuntu-12.10 -# # Update ubuntu sources -# run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list -# run apt-get update -# # Install system packages -# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git -# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl -# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang -# # Insert files from the host (./myscript must be present in the current directory) -# copy myscript /usr/local/bin/myscript -# -# -# Run docker-build, and pass the contents of your Changefile as standard input. -# -# $ IMG=$(./docker-build < Changefile) -# -# This will take a while: for each line of the changefile, docker-build will: -# -# 1. Create a new container to execute the given command or insert the given file -# 2. Wait for the container to complete execution -# 3. Commit the resulting changes as a new image -# 4. Use the resulting image as the input of the next step - - -import sys -import subprocess -import json -import hashlib - -def docker(args, stdin=None): - print "# docker " + " ".join(args) - p = subprocess.Popen(["docker"] + list(args), stdin=stdin, stdout=subprocess.PIPE) - return p.stdout - -def image_exists(img): - return docker(["inspect", img]).read().strip() != "" - -def image_config(img): - return json.loads(docker(["inspect", img]).read()).get("config", {}) - -def run_and_commit(img_in, cmd, stdin=None, author=None, run=None): - run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip() - print "---> Waiting for " + run_id - result=int(docker(["wait", run_id]).read().rstrip()) - if result != 0: - print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result) - sys.exit(1) - return docker(["commit"] + (["-author", author] if author else []) + (["-run", json.dumps(run)] if run is not None else []) + [run_id]).read().rstrip() - -def insert(base, src, dst, author=None): - print "COPY {} to {} in {}".format(src, dst, base) - if dst == "": - raise Exception("Missing destination path") - stdin = file(src) - stdin.seek(0) - return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin, author=author) - -def add(base, src, dst, author=None): - print "PUSH to {} in {}".format(dst, base) - if src == ".": - tar = subprocess.Popen(["tar", "-c", "."], stdout=subprocess.PIPE).stdout - else: - tar = subprocess.Popen(["curl", src], stdout=subprocess.PIPE).stdout - if dst == "": - raise Exception("Missing argument to push") - return run_and_commit(base, "mkdir -p '{0}' && tar -C '{0}' -x".format(dst), stdin=tar, author=author) - -def main(): - base="" - maintainer="" - steps = [] - try: - for line in sys.stdin.readlines(): - line = line.strip() - # Skip comments and empty lines - if line == "" or line[0] == "#": - continue - op, param = line.split(None, 1) - print op.upper() + " " + param - if op == "from": - base = param - steps.append(base) - elif op == "maintainer": - maintainer = param - elif op == "run": - result = run_and_commit(base, param, author=maintainer) - steps.append(result) - base = result - print "===> " + base - elif op == "copy": - src, dst = param.split(" ", 1) - result = insert(base, src, dst, author=maintainer) - steps.append(result) - base = result - print "===> " + base - elif op == "add": - src, dst = param.split(" ", 1) - result = add(base, src, dst, author=maintainer) - steps.append(result) - base=result - print "===> " + base - elif op == "expose": - config = image_config(base) - if config.get("PortSpecs") is None: - config["PortSpecs"] = [] - portspec = param.strip() - config["PortSpecs"].append(portspec) - result = run_and_commit(base, "# (nop) expose port {}".format(portspec), author=maintainer, run=config) - steps.append(result) - base=result - print "===> " + base - elif op == "cmd": - config = image_config(base) - cmd = list(json.loads(param)) - config["Cmd"] = cmd - result = run_and_commit(base, "# (nop) set default command to '{}'".format(" ".join(cmd)), author=maintainer, run=config) - steps.append(result) - base=result - print "===> " + base - else: - print "Skipping uknown op " + op - except: - docker(["rmi"] + steps[1:]) - raise - print base - -if __name__ == "__main__": - main() diff --git a/components/engine/contrib/docker-build/example.changefile b/components/engine/contrib/docker-build/example.changefile deleted file mode 100644 index d76bbb4389..0000000000 --- a/components/engine/contrib/docker-build/example.changefile +++ /dev/null @@ -1,13 +0,0 @@ -# Start build from a know base image -maintainer Solomon Hykes -from base:ubuntu-12.10 -# Update ubuntu sources -run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list -run apt-get update -# Install system packages -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl -run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang -# Insert files from the host (./myscript must be present in the current directory) -copy myscript /usr/local/bin/myscript -push /src diff --git a/components/engine/contrib/docker-build/myscript b/components/engine/contrib/docker-build/myscript deleted file mode 100644 index a6ffda5f11..0000000000 --- a/components/engine/contrib/docker-build/myscript +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo hello, world! diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 7b8aa7f858..74236613a7 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -15,7 +15,7 @@ import ( ) var ( - GIT_COMMIT string + GITCOMMIT string ) func main() { @@ -33,6 +33,7 @@ func main() { bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to") + flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flag.Parse() if *bridgeName != "" { docker.NetworkBridgeIface = *bridgeName @@ -59,13 +60,13 @@ func main() { if *flDebug { os.Setenv("DEBUG", "1") } - docker.GIT_COMMIT = GIT_COMMIT + docker.GITCOMMIT = GITCOMMIT if *flDaemon { if flag.NArg() != 0 { flag.Usage() return } - if err := daemon(*pidfile, host, port, *flAutoRestart); err != nil { + if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors); err != nil { log.Fatal(err) os.Exit(-1) } @@ -104,7 +105,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile, addr string, port int, autoRestart bool) error { +func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error { if addr != "127.0.0.1" { log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } @@ -122,7 +123,7 @@ func daemon(pidfile, addr string, port int, autoRestart bool) error { os.Exit(0) }() - server, err := docker.NewServer(autoRestart) + server, err := docker.NewServer(autoRestart, enableCors) if err != nil { return err } diff --git a/components/engine/docs/Makefile b/components/engine/docs/Makefile index dcbf111c29..a97255f516 100644 --- a/components/engine/docs/Makefile +++ b/components/engine/docs/Makefile @@ -46,12 +46,11 @@ clean: -rm -rf $(BUILDDIR)/* docs: - #-rm -rf $(BUILDDIR)/* $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The documentation pages are now in $(BUILDDIR)/html." -server: +server: docs @cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000 site: @@ -62,12 +61,13 @@ site: connect: @echo connecting dotcloud to www.docker.io website, make sure to use user 1 - @cd _build/website/ ; \ - dotcloud connect dockerwebsite ; + @echo or create your own "dockerwebsite" app + @cd $(BUILDDIR)/website/ ; \ + dotcloud connect dockerwebsite ; \ dotcloud list push: - @cd _build/website/ ; \ + @cd $(BUILDDIR)/website/ ; \ dotcloud push $(VERSIONS): diff --git a/components/engine/docs/sources/api/README.md b/components/engine/docs/sources/api/README.md new file mode 100644 index 0000000000..10dede382b --- /dev/null +++ b/components/engine/docs/sources/api/README.md @@ -0,0 +1,5 @@ +This directory holds the authoritative specifications of APIs defined and implemented by Docker. Currently this includes: + +* The remote API by which a docker node can be queried over HTTP +* The registry API by which a docker node can download and upload container images for storage and sharing +* The index search API by which a docker node can search the public index for images to download diff --git a/components/engine/docs/sources/api/docker_remote_api.rst b/components/engine/docs/sources/api/docker_remote_api.rst index dca4599c55..95abd8339d 100644 --- a/components/engine/docs/sources/api/docker_remote_api.rst +++ b/components/engine/docs/sources/api/docker_remote_api.rst @@ -564,7 +564,7 @@ Create an image Content-Type: application/json {"status":"Pulling..."} - {"progress":"1/? (n/a)"} + {"status":"Pulling", "progress":"1/? (n/a)"} {"error":"Invalid..."} ... @@ -607,7 +607,7 @@ Insert a file in a image Content-Type: application/json {"status":"Inserting..."} - {"progress":"1/? (n/a)"} + {"status":"Inserting", "progress":"1/? (n/a)"} {"error":"Invalid..."} ... @@ -734,7 +734,7 @@ Push an image on the registry Content-Type: application/json {"status":"Pushing..."} - {"progress":"1/? (n/a)"} + {"status":"Pushing", "progress":"1/? (n/a)"} {"error":"Invalid..."} ... @@ -777,6 +777,7 @@ Tag an image into a repository :statuscode 200: no error :statuscode 400: bad parameter :statuscode 404: no such image + :statuscode 409: conflict :statuscode 500: server error @@ -793,14 +794,30 @@ Remove an image DELETE /images/test HTTP/1.1 - **Example response**: + **Example response v1.0**: .. sourcecode:: http HTTP/1.1 204 OK + **Example response v1.1**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error :statuscode 204: no error :statuscode 404: no such image + :statuscode 409: conflict :statuscode 500: server error @@ -839,9 +856,9 @@ Search images } ] - :query term: term to search - :statuscode 200: no error - :statuscode 500: server error + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error 3.3 Misc @@ -1056,3 +1073,36 @@ Here are the steps of 'docker run' : In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. + +3.3 CORS Requests +----------------- + +To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. + + docker -d -H="192.168.1.9:4243" -api-enable-cors + + +================================== +Docker Remote API Client Libraries +================================== + +These libraries have been not tested by the Docker Maintainers for +compatibility. Please file issues with the library owners. If you +find more library implementations, please list them in Docker doc bugs +and we will add the libraries here. + ++----------------------+----------------+--------------------------------------------+ +| Language/Framework | Name | Repository | ++======================+================+============================================+ +| Python | docker-py | https://github.com/dotcloud/docker-py | ++----------------------+----------------+--------------------------------------------+ +| Ruby | docker-ruby | https://github.com/ActiveState/docker-ruby | ++----------------------+----------------+--------------------------------------------+ +| Ruby | docker-client | https://github.com/geku/docker-client | ++----------------------+----------------+--------------------------------------------+ +| Javascript | docker-js | https://github.com/dgoujard/docker-js | ++----------------------+----------------+--------------------------------------------+ +| Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | +| **WebUI** | | | ++----------------------+----------------+--------------------------------------------+ + diff --git a/components/engine/docs/sources/api/index.rst b/components/engine/docs/sources/api/index.rst index 85770f484e..d7345e8746 100644 --- a/components/engine/docs/sources/api/index.rst +++ b/components/engine/docs/sources/api/index.rst @@ -5,13 +5,14 @@ APIs ==== -This following : +Your programs and scripts can access Docker's functionality via these interfaces: .. toctree:: :maxdepth: 3 + registry_index_spec registry_api - index_search_api + index_api docker_remote_api diff --git a/components/engine/docs/sources/api/index_api.rst b/components/engine/docs/sources/api/index_api.rst new file mode 100644 index 0000000000..42dc49a5d7 --- /dev/null +++ b/components/engine/docs/sources/api/index_api.rst @@ -0,0 +1,553 @@ +:title: Index API +:description: API Documentation for Docker Index +:keywords: API, Docker, index, REST, documentation + +================= +Docker Index API +================= + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- This is the REST API for the Docker index +- Authorization is done with basic auth over SSL +- Not all commands require authentication, only those noted as such. + +2. Endpoints +============ + +2.1 Repository +^^^^^^^^^^^^^^ + +Repositories +************* + +User Repo +~~~~~~~~~ + +.. http:put:: /v1/repositories/(namespace)/(repo_name)/ + + Create a user repository with the given ``namespace`` and ``repo_name``. + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/repositories/foo/bar/ HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Basic akmklmasadalkm== + X-Docker-Token: true + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}] + + :parameter namespace: the namespace for the repo + :parameter repo_name: the name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write + X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] + + "" + + :statuscode 200: Created + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active + + +.. http:delete:: /v1/repositories/(namespace)/(repo_name)/ + + Delete a user repository with the given ``namespace`` and ``repo_name``. + + **Example Request**: + + .. sourcecode:: http + + DELETE /v1/repositories/foo/bar/ HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Basic akmklmasadalkm== + X-Docker-Token: true + + "" + + :parameter namespace: the namespace for the repo + :parameter repo_name: the name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 202 + Vary: Accept + Content-Type: application/json + WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=delete + X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] + + "" + + :statuscode 200: Deleted + :statuscode 202: Accepted + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active + +Library Repo +~~~~~~~~~~~~ + +.. http:put:: /v1/repositories/(repo_name)/ + + Create a library repository with the given ``repo_name``. + This is a restricted feature only available to docker admins. + + When namespace is missing, it is assumed to be ``library`` + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/repositories/foobar/ HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Basic akmklmasadalkm== + X-Docker-Token: true + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}] + + :parameter repo_name: the library name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=write + X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] + + "" + + :statuscode 200: Created + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active + +.. http:delete:: /v1/repositories/(repo_name)/ + + Delete a library repository with the given ``repo_name``. + This is a restricted feature only available to docker admins. + + When namespace is missing, it is assumed to be ``library`` + + **Example Request**: + + .. sourcecode:: http + + DELETE /v1/repositories/foobar/ HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Basic akmklmasadalkm== + X-Docker-Token: true + + "" + + :parameter repo_name: the library name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 202 + Vary: Accept + Content-Type: application/json + WWW-Authenticate: Token signature=123abc,repository=”library/foobar”,access=delete + X-Docker-Endpoints: registry-1.docker.io [, registry-2.docker.io] + + "" + + :statuscode 200: Deleted + :statuscode 202: Accepted + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active + +Repository Images +***************** + +User Repo Images +~~~~~~~~~~~~~~~~ + +.. http:put:: /v1/repositories/(namespace)/(repo_name)/images + + Update the images for a user repo. + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/repositories/foo/bar/images HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Basic akmklmasadalkm== + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, + “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] + + :parameter namespace: the namespace for the repo + :parameter repo_name: the name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 204 + Vary: Accept + Content-Type: application/json + + "" + + :statuscode 204: Created + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active or permission denied + + +.. http:get:: /v1/repositories/(namespace)/(repo_name)/images + + get the images for a user repo. + + **Example Request**: + + .. sourcecode:: http + + GET /v1/repositories/foo/bar/images HTTP/1.1 + Host: index.docker.io + Accept: application/json + + :parameter namespace: the namespace for the repo + :parameter repo_name: the name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, + “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}, + {“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”, + “checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}] + + :statuscode 200: OK + :statuscode 404: Not found + +Library Repo Images +~~~~~~~~~~~~~~~~~~~ + +.. http:put:: /v1/repositories/(repo_name)/images + + Update the images for a library repo. + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/repositories/foobar/images HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Basic akmklmasadalkm== + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, + “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] + + :parameter repo_name: the library name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 204 + Vary: Accept + Content-Type: application/json + + "" + + :statuscode 204: Created + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active or permission denied + + +.. http:get:: /v1/repositories/(repo_name)/images + + get the images for a library repo. + + **Example Request**: + + .. sourcecode:: http + + GET /v1/repositories/foobar/images HTTP/1.1 + Host: index.docker.io + Accept: application/json + + :parameter repo_name: the library name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, + “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}, + {“id”: “ertwetewtwe38722009fe6857087b486531f9a779a0c1dfddgfgsdgdsgds”, + “checksum”: “34t23f23fc17e3ed29dae8f12c4f9e89cc6f0bsdfgfsdgdsgdsgerwgew”}] + + :statuscode 200: OK + :statuscode 404: Not found + + +Repository Authorization +************************ + +Library Repo +~~~~~~~~~~~~ + +.. http:put:: /v1/repositories/(repo_name)/auth + + authorize a token for a library repo + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/repositories/foobar/auth HTTP/1.1 + Host: index.docker.io + Accept: application/json + Authorization: Token signature=123abc,repository="library/foobar",access=write + + :parameter repo_name: the library name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + "OK" + + :statuscode 200: OK + :statuscode 403: Permission denied + :statuscode 404: Not found + + +User Repo +~~~~~~~~~ + +.. http:put:: /v1/repositories/(namespace)/(repo_name)/auth + + authorize a token for a user repo + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/repositories/foo/bar/auth HTTP/1.1 + Host: index.docker.io + Accept: application/json + Authorization: Token signature=123abc,repository="foo/bar",access=write + + :parameter namespace: the namespace for the repo + :parameter repo_name: the name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + "OK" + + :statuscode 200: OK + :statuscode 403: Permission denied + :statuscode 404: Not found + + +2.2 Users +^^^^^^^^^ + +User Login +********** + +.. http:get:: /v1/users + + If you want to check your login, you can try this endpoint + + **Example Request**: + + .. sourcecode:: http + + GET /v1/users HTTP/1.1 + Host: index.docker.io + Accept: application/json + Authorization: Basic akmklmasadalkm== + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + OK + + :statuscode 200: no error + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active + + +User Register +************* + +.. http:post:: /v1/users + + Registering a new account. + + **Example request**: + + .. sourcecode:: http + + POST /v1/users HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + + {"email": "sam@dotcloud.com", + "password": "toto42", + "username": "foobar"'} + + :jsonparameter email: valid email address, that needs to be confirmed + :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. + :jsonparameter password: min 5 characters + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Vary: Accept + Content-Type: application/json + + "User Created" + + :statuscode 201: User Created + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + +Update User +*********** + +.. http:put:: /v1/users/(username)/ + + Change a password or email address for given user. If you pass in an email, + it will add it to your account, it will not remove the old one. Passwords will + be updated. + + It is up to the client to verify that that password that is sent is the one that + they want. Common approach is to have them type it twice. + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/users/fakeuser/ HTTP/1.1 + Host: index.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Basic akmklmasadalkm== + + {"email": "sam@dotcloud.com", + "password": "toto42"} + + :parameter username: username for the person you want to update + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 204 + Vary: Accept + Content-Type: application/json + + "" + + :statuscode 204: User Updated + :statuscode 400: Errors (invalid json, missing or invalid fields, etc) + :statuscode 401: Unauthorized + :statuscode 403: Account is not Active + :statuscode 404: User not found + + +2.3 Search +^^^^^^^^^^ +If you need to search the index, this is the endpoint you would use. + +Search +****** + +.. http:get:: /v1/search + + Search the Index given a search term. It accepts :http:method:`get` only. + + **Example request**: + + .. sourcecode:: http + + GET /v1/search?q=search_term HTTP/1.1 + Host: example.com + Accept: application/json + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + {"query":"search_term", + "num_results": 2, + "results" : [ + {"name": "dotcloud/base", "description": "A base ubuntu64 image..."}, + {"name": "base2", "description": "A base ubuntu64 image..."}, + ] + } + + :query q: what you want to search for + :statuscode 200: no error + :statuscode 500: server error diff --git a/components/engine/docs/sources/api/index_search_api.rst b/components/engine/docs/sources/api/index_search_api.rst deleted file mode 100644 index e2f8edc492..0000000000 --- a/components/engine/docs/sources/api/index_search_api.rst +++ /dev/null @@ -1,43 +0,0 @@ -:title: Docker Index documentation -:description: Documentation for docker Index -:keywords: docker, index, api - - -======================= -Docker Index Search API -======================= - -Search ------- - -.. http:get:: /v1/search - - Search the Index given a search term. It accepts :http:method:`get` only. - - **Example request**: - - .. sourcecode:: http - - GET /v1/search?q=search_term HTTP/1.1 - Host: example.com - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - {"query":"search_term", - "num_results": 2, - "results" : [ - {"name": "dotcloud/base", "description": "A base ubuntu64 image..."}, - {"name": "base2", "description": "A base ubuntu64 image..."}, - ] - } - - :query q: what you want to search for - :statuscode 200: no error - :statuscode 500: server error \ No newline at end of file diff --git a/components/engine/docs/sources/api/registry_api.rst b/components/engine/docs/sources/api/registry_api.rst index bb8a16652d..58c8ff3bd4 100644 --- a/components/engine/docs/sources/api/registry_api.rst +++ b/components/engine/docs/sources/api/registry_api.rst @@ -1,7 +1,6 @@ -:title: Registry Documentation -:description: Documentation for docker Registry and Registry API -:keywords: docker, registry, api, index - +:title: Registry API +:description: API Documentation for Docker Registry +:keywords: API, Docker, index, registry, REST, documentation =================== Docker Registry API @@ -9,29 +8,10 @@ Docker Registry API .. contents:: Table of Contents -1. The 3 roles -=============== +1. Brief introduction +===================== -1.1 Index ---------- - -The Index is responsible for centralizing information about: -- User accounts -- Checksums of the images -- Public namespaces - -The Index has different components: -- Web UI -- Meta-data store (comments, stars, list public repositories) -- Authentication service -- Tokenization - -The index is authoritative for those information. - -We expect that there will be only one instance of the index, run and managed by dotCloud. - -1.2 Registry ------------- +- This is the REST API for the Docker Registry - It stores the images and the graph for a set of repositories - It does not have user accounts data - It has no notion of user accounts or authorization @@ -60,418 +40,424 @@ We expect that there will be multiple registries out there. To help to grasp the The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys). -1.3 Docker +2. Endpoints +============ + +2.1 Images ---------- -On top of being a runtime for LXC, Docker is the Registry client. It supports: -- Push / Pull on the registry -- Client authentication on the Index +Layer +***** -2. Workflow -=========== +.. http:get:: /v1/images/(image_id)/layer -2.1 Pull + get image layer for a given ``image_id`` + + **Example Request**: + + .. sourcecode:: http + + GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/layer HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Token akmklmasadalkmsdfgsdgdge33 + + :parameter image_id: the id for the layer you want to get + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + Cookie: (Cookie provided by the Registry) + + { + id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c", + parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f", + created: "2013-04-30T17:46:10.843673+03:00", + container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7", + container_config: { + Hostname: "host-test", + User: "", + Memory: 0, + MemorySwap: 0, + AttachStdin: false, + AttachStdout: false, + AttachStderr: false, + PortSpecs: null, + Tty: false, + OpenStdin: false, + StdinOnce: false, + Env: null, + Cmd: [ + "/bin/bash", + "-c", + "apt-get -q -yy -f install libevent-dev" + ], + Dns: null, + Image: "imagename/blah", + Volumes: { }, + VolumesFrom: "" + }, + docker_version: "0.1.7" + } + + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Image not found + + +.. http:put:: /v1/images/(image_id)/layer + + put image layer for a given ``image_id`` + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/layer HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json + Authorization: Token akmklmasadalkmsdfgsdgdge33 + + { + id: "088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c", + parent: "aeee6396d62273d180a49c96c62e45438d87c7da4a5cf5d2be6bee4e21bc226f", + created: "2013-04-30T17:46:10.843673+03:00", + container: "8305672a76cc5e3d168f97221106ced35a76ec7ddbb03209b0f0d96bf74f6ef7", + container_config: { + Hostname: "host-test", + User: "", + Memory: 0, + MemorySwap: 0, + AttachStdin: false, + AttachStdout: false, + AttachStderr: false, + PortSpecs: null, + Tty: false, + OpenStdin: false, + StdinOnce: false, + Env: null, + Cmd: [ + "/bin/bash", + "-c", + "apt-get -q -yy -f install libevent-dev" + ], + Dns: null, + Image: "imagename/blah", + Volumes: { }, + VolumesFrom: "" + }, + docker_version: "0.1.7" + } + + :parameter image_id: the id for the layer you want to get + + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + "" + + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Image not found + + +Image +***** + +.. http:put:: /v1/images/(image_id)/json + + put image for a given ``image_id`` + + **Example Request**: + + .. sourcecode:: http + + PUT /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/json HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json + Cookie: (Cookie provided by the Registry) + + { + “id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”, + “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087” + } + + :parameter image_id: the id for the layer you want to get + + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + "" + + :statuscode 200: OK + :statuscode 401: Requires authorization + +.. http:get:: /v1/images/(image_id)/json + + get image for a given ``image_id`` + + **Example Request**: + + .. sourcecode:: http + + GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/json HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json + Cookie: (Cookie provided by the Registry) + + :parameter image_id: the id for the layer you want to get + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + { + “id”: “088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c”, + “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087” + } + + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Image not found + + +Ancestry +******** + +.. http:get:: /v1/images/(image_id)/ancestry + + get ancestry for an image given an ``image_id`` + + **Example Request**: + + .. sourcecode:: http + + GET /v1/images/088b4505aa3adc3d35e79c031fa126b403200f02f51920fbd9b7c503e87c7a2c/ancestry HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json + Cookie: (Cookie provided by the Registry) + + :parameter image_id: the id for the layer you want to get + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + ["088b4502f51920fbd9b7c503e87c7a2c05aa3adc3d35e79c031fa126b403200f", + "aeee63968d87c7da4a5cf5d2be6bee4e21bc226fd62273d180a49c96c62e4543", + "bfa4c5326bc764280b0863b46a4b20d940bc1897ef9c1dfec060604bdc383280", + "6ab5893c6927c15a15665191f2c6cf751f5056d8b95ceee32e43c5e8a3648544"] + + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Image not found + + +2.2 Tags -------- -.. image:: /static_files/docker_pull_chart.png +.. http:get:: /v1/repositories/(namespace)/(repository)/tags -1. Contact the Index to know where I should download “samalba/busybox” -2. Index replies: - a. “samalba/busybox” is on Registry A - b. here are the checksums for “samalba/busybox” (for all layers) - c. token -3. Contact Registry A to receive the layers for “samalba/busybox” (all of them to the base image). Registry A is authoritative for “samalba/busybox” but keeps a copy of all inherited layers and serve them all from the same location. -4. registry contacts index to verify if token/user is allowed to download images -5. Index returns true/false lettings registry know if it should proceed or error out -6. Get the payload for all layers + get all of the tags for the given repo. -It’s possible to run docker pull \https:///repositories/samalba/busybox. In this case, docker bypasses the Index. However the security is not guaranteed (in case Registry A is corrupted) because there won’t be any checksum checks. + **Example Request**: -Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage. + .. sourcecode:: http -Token is only returned when the 'X-Docker-Token' header is sent with request. - -Basic Auth is required to pull private repos. Basic auth isn't required for pulling public repos, but if one is provided, it needs to be valid and for an active account. - -API (pulling repository foo/bar): -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -1. (Docker -> Index) GET /v1/repositories/foo/bar/images - **Headers**: - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== - X-Docker-Token: true - **Action**: - (looking up the foo/bar in db and gets images and checksums for that repo (all if no tag is specified, if tag, only checksums for those tags) see part 4.4.1) - -2. (Index -> Docker) HTTP 200 OK - - **Headers**: - - Authorization: Token signature=123abc,repository=”foo/bar”,access=write - - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] - **Body**: - Jsonified checksums (see part 4.4.1) - -3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest - **Headers**: - Authorization: Token signature=123abc,repository=”foo/bar”,access=write - -4. (Registry -> Index) GET /v1/repositories/foo/bar/images - - **Headers**: - Authorization: Token signature=123abc,repository=”foo/bar”,access=read - - **Body**: - - - **Action**: - ( Lookup token see if they have access to pull.) - - If good: - HTTP 200 OK - Index will invalidate the token - If bad: - HTTP 401 Unauthorized - -5. (Docker -> Registry) GET /v1/images/928374982374/ancestry - **Action**: - (for each image id returned in the registry, fetch /json + /layer) - -.. note:: - - If someone makes a second request, then we will always give a new token, never reuse tokens. - -2.2 Push --------- - -.. image:: /static_files/docker_push_chart.png - -1. Contact the index to allocate the repository name “samalba/busybox” (authentication required with user credentials) -2. If authentication works and namespace available, “samalba/busybox” is allocated and a temporary token is returned (namespace is marked as initialized in index) -3. Push the image on the registry (along with the token) -4. Registry A contacts the Index to verify the token (token must corresponds to the repository name) -5. Index validates the token. Registry A starts reading the stream pushed by docker and store the repository (with its images) -6. docker contacts the index to give checksums for upload images - -.. note:: - - **It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed. - -.. note:: - - **Index can be replaced!** For a private Registry deployed, a custom Index can be used to serve and validate token according to different policies. - -Docker computes the checksums and submit them to the Index at the end of the push. When a repository name does not have checksums on the Index, it means that the push is in progress (since checksums are submitted at the end). - -API (pushing repos foo/bar): -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -1. (Docker -> Index) PUT /v1/repositories/foo/bar/ - **Headers**: - Authorization: Basic sdkjfskdjfhsdkjfh== - X-Docker-Token: true - - **Action**:: - - in index, we allocated a new repository, and set to initialized - - **Body**:: - (The body contains the list of images that are going to be pushed, with empty checksums. The checksums will be set at the end of the push):: - - [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}] - -2. (Index -> Docker) 200 Created - **Headers**: - - WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write - - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] - -3. (Docker -> Registry) PUT /v1/images/98765432_parent/json - **Headers**: - Authorization: Token signature=123abc,repository=”foo/bar”,access=write - -4. (Registry->Index) GET /v1/repositories/foo/bar/images - **Headers**: - Authorization: Token signature=123abc,repository=”foo/bar”,access=write - **Action**:: - - Index: - will invalidate the token. - - Registry: - grants a session (if token is approved) and fetches the images id - -5. (Docker -> Registry) PUT /v1/images/98765432_parent/json - **Headers**:: - - Authorization: Token signature=123abc,repository=”foo/bar”,access=write - - Cookie: (Cookie provided by the Registry) - -6. (Docker -> Registry) PUT /v1/images/98765432/json - **Headers**: + GET /v1/repositories/foo/bar/tags HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json Cookie: (Cookie provided by the Registry) -7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer - **Headers**: + :parameter namespace: namespace for the repo + :parameter repository: name for the repo + + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + { + "latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", + “0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087” + } + + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Repository not found + + +.. http:get:: /v1/repositories/(namespace)/(repository)/tags/(tag) + + get a tag for the given repo. + + **Example Request**: + + .. sourcecode:: http + + GET /v1/repositories/foo/bar/tags/latest HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json Cookie: (Cookie provided by the Registry) -8. (Docker -> Registry) PUT /v1/images/98765432/layer - **Headers**: - X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh + :parameter namespace: namespace for the repo + :parameter repository: name for the repo + :parameter tag: name of tag you want to get -9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest - **Headers**: + **Example Response**: + + .. sourcecode:: http + + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json + + "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f" + + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Tag not found + +.. http:delete:: /v1/repositories/(namespace)/(repository)/tags/(tag) + + delete the tag for the repo + + **Example Request**: + + .. sourcecode:: http + + DELETE /v1/repositories/foo/bar/tags/latest HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json Cookie: (Cookie provided by the Registry) - **Body**: - “98765432” -10. (Docker -> Index) PUT /v1/repositories/foo/bar/images + :parameter namespace: namespace for the repo + :parameter repository: name for the repo + :parameter tag: name of tag you want to delete - **Headers**: - Authorization: Basic 123oislifjsldfj== - X-Docker-Endpoints: registry1.docker.io (no validation on this right now) + **Example Response**: - **Body**: - (The image, id’s, tags and checksums) + .. sourcecode:: http - [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, - “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json - **Return** HTTP 204 + "" -.. note:: + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Tag not found - If push fails and they need to start again, what happens in the index, there will already be a record for the namespace/name, but it will be initialized. Should we allow it, or mark as name already used? One edge case could be if someone pushes the same thing at the same time with two different shells. - If it's a retry on the Registry, Docker has a cookie (provided by the registry after token validation). So the Index won’t have to provide a new token. +.. http:put:: /v1/repositories/(namespace)/(repository)/tags/(tag) -3. How to use the Registry in standalone mode -============================================= + put a tag for the given repo. -The Index has two main purposes (along with its fancy social features): + **Example Request**: -- Resolve short names (to avoid passing absolute URLs all the time) - - username/projectname -> \https://registry.docker.io/users//repositories// -- Authenticate a user as a repos owner (for a central referenced repository) + .. sourcecode:: http -3.1 Without an Index --------------------- -Using the Registry without the Index can be useful to store the images on a private network without having to rely on an external entity controlled by dotCloud. + PUT /v1/repositories/foo/bar/tags/latest HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json + Cookie: (Cookie provided by the Registry) -In this case, the registry will be launched in a special mode (--standalone? --no-index?). In this mode, the only thing which changes is that Registry will never contact the Index to verify a token. It will be the Registry owner responsibility to authenticate the user who pushes (or even pulls) an image using any mechanism (HTTP auth, IP based, etc...). + “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f” -In this scenario, the Registry is responsible for the security in case of data corruption since the checksums are not delivered by a trusted entity. + :parameter namespace: namespace for the repo + :parameter repository: name for the repo + :parameter tag: name of tag you want to add -As hinted previously, a standalone registry can also be implemented by any HTTP server handling GET/PUT requests (or even only GET requests if no write access is necessary). + **Example Response**: -3.2 With an Index ------------------ + .. sourcecode:: http -The Index data needed by the Registry are simple: -- Serve the checksums -- Provide and authorize a Token + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json -In the scenario of a Registry running on a private network with the need of centralizing and authorizing, it’s easy to use a custom Index. + "" -The only challenge will be to tell Docker to contact (and trust) this custom Index. Docker will be configurable at some point to use a specific Index, it’ll be the private entity responsibility (basically the organization who uses Docker in a private environment) to maintain the Index and the Docker’s configuration among its consumers. + :statuscode 200: OK + :statuscode 400: Invalid data + :statuscode 401: Requires authorization + :statuscode 404: Image not found -4. The API -========== +2.3 Repositories +---------------- -The first version of the api is available here: https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md +.. http:delete:: /v1/repositories/(namespace)/(repository)/ -4.1 Images ----------- + delete a repository -The format returned in the images is not defined here (for layer and json), basically because Registry stores exactly the same kind of information as Docker uses to manage them. + **Example Request**: -The format of ancestry is a line-separated list of image ids, in age order. I.e. the image’s parent is on the last line, the parent of the parent on the next-to-last line, etc.; if the image has no parent, the file is empty. + .. sourcecode:: http -GET /v1/images//layer -PUT /v1/images//layer -GET /v1/images//json -PUT /v1/images//json -GET /v1/images//ancestry -PUT /v1/images//ancestry + DELETE /v1/repositories/foo/bar/ HTTP/1.1 + Host: registry-1.docker.io + Accept: application/json + Content-Type: application/json + Cookie: (Cookie provided by the Registry) -4.2 Users ---------- + "" -4.2.1 Create a user (Index) -^^^^^^^^^^^^^^^^^^^^^^^^^^^ + :parameter namespace: namespace for the repo + :parameter repository: name for the repo -POST /v1/users + **Example Response**: -**Body**: - {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'} + .. sourcecode:: http -**Validation**: - - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. - - **password**: min 5 characters + HTTP/1.1 200 + Vary: Accept + Content-Type: application/json -**Valid**: return HTTP 200 + "" -Errors: HTTP 400 (we should create error codes for possible errors) -- invalid json -- missing field -- wrong format (username, password, email, etc) -- forbidden name -- name already exists + :statuscode 200: OK + :statuscode 401: Requires authorization + :statuscode 404: Repository not found -.. note:: +3.0 Authorization +================= +This is where we describe the authorization process, including the tokens and cookies. - A user account will be valid only if the email has been validated (a validation link is sent to the email address). - -4.2.2 Update a user (Index) -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -PUT /v1/users/ - -**Body**: - {"password": "toto"} - -.. note:: - - We can also update email address, if they do, they will need to reverify their new email address. - -4.2.3 Login (Index) -^^^^^^^^^^^^^^^^^^^ -Does nothing else but asking for a user authentication. Can be used to validate credentials. HTTP Basic Auth for now, maybe change in future. - -GET /v1/users - -**Return**: - - Valid: HTTP 200 - - Invalid login: HTTP 401 - - Account inactive: HTTP 403 Account is not Active - -4.3 Tags (Registry) -------------------- - -The Registry does not know anything about users. Even though repositories are under usernames, it’s just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registry’s API. - -The following naming restrictions apply: - -- Namespaces must match the same regular expression as usernames (See 4.2.1.) -- Repository names must match the regular expression [a-zA-Z0-9-_.] - -4.3.1 Get all tags -^^^^^^^^^^^^^^^^^^ - -GET /v1/repositories///tags - -**Return**: HTTP 200 - { - "latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", - “0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087” - } - -4.3.2 Read the content of a tag (resolve the image id) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -GET /v1/repositories///tags/ - -**Return**: - "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f" - -4.3.3 Delete a tag (registry) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -DELETE /v1/repositories///tags/ - -4.4 Images (Index) ------------------- - -For the Index to “resolve” the repository name to a Registry location, it uses the X-Docker-Endpoints header. In other terms, this requests always add a “X-Docker-Endpoints” to indicate the location of the registry which hosts this repository. - -4.4.1 Get the images -^^^^^^^^^^^^^^^^^^^^^ - -GET /v1/repositories///images - -**Return**: HTTP 200 - [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] - - -4.4.2 Add/update the images -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You always add images, you never remove them. - -PUT /v1/repositories///images - -**Body**: - [ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ] - -**Return** 204 - -5. Chaining Registries -====================== - -It’s possible to chain Registries server for several reasons: -- Load balancing -- Delegate the next request to another server - -When a Registry is a reference for a repository, it should host the entire images chain in order to avoid breaking the chain during the download. - -The Index and Registry use this mechanism to redirect on one or the other. - -Example with an image download: -On every request, a special header can be returned: - -X-Docker-Endpoints: server1,server2 - -On the next request, the client will always pick a server from this list. - -6. Authentication & Authorization -================================= - -6.1 On the Index ------------------ - -The Index supports both “Basic” and “Token” challenges. Usually when there is a “401 Unauthorized”, the Index replies this:: - - 401 Unauthorized - WWW-Authenticate: Basic realm="auth required",Token - -You have 3 options: - -1. Provide user credentials and ask for a token - - **Header**: - - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== - - X-Docker-Token: true - - In this case, along with the 200 response, you’ll get a new token (if user auth is ok): - If authorization isn't correct you get a 401 response. - If account isn't active you will get a 403 response. - - **Response**: - - 200 OK - - X-Docker-Token: Token signature=123abc,repository=”foo/bar”,access=read - -2. Provide user credentials only - - **Header**: - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== - -3. Provide Token - - **Header**: - Authorization: Token signature=123abc,repository=”foo/bar”,access=read - -6.2 On the Registry -------------------- - -The Registry only supports the Token challenge:: - - 401 Unauthorized - WWW-Authenticate: Token - -The only way is to provide a token on “401 Unauthorized” responses:: - - Authorization: Token signature=123abc,repository=”foo/bar”,access=read - -Usually, the Registry provides a Cookie when a Token verification succeeded. Every time the Registry passes a Cookie, you have to pass it back the same cookie.:: - - 200 OK - Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly - -Next request:: - - GET /(...) - Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4=" +TODO: add more info. diff --git a/components/engine/docs/sources/api/registry_index_spec.rst b/components/engine/docs/sources/api/registry_index_spec.rst new file mode 100644 index 0000000000..c1854194b2 --- /dev/null +++ b/components/engine/docs/sources/api/registry_index_spec.rst @@ -0,0 +1,569 @@ +:title: Registry Documentation +:description: Documentation for docker Registry and Registry API +:keywords: docker, registry, api, index + + +===================== +Registry & index Spec +===================== + +.. contents:: Table of Contents + +1. The 3 roles +=============== + +1.1 Index +--------- + +The Index is responsible for centralizing information about: +- User accounts +- Checksums of the images +- Public namespaces + +The Index has different components: +- Web UI +- Meta-data store (comments, stars, list public repositories) +- Authentication service +- Tokenization + +The index is authoritative for those information. + +We expect that there will be only one instance of the index, run and managed by dotCloud. + +1.2 Registry +------------ +- It stores the images and the graph for a set of repositories +- It does not have user accounts data +- It has no notion of user accounts or authorization +- It delegates authentication and authorization to the Index Auth service using tokens +- It supports different storage backends (S3, cloud files, local FS) +- It doesn’t have a local database +- It will be open-sourced at some point + +We expect that there will be multiple registries out there. To help to grasp the context, here are some examples of registries: + +- **sponsor registry**: such a registry is provided by a third-party hosting infrastructure as a convenience for their customers and the docker community as a whole. Its costs are supported by the third party, but the management and operation of the registry are supported by dotCloud. It features read/write access, and delegates authentication and authorization to the Index. +- **mirror registry**: such a registry is provided by a third-party hosting infrastructure but is targeted at their customers only. Some mechanism (unspecified to date) ensures that public images are pulled from a sponsor registry to the mirror registry, to make sure that the customers of the third-party provider can “docker pull” those images locally. +- **vendor registry**: such a registry is provided by a software vendor, who wants to distribute docker images. It would be operated and managed by the vendor. Only users authorized by the vendor would be able to get write access. Some images would be public (accessible for anyone), others private (accessible only for authorized users). Authentication and authorization would be delegated to the Index. The goal of vendor registries is to let someone do “docker pull basho/riak1.3” and automatically push from the vendor registry (instead of a sponsor registry); i.e. get all the convenience of a sponsor registry, while retaining control on the asset distribution. +- **private registry**: such a registry is located behind a firewall, or protected by an additional security layer (HTTP authorization, SSL client-side certificates, IP address authorization...). The registry is operated by a private entity, outside of dotCloud’s control. It can optionally delegate additional authorization to the Index, but it is not mandatory. + +.. note:: + + Mirror registries and private registries which do not use the Index don’t even need to run the registry code. They can be implemented by any kind of transport implementing HTTP GET and PUT. Read-only registries can be powered by a simple static HTTP server. + +.. note:: + + The latter implies that while HTTP is the protocol of choice for a registry, multiple schemes are possible (and in some cases, trivial): + - HTTP with GET (and PUT for read-write registries); + - local mount point; + - remote docker addressed through SSH. + +The latter would only require two new commands in docker, e.g. “registryget” and “registryput”, wrapping access to the local filesystem (and optionally doing consistency checks). Authentication and authorization are then delegated to SSH (e.g. with public keys). + +1.3 Docker +---------- + +On top of being a runtime for LXC, Docker is the Registry client. It supports: +- Push / Pull on the registry +- Client authentication on the Index + +2. Workflow +=========== + +2.1 Pull +-------- + +.. image:: /static_files/docker_pull_chart.png + +1. Contact the Index to know where I should download “samalba/busybox” +2. Index replies: + a. “samalba/busybox” is on Registry A + b. here are the checksums for “samalba/busybox” (for all layers) + c. token +3. Contact Registry A to receive the layers for “samalba/busybox” (all of them to the base image). Registry A is authoritative for “samalba/busybox” but keeps a copy of all inherited layers and serve them all from the same location. +4. registry contacts index to verify if token/user is allowed to download images +5. Index returns true/false lettings registry know if it should proceed or error out +6. Get the payload for all layers + +It’s possible to run docker pull \https:///repositories/samalba/busybox. In this case, docker bypasses the Index. However the security is not guaranteed (in case Registry A is corrupted) because there won’t be any checksum checks. + +Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage. + +Token is only returned when the 'X-Docker-Token' header is sent with request. + +Basic Auth is required to pull private repos. Basic auth isn't required for pulling public repos, but if one is provided, it needs to be valid and for an active account. + +API (pulling repository foo/bar): +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. (Docker -> Index) GET /v1/repositories/foo/bar/images + **Headers**: + Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + X-Docker-Token: true + **Action**: + (looking up the foo/bar in db and gets images and checksums for that repo (all if no tag is specified, if tag, only checksums for those tags) see part 4.4.1) + +2. (Index -> Docker) HTTP 200 OK + + **Headers**: + - Authorization: Token signature=123abc,repository=”foo/bar”,access=write + - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] + **Body**: + Jsonified checksums (see part 4.4.1) + +3. (Docker -> Registry) GET /v1/repositories/foo/bar/tags/latest + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=write + +4. (Registry -> Index) GET /v1/repositories/foo/bar/images + + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=read + + **Body**: + + + **Action**: + ( Lookup token see if they have access to pull.) + + If good: + HTTP 200 OK + Index will invalidate the token + If bad: + HTTP 401 Unauthorized + +5. (Docker -> Registry) GET /v1/images/928374982374/ancestry + **Action**: + (for each image id returned in the registry, fetch /json + /layer) + +.. note:: + + If someone makes a second request, then we will always give a new token, never reuse tokens. + +2.2 Push +-------- + +.. image:: /static_files/docker_push_chart.png + +1. Contact the index to allocate the repository name “samalba/busybox” (authentication required with user credentials) +2. If authentication works and namespace available, “samalba/busybox” is allocated and a temporary token is returned (namespace is marked as initialized in index) +3. Push the image on the registry (along with the token) +4. Registry A contacts the Index to verify the token (token must corresponds to the repository name) +5. Index validates the token. Registry A starts reading the stream pushed by docker and store the repository (with its images) +6. docker contacts the index to give checksums for upload images + +.. note:: + + **It’s possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed. + +.. note:: + + **Index can be replaced!** For a private Registry deployed, a custom Index can be used to serve and validate token according to different policies. + +Docker computes the checksums and submit them to the Index at the end of the push. When a repository name does not have checksums on the Index, it means that the push is in progress (since checksums are submitted at the end). + +API (pushing repos foo/bar): +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. (Docker -> Index) PUT /v1/repositories/foo/bar/ + **Headers**: + Authorization: Basic sdkjfskdjfhsdkjfh== + X-Docker-Token: true + + **Action**:: + - in index, we allocated a new repository, and set to initialized + + **Body**:: + (The body contains the list of images that are going to be pushed, with empty checksums. The checksums will be set at the end of the push):: + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”}] + +2. (Index -> Docker) 200 Created + **Headers**: + - WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=write + - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] + +3. (Docker -> Registry) PUT /v1/images/98765432_parent/json + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=write + +4. (Registry->Index) GET /v1/repositories/foo/bar/images + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=write + **Action**:: + - Index: + will invalidate the token. + - Registry: + grants a session (if token is approved) and fetches the images id + +5. (Docker -> Registry) PUT /v1/images/98765432_parent/json + **Headers**:: + - Authorization: Token signature=123abc,repository=”foo/bar”,access=write + - Cookie: (Cookie provided by the Registry) + +6. (Docker -> Registry) PUT /v1/images/98765432/json + **Headers**: + Cookie: (Cookie provided by the Registry) + +7. (Docker -> Registry) PUT /v1/images/98765432_parent/layer + **Headers**: + Cookie: (Cookie provided by the Registry) + +8. (Docker -> Registry) PUT /v1/images/98765432/layer + **Headers**: + X-Docker-Checksum: sha256:436745873465fdjkhdfjkgh + +9. (Docker -> Registry) PUT /v1/repositories/foo/bar/tags/latest + **Headers**: + Cookie: (Cookie provided by the Registry) + **Body**: + “98765432” + +10. (Docker -> Index) PUT /v1/repositories/foo/bar/images + + **Headers**: + Authorization: Basic 123oislifjsldfj== + X-Docker-Endpoints: registry1.docker.io (no validation on this right now) + + **Body**: + (The image, id’s, tags and checksums) + + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, + “checksum”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] + + **Return** HTTP 204 + +.. note:: + + If push fails and they need to start again, what happens in the index, there will already be a record for the namespace/name, but it will be initialized. Should we allow it, or mark as name already used? One edge case could be if someone pushes the same thing at the same time with two different shells. + + If it's a retry on the Registry, Docker has a cookie (provided by the registry after token validation). So the Index won’t have to provide a new token. + +2.3 Delete +---------- + +If you need to delete something from the index or registry, we need a nice clean way to do that. Here is the workflow. + +1. Docker contacts the index to request a delete of a repository “samalba/busybox” (authentication required with user credentials) +2. If authentication works and repository is valid, “samalba/busybox” is marked as deleted and a temporary token is returned +3. Send a delete request to the registry for the repository (along with the token) +4. Registry A contacts the Index to verify the token (token must corresponds to the repository name) +5. Index validates the token. Registry A deletes the repository and everything associated to it. +6. docker contacts the index to let it know it was removed from the registry, the index removes all records from the database. + +.. note:: + + The Docker client should present an "Are you sure?" prompt to confirm the deletion before starting the process. Once it starts it can't be undone. + +API (deleting repository foo/bar): +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. (Docker -> Index) DELETE /v1/repositories/foo/bar/ + **Headers**: + Authorization: Basic sdkjfskdjfhsdkjfh== + X-Docker-Token: true + + **Action**:: + - in index, we make sure it is a valid repository, and set to deleted (logically) + + **Body**:: + Empty + +2. (Index -> Docker) 202 Accepted + **Headers**: + - WWW-Authenticate: Token signature=123abc,repository=”foo/bar”,access=delete + - X-Docker-Endpoints: registry.docker.io [, registry2.docker.io] # list of endpoints where this repo lives. + +3. (Docker -> Registry) DELETE /v1/repositories/foo/bar/ + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=delete + +4. (Registry->Index) PUT /v1/repositories/foo/bar/auth + **Headers**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=delete + **Action**:: + - Index: + will invalidate the token. + - Registry: + deletes the repository (if token is approved) + +5. (Registry -> Docker) 200 OK + 200 If success + 403 if forbidden + 400 if bad request + 404 if repository isn't found + +6. (Docker -> Index) DELETE /v1/repositories/foo/bar/ + + **Headers**: + Authorization: Basic 123oislifjsldfj== + X-Docker-Endpoints: registry-1.docker.io (no validation on this right now) + + **Body**: + Empty + + **Return** HTTP 200 + + +3. How to use the Registry in standalone mode +============================================= + +The Index has two main purposes (along with its fancy social features): + +- Resolve short names (to avoid passing absolute URLs all the time) + - username/projectname -> \https://registry.docker.io/users//repositories// + - team/projectname -> \https://registry.docker.io/team//repositories// +- Authenticate a user as a repos owner (for a central referenced repository) + +3.1 Without an Index +-------------------- +Using the Registry without the Index can be useful to store the images on a private network without having to rely on an external entity controlled by dotCloud. + +In this case, the registry will be launched in a special mode (--standalone? --no-index?). In this mode, the only thing which changes is that Registry will never contact the Index to verify a token. It will be the Registry owner responsibility to authenticate the user who pushes (or even pulls) an image using any mechanism (HTTP auth, IP based, etc...). + +In this scenario, the Registry is responsible for the security in case of data corruption since the checksums are not delivered by a trusted entity. + +As hinted previously, a standalone registry can also be implemented by any HTTP server handling GET/PUT requests (or even only GET requests if no write access is necessary). + +3.2 With an Index +----------------- + +The Index data needed by the Registry are simple: +- Serve the checksums +- Provide and authorize a Token + +In the scenario of a Registry running on a private network with the need of centralizing and authorizing, it’s easy to use a custom Index. + +The only challenge will be to tell Docker to contact (and trust) this custom Index. Docker will be configurable at some point to use a specific Index, it’ll be the private entity responsibility (basically the organization who uses Docker in a private environment) to maintain the Index and the Docker’s configuration among its consumers. + +4. The API +========== + +The first version of the api is available here: https://github.com/jpetazzo/docker/blob/acd51ecea8f5d3c02b00a08176171c59442df8b3/docs/images-repositories-push-pull.md + +4.1 Images +---------- + +The format returned in the images is not defined here (for layer and json), basically because Registry stores exactly the same kind of information as Docker uses to manage them. + +The format of ancestry is a line-separated list of image ids, in age order. I.e. the image’s parent is on the last line, the parent of the parent on the next-to-last line, etc.; if the image has no parent, the file is empty. + +GET /v1/images//layer +PUT /v1/images//layer +GET /v1/images//json +PUT /v1/images//json +GET /v1/images//ancestry +PUT /v1/images//ancestry + +4.2 Users +--------- + +4.2.1 Create a user (Index) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +POST /v1/users + +**Body**: + {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'} + +**Validation**: + - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. + - **password**: min 5 characters + +**Valid**: return HTTP 200 + +Errors: HTTP 400 (we should create error codes for possible errors) +- invalid json +- missing field +- wrong format (username, password, email, etc) +- forbidden name +- name already exists + +.. note:: + + A user account will be valid only if the email has been validated (a validation link is sent to the email address). + +4.2.2 Update a user (Index) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +PUT /v1/users/ + +**Body**: + {"password": "toto"} + +.. note:: + + We can also update email address, if they do, they will need to reverify their new email address. + +4.2.3 Login (Index) +^^^^^^^^^^^^^^^^^^^ +Does nothing else but asking for a user authentication. Can be used to validate credentials. HTTP Basic Auth for now, maybe change in future. + +GET /v1/users + +**Return**: + - Valid: HTTP 200 + - Invalid login: HTTP 401 + - Account inactive: HTTP 403 Account is not Active + +4.3 Tags (Registry) +------------------- + +The Registry does not know anything about users. Even though repositories are under usernames, it’s just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registry’s API. + +The following naming restrictions apply: + +- Namespaces must match the same regular expression as usernames (See 4.2.1.) +- Repository names must match the regular expression [a-zA-Z0-9-_.] + +4.3.1 Get all tags +^^^^^^^^^^^^^^^^^^ + +GET /v1/repositories///tags + +**Return**: HTTP 200 + { + "latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f", + “0.1.1”: “b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087” + } + +4.3.2 Read the content of a tag (resolve the image id) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +GET /v1/repositories///tags/ + +**Return**: + "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f" + +4.3.3 Delete a tag (registry) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +DELETE /v1/repositories///tags/ + +4.4 Images (Index) +------------------ + +For the Index to “resolve” the repository name to a Registry location, it uses the X-Docker-Endpoints header. In other terms, this requests always add a “X-Docker-Endpoints” to indicate the location of the registry which hosts this repository. + +4.4.1 Get the images +^^^^^^^^^^^^^^^^^^^^^ + +GET /v1/repositories///images + +**Return**: HTTP 200 + [{“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “md5:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”}] + + +4.4.2 Add/update the images +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You always add images, you never remove them. + +PUT /v1/repositories///images + +**Body**: + [ {“id”: “9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f”, “checksum”: “sha256:b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087”} ] + +**Return** 204 + +4.5 Repositories +---------------- + +4.5.1 Remove a Repository (Registry) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +DELETE /v1/repositories// + +Return 200 OK + +4.5.2 Remove a Repository (Index) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This starts the delete process. see 2.3 for more details. + +DELETE /v1/repositories// + +Return 202 OK + +5. Chaining Registries +====================== + +It’s possible to chain Registries server for several reasons: +- Load balancing +- Delegate the next request to another server + +When a Registry is a reference for a repository, it should host the entire images chain in order to avoid breaking the chain during the download. + +The Index and Registry use this mechanism to redirect on one or the other. + +Example with an image download: +On every request, a special header can be returned: + +X-Docker-Endpoints: server1,server2 + +On the next request, the client will always pick a server from this list. + +6. Authentication & Authorization +================================= + +6.1 On the Index +----------------- + +The Index supports both “Basic” and “Token” challenges. Usually when there is a “401 Unauthorized”, the Index replies this:: + + 401 Unauthorized + WWW-Authenticate: Basic realm="auth required",Token + +You have 3 options: + +1. Provide user credentials and ask for a token + + **Header**: + - Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + - X-Docker-Token: true + + In this case, along with the 200 response, you’ll get a new token (if user auth is ok): + If authorization isn't correct you get a 401 response. + If account isn't active you will get a 403 response. + + **Response**: + - 200 OK + - X-Docker-Token: Token signature=123abc,repository=”foo/bar”,access=read + +2. Provide user credentials only + + **Header**: + Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + +3. Provide Token + + **Header**: + Authorization: Token signature=123abc,repository=”foo/bar”,access=read + +6.2 On the Registry +------------------- + +The Registry only supports the Token challenge:: + + 401 Unauthorized + WWW-Authenticate: Token + +The only way is to provide a token on “401 Unauthorized” responses:: + + Authorization: Token signature=123abc,repository=”foo/bar”,access=read + +Usually, the Registry provides a Cookie when a Token verification succeeded. Every time the Registry passes a Cookie, you have to pass it back the same cookie.:: + + 200 OK + Set-Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4="; Path=/; HttpOnly + +Next request:: + + GET /(...) + Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4=" + + +7.0 Document Version +--------------------- + +- 1.0 : May 6th 2013 : initial release +- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. \ No newline at end of file diff --git a/components/engine/docs/sources/commandline/command/build.rst b/components/engine/docs/sources/commandline/command/build.rst index 81120b22d2..254b0371a9 100644 --- a/components/engine/docs/sources/commandline/command/build.rst +++ b/components/engine/docs/sources/commandline/command/build.rst @@ -19,10 +19,15 @@ Examples docker build . -This will take the local Dockerfile +| This will read the Dockerfile from the current directory. It will also send any other files and directories found in the current directory to the docker daemon. +| The contents of this directory would be used by ADD commands found within the Dockerfile. +| This will send a lot of data to the docker daemon if the current directory contains a lot of data. +| If the absolute path is provided instead of '.', only the files and directories required by the ADD commands from the Dockerfile will be added to the context and transferred to the docker daemon. +| .. code-block:: bash docker build - -This will read a Dockerfile form Stdin without context +| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon. +| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container. diff --git a/components/engine/docs/sources/contributing/contributing.rst b/components/engine/docs/sources/contributing/contributing.rst index 25b4df763a..1913cec30d 100644 --- a/components/engine/docs/sources/contributing/contributing.rst +++ b/components/engine/docs/sources/contributing/contributing.rst @@ -5,5 +5,5 @@ Contributing to Docker ====================== -Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started `. +Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started `_. diff --git a/components/engine/docs/sources/contributing/devenvironment.rst b/components/engine/docs/sources/contributing/devenvironment.rst index b5742c780a..5d937c5a44 100644 --- a/components/engine/docs/sources/contributing/devenvironment.rst +++ b/components/engine/docs/sources/contributing/devenvironment.rst @@ -5,11 +5,35 @@ Setting Up a Dev Environment ============================ -Instructions that have been verified to work on Ubuntu 12.10, +Instructions that have been verified to work on Ubuntu Precise 12.04 (LTS) (64-bit), + + +Dependencies +------------ + +**Linux kernel 3.8** + +Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in. + .. code-block:: bash - sudo apt-get -y install lxc wget bsdtar curl golang git + # install the backported kernel + sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring + + # reboot + sudo reboot + + +Installation +------------ + +.. code-block:: bash + + sudo apt-get install python-software-properties + sudo add-apt-repository ppa:gophers/go + sudo apt-get update + sudo apt-get -y install lxc wget bsdtar curl golang-stable git export GOPATH=~/go/ export PATH=$GOPATH/bin:$PATH diff --git a/components/engine/docs/sources/faq.rst b/components/engine/docs/sources/faq.rst index 901e51ddbb..12b9751338 100644 --- a/components/engine/docs/sources/faq.rst +++ b/components/engine/docs/sources/faq.rst @@ -19,7 +19,8 @@ Most frequently asked questions. 3. **Does Docker run on Mac OS X or Windows?** - Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ installation guides. + Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a + virtual machine on your box, and get the best of both worlds. Check out the :ref:`install_using_vagrant` and :ref:`windows` installation guides. 4. **How do containers compare to virtual machines?** @@ -34,15 +35,16 @@ Most frequently asked questions. You can find more answers on: - * `IRC: docker on freenode`_ + * `Docker club mailinglist`_ + * `IRC, docker on freenode`_ * `Github`_ * `Ask questions on Stackoverflow`_ * `Join the conversation on Twitter`_ - .. _Windows: ../installation/windows/ - .. _MacOSX: ../installation/vagrant/ + + .. _Docker club mailinglist: https://groups.google.com/d/forum/docker-club .. _the repo: http://www.github.com/dotcloud/docker - .. _IRC\: docker on freenode: irc://chat.freenode.net#docker + .. _IRC, docker on freenode: irc://chat.freenode.net#docker .. _Github: http://www.github.com/dotcloud/docker .. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker .. _Join the conversation on Twitter: http://twitter.com/getdocker diff --git a/components/engine/docs/sources/installation/ubuntulinux.rst b/components/engine/docs/sources/installation/ubuntulinux.rst index 1fb1ce5299..ed592d3a9d 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.rst +++ b/components/engine/docs/sources/installation/ubuntulinux.rst @@ -92,6 +92,16 @@ have AUFS filesystem support enabled, so we need to install it. sudo apt-get update sudo apt-get install linux-image-extra-`uname -r` +**add-apt-repository support** + +Some installations of Ubuntu 13.04 require ``software-properties-common`` to be +installed before being able to use add-apt-repository. + +.. code-block:: bash + + sudo apt-get install software-properties-common + + Installation ------------ diff --git a/components/engine/docs/sources/installation/windows.rst b/components/engine/docs/sources/installation/windows.rst index 230ac78051..7830106020 100644 --- a/components/engine/docs/sources/installation/windows.rst +++ b/components/engine/docs/sources/installation/windows.rst @@ -2,6 +2,7 @@ :description: Docker's tutorial to run docker on Windows :keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin +.. _windows: Using Vagrant (Windows) ======================= diff --git a/components/engine/docs/sources/use/builder.rst b/components/engine/docs/sources/use/builder.rst index abd5b9ecb1..f2e9ce97ce 100644 --- a/components/engine/docs/sources/use/builder.rst +++ b/components/engine/docs/sources/use/builder.rst @@ -18,7 +18,7 @@ steps and commit them along the way, giving you a final image. To use Docker Builder, assemble the steps into a text file (commonly referred to as a Dockerfile) and supply this to `docker build` on STDIN, like so: - ``docker build < Dockerfile`` + ``docker build - < Dockerfile`` Docker will run your steps one-by-one, committing the result if necessary, before finally outputting the ID of your new image. diff --git a/components/engine/docs/theme/docker/layout.html b/components/engine/docs/theme/docker/layout.html index d212c9ca86..baaaec9155 100755 --- a/components/engine/docs/theme/docker/layout.html +++ b/components/engine/docs/theme/docker/layout.html @@ -64,14 +64,15 @@
@@ -86,8 +87,13 @@
-

DOCUMENTATION

+
+ +

DOCUMENTATION

+
@@ -123,8 +129,14 @@