first version of Pull

This commit is contained in:
Victor Vieux 2013-05-24 14:43:52 +00:00
commit 3c7bca7a21
14 changed files with 251 additions and 214 deletions

View file

@ -1,5 +1,9 @@
# Changelog # Changelog
## 0.3.3 (2013-05-23)
- Registry: Fix push regression
- Various bugfixes
## 0.3.2 (2013-05-09) ## 0.3.2 (2013-05-09)
* Runtime: Store the actual archive on commit * Runtime: Store the actual archive on commit
* Registry: Improve the checksum process * Registry: Improve the checksum process

90
api.go
View file

@ -13,6 +13,8 @@ import (
"strings" "strings"
) )
const API_VERSION = 1.1
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack() conn, _, err := w.(http.Hijacker).Hijack()
if err != nil { if err != nil {
@ -56,8 +58,8 @@ func getBoolParam(value string) (bool, error) {
return false, fmt.Errorf("Bad parameter") return false, fmt.Errorf("Bad parameter")
} }
func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
b, err := json.Marshal(srv.registry.GetAuthConfig()) b, err := json.Marshal(srv.registry.GetAuthConfig(false))
if err != nil { if err != nil {
return err return err
} }
@ -65,14 +67,14 @@ func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[strin
return nil return nil
} }
func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
config := &auth.AuthConfig{} config := &auth.AuthConfig{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil { if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return err return err
} }
authConfig := srv.registry.GetAuthConfig(true)
if config.Username == srv.registry.GetAuthConfig().Username { if config.Username == authConfig.Username {
config.Password = srv.registry.GetAuthConfig().Password config.Password = authConfig.Password
} }
newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root)
@ -94,7 +96,7 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri
return nil return nil
} }
func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
m := srv.DockerVersion() m := srv.DockerVersion()
b, err := json.Marshal(m) b, err := json.Marshal(m)
if err != nil { if err != nil {
@ -104,7 +106,7 @@ func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[st
return nil return nil
} }
func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -116,7 +118,7 @@ func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, var
return nil return nil
} }
func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -129,7 +131,7 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, va
return nil return nil
} }
func getImagesJson(srv *Server, 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 { if err := parseForm(r); err != nil {
return err return err
} }
@ -152,14 +154,14 @@ func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map
return nil return nil
} }
func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := srv.ImagesViz(w); err != nil { if err := srv.ImagesViz(w); err != nil {
return err return err
} }
return nil return nil
} }
func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
out := srv.DockerInfo() out := srv.DockerInfo()
b, err := json.Marshal(out) b, err := json.Marshal(out)
if err != nil { if err != nil {
@ -169,7 +171,7 @@ func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[strin
return nil return nil
} }
func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -186,7 +188,7 @@ func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars
return nil return nil
} }
func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -203,7 +205,7 @@ func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, v
return nil return nil
} }
func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getContainersPs(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -227,7 +229,7 @@ func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars m
return nil return nil
} }
func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -249,7 +251,7 @@ func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map
return nil return nil
} }
func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -276,7 +278,7 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[st
} }
// Creates an image from Pull or from Import // Creates an image from Pull or from Import
func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -288,8 +290,10 @@ func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars
if image != "" { //pull if image != "" { //pull
registry := r.Form.Get("registry") registry := r.Form.Get("registry")
w.Header().Set("Content-Type", "application/json") if version > 1.0 {
if err := srv.ImagePull(image, tag, registry, w); err != nil { w.Header().Set("Content-Type", "application/json")
}
if err := srv.ImagePull(image, tag, registry, w, version > 1.0); err != nil {
return err return err
} }
} else { //import } else { //import
@ -300,7 +304,7 @@ func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars
return nil return nil
} }
func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -318,7 +322,7 @@ func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars m
return nil return nil
} }
func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -336,7 +340,7 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars
return nil return nil
} }
func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -353,7 +357,7 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars ma
return nil return nil
} }
func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
config := &Config{} config := &Config{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil { if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return err return err
@ -383,7 +387,7 @@ func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, v
return nil return nil
} }
func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -402,7 +406,7 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request,
return nil return nil
} }
func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -422,7 +426,7 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars
return nil return nil
} }
func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -434,7 +438,7 @@ func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[
return nil return nil
} }
func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -446,7 +450,7 @@ func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, va
return nil return nil
} }
func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -467,7 +471,7 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, var
return nil return nil
} }
func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -484,7 +488,7 @@ func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, var
return nil return nil
} }
func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -527,7 +531,7 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, v
return nil return nil
} }
func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -545,7 +549,7 @@ func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, va
return nil return nil
} }
func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
@ -563,7 +567,7 @@ func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars m
return nil return nil
} }
func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 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 { if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
return err return err
@ -590,7 +594,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
r := mux.NewRouter() r := mux.NewRouter()
log.Printf("Listening for HTTP on %s\n", addr) log.Printf("Listening for HTTP on %s\n", addr)
m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request, map[string]string) error{ m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
"GET": { "GET": {
"/auth": getAuth, "/auth": getAuth,
"/version": getVersion, "/version": getVersion,
@ -634,7 +638,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
localRoute := route localRoute := route
localMethod := method localMethod := method
localFct := fct localFct := fct
r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { f := func(w http.ResponseWriter, r *http.Request) {
utils.Debugf("Calling %s %s", localMethod, localRoute) utils.Debugf("Calling %s %s", localMethod, localRoute)
if logging { if logging {
log.Println(r.Method, r.RequestURI) log.Println(r.Method, r.RequestURI)
@ -645,10 +649,20 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION) utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
} }
} }
if err := localFct(srv, w, r, mux.Vars(r)); err != nil { version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
if err != nil {
version = API_VERSION
}
if version == 0 || version > API_VERSION {
w.WriteHeader(http.StatusNotFound)
return
}
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
httpError(w, err) httpError(w, err)
} }
}) }
r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
} }
} }

View file

@ -48,7 +48,7 @@ func TestGetAuth(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err := postAuth(srv, r, req, nil); err != nil { if err := postAuth(srv, API_VERSION, r, req, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -74,7 +74,7 @@ func TestGetVersion(t *testing.T) {
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getVersion(srv, r, nil, nil); err != nil { if err := getVersion(srv, API_VERSION, r, nil, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -98,7 +98,7 @@ func TestGetInfo(t *testing.T) {
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getInfo(srv, r, nil, nil); err != nil { if err := getInfo(srv, API_VERSION, r, nil, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -129,7 +129,7 @@ func TestGetImagesJson(t *testing.T) {
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getImagesJson(srv, r, req, nil); err != nil { if err := getImagesJson(srv, API_VERSION, r, req, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -154,7 +154,7 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err := getImagesJson(srv, r2, req2, nil); err != nil { if err := getImagesJson(srv, API_VERSION, r2, req2, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -179,7 +179,7 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err := getImagesJson(srv, r3, req3, nil); err != nil { if err := getImagesJson(srv, API_VERSION, r3, req3, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -200,7 +200,7 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
err = getImagesJson(srv, r4, req4, nil) err = getImagesJson(srv, API_VERSION, r4, req4, nil)
if err == nil { if err == nil {
t.Fatalf("Error expected, received none") t.Fatalf("Error expected, received none")
} }
@ -221,7 +221,7 @@ func TestGetImagesViz(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getImagesViz(srv, r, nil, nil); err != nil { if err := getImagesViz(srv, API_VERSION, r, nil, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -258,7 +258,7 @@ func TestGetImagesSearch(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err := getImagesSearch(srv, r, req, nil); err != nil { if err := getImagesSearch(srv, API_VERSION, r, req, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -282,7 +282,7 @@ func TestGetImagesHistory(t *testing.T) {
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getImagesHistory(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil { if err := getImagesHistory(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -305,7 +305,7 @@ func TestGetImagesByName(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getImagesByName(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil { if err := getImagesByName(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -342,7 +342,7 @@ func TestGetContainersPs(t *testing.T) {
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getContainersPs(srv, r, req, nil); err != nil { if err := getContainersPs(srv, API_VERSION, r, req, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
containers := []ApiContainers{} containers := []ApiContainers{}
@ -385,7 +385,7 @@ func TestGetContainersExport(t *testing.T) {
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err = getContainersExport(srv, r, nil, map[string]string{"name": container.Id}); err != nil { if err = getContainersExport(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -440,7 +440,7 @@ func TestGetContainersChanges(t *testing.T) {
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getContainersChanges(srv, r, nil, map[string]string{"name": container.Id}); err != nil { if err := getContainersChanges(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
changes := []Change{} changes := []Change{}
@ -484,7 +484,7 @@ func TestGetContainersByName(t *testing.T) {
defer runtime.Destroy(container) defer runtime.Destroy(container)
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getContainersByName(srv, r, nil, map[string]string{"name": container.Id}); err != nil { if err := getContainersByName(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
outContainer := &Container{} outContainer := &Container{}
@ -515,7 +515,7 @@ func TestPostAuth(t *testing.T) {
srv.registry.ResetClient(authConfigOrig) srv.registry.ResetClient(authConfigOrig)
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := getAuth(srv, r, nil, nil); err != nil { if err := getAuth(srv, API_VERSION, r, nil, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -562,7 +562,7 @@ func TestPostCommit(t *testing.T) {
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := postCommit(srv, r, req, nil); err != nil { if err := postCommit(srv, API_VERSION, r, req, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if r.Code != http.StatusCreated { if r.Code != http.StatusCreated {
@ -840,7 +840,7 @@ func TestPostContainersCreate(t *testing.T) {
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := postContainersCreate(srv, r, req, nil); err != nil { if err := postContainersCreate(srv, API_VERSION, r, req, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if r.Code != http.StatusCreated { if r.Code != http.StatusCreated {
@ -903,7 +903,7 @@ func TestPostContainersKill(t *testing.T) {
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id}); err != nil { if err := postContainersKill(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if r.Code != http.StatusNoContent { if r.Code != http.StatusNoContent {
@ -951,7 +951,7 @@ func TestPostContainersRestart(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := postContainersRestart(srv, r, req, map[string]string{"name": container.Id}); err != nil { if err := postContainersRestart(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if r.Code != http.StatusNoContent { if r.Code != http.StatusNoContent {
@ -992,7 +992,7 @@ func TestPostContainersStart(t *testing.T) {
defer runtime.Destroy(container) defer runtime.Destroy(container)
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err != nil { if err := postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if r.Code != http.StatusNoContent { if r.Code != http.StatusNoContent {
@ -1007,7 +1007,7 @@ func TestPostContainersStart(t *testing.T) {
} }
r = httptest.NewRecorder() r = httptest.NewRecorder()
if err = postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err == nil { if err = postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err == nil {
t.Fatalf("A running containter should be able to be started") t.Fatalf("A running containter should be able to be started")
} }
@ -1054,7 +1054,7 @@ func TestPostContainersStop(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := postContainersStop(srv, r, req, map[string]string{"name": container.Id}); err != nil { if err := postContainersStop(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if r.Code != http.StatusNoContent { if r.Code != http.StatusNoContent {
@ -1092,7 +1092,7 @@ func TestPostContainersWait(t *testing.T) {
setTimeout(t, "Wait timed out", 3*time.Second, func() { setTimeout(t, "Wait timed out", 3*time.Second, func() {
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := postContainersWait(srv, r, nil, map[string]string{"name": container.Id}); err != nil { if err := postContainersWait(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
apiWait := &ApiWait{} apiWait := &ApiWait{}
@ -1154,7 +1154,7 @@ func TestPostContainersAttach(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id}); err != nil { if err := postContainersAttach(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}() }()
@ -1224,7 +1224,7 @@ func TestDeleteContainers(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
r := httptest.NewRecorder() r := httptest.NewRecorder()
if err := deleteContainers(srv, r, req, map[string]string{"name": container.Id}); err != nil { if err := deleteContainers(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if r.Code != http.StatusNoContent { if r.Code != http.StatusNoContent {

View file

@ -32,12 +32,6 @@ type builderClient struct {
} }
func (b *builderClient) clearTmp(containers, images map[string]struct{}) { func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
for c := range containers {
if _, _, err := b.cli.call("DELETE", "/containers/"+c, nil); err != nil {
utils.Debugf("%s", err)
}
utils.Debugf("Removing container %s", c)
}
for i := range images { for i := range images {
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil { if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
utils.Debugf("%s", err) utils.Debugf("%s", err)

View file

@ -24,7 +24,7 @@ import (
"unicode" "unicode"
) )
const VERSION = "0.3.2" const VERSION = "0.3.3"
var ( var (
GIT_COMMIT string GIT_COMMIT string
@ -1167,7 +1167,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf) params = bytes.NewBuffer(buf)
} }
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d", cli.host, cli.port)+path, params) req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%f", cli.host, cli.port, API_VERSION)+path, params)
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
} }
@ -1199,7 +1199,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if (method == "POST" || method == "PUT") && in == nil { if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{}) in = bytes.NewReader([]byte{})
} }
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, path), in) req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%f%s", cli.host, cli.port, API_VERSION, path), in)
if err != nil { if err != nil {
return err return err
} }
@ -1224,7 +1224,6 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
} }
if resp.Header.Get("Content-Type") == "application/json" { if resp.Header.Get("Content-Type") == "application/json" {
type Message struct { type Message struct {
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"` Progress string `json:"progress,omitempty"`
@ -1237,13 +1236,12 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
} else if err != nil { } else if err != nil {
return err return err
} }
if m.Status != "" { if m.Progress != "" {
fmt.Fprintf(out, "Downloading %s\r", m.Progress)
} else {
fmt.Fprintf(out, "%s\n", m.Status) fmt.Fprintf(out, "%s\n", m.Status)
} else if m.Progress != "" {
fmt.Fprintf(out, "Downloading... %s\r", m.Progress)
} }
} }
fmt.Fprintf(out, "\n")
} else { } else {
if _, err := io.Copy(out, resp.Body); err != nil { if _, err := io.Copy(out, resp.Body); err != nil {
return err return err
@ -1253,7 +1251,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
} }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error { func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
req, err := http.NewRequest(method, path, nil) req, err := http.NewRequest(method, fmt.Sprintf("/v%f%s", API_VERSION, path), nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -23,5 +23,5 @@
-t=false: Allocate a pseudo-tty -t=false: Allocate a pseudo-tty
-u="": Username or UID -u="": Username or UID
-d=[]: Set custom dns servers for the container -d=[]: Set custom dns servers for the container
-v=[]: Creates a new volumes and mount it at the specified path. -v=[]: Creates a new volume and mounts it at the specified path.
-volumes-from="": Mount all volumes from the given container. -volumes-from="": Mount all volumes from the given container.

View file

@ -7,61 +7,29 @@
Kernel Requirements Kernel Requirements
=================== ===================
In short, Docker has the following kernel requirements:
- Linux version 3.8 or above.
- `AUFS support <http://aufs.sourceforge.net/>`_.
- Cgroups and namespaces must be enabled.
The officially supported kernel is the one recommended by the The officially supported kernel is the one recommended by the
:ref:`ubuntu_linux` installation path. It is the one that most developers :ref:`ubuntu_linux` installation path. It is the one that most developers
will use, and the one that receives the most attention from the core will use, and the one that receives the most attention from the core
contributors. If you decide to go with a different kernel and hit a bug, contributors. If you decide to go with a different kernel and hit a bug,
please try to reproduce it with the official kernels first. please try to reproduce it with the official kernels first.
If for some reason you cannot or do not want to use the "official" kernels, If you cannot or do not want to use the "official" kernels,
here is some technical background about the features (both optional and here is some technical background about the features (both optional and
mandatory) that docker needs to run successfully. mandatory) that docker needs to run successfully.
In short, you need kernel version 3.8 (or above), compiled to include Linux version 3.8 or above
`AUFS support <http://aufs.sourceforge.net/>`_. Of course, you need to --------------------------
enable cgroups and namespaces.
Kernel versions 3.2 to 3.5 are not stable when used with docker.
Namespaces and Cgroups
----------------------
You need to enable namespaces and cgroups, to the extend of what is needed
to run LXC containers. Technically, while namespaces have been introduced
in the early 2.6 kernels, we do not advise to try any kernel before 2.6.32
to run LXC containers. Note that 2.6.32 has some documented issues regarding
network namespace setup and teardown; those issues are not a risk if you
run containers in a private environment, but can lead to denial-of-service
attacks if you want to run untrusted code in your containers. For more details,
see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
Kernels 2.6.38, and every version since 3.2, have been deployed successfully
to run containerized production workloads. Feature-wise, there is no huge
improvement between 2.6.38 and up to 3.6 (as far as docker is concerned!).
Starting with version 3.7, the kernel has basic support for
`Checkpoint/Restore In Userspace <http://criu.org/>`_, which is not used by
docker at this point, but allows to suspend the state of a container to
disk and resume it later.
Version 3.8 provides improvements in stability, which are deemed necessary
for the operation of docker. Versions 3.2 to 3.5 have been shown to
exhibit a reproducible bug (for more details, see issue
`#407 <https://github.com/dotcloud/docker/issues/407>`_).
Version 3.8 also brings better support for the
`setns() syscall <http://lwn.net/Articles/531381/>`_ -- but this should not
be a concern since docker does not leverage on this feature for now.
If you want a technical overview about those concepts, you might
want to check those articles on dotCloud's blog:
`about namespaces <http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part>`_
and `about cgroups <http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c>`_.
Important Note About Pre-3.8 Kernels
------------------------------------
As mentioned above, kernels before 3.8 are not stable when used with docker.
In some circumstances, you will experience kernel "oopses", or even crashes. In some circumstances, you will experience kernel "oopses", or even crashes.
The symptoms include: The symptoms include:
@ -81,6 +49,36 @@ detects something older than 3.8.
See issue `#407 <https://github.com/dotcloud/docker/issues/407>`_ for details. See issue `#407 <https://github.com/dotcloud/docker/issues/407>`_ for details.
AUFS support
------------
Docker currently relies on AUFS, an unioning filesystem.
While AUFS is included in the kernels built by the Debian and Ubuntu
distributions, is not part of the standard kernel. This means that if
you decide to roll your own kernel, you will have to patch your
kernel tree to add AUFS. The process is documented on
`AUFS webpage <http://aufs.sourceforge.net/>`_.
Cgroups and namespaces
----------------------
You need to enable namespaces and cgroups, to the extend of what is needed
to run LXC containers. Technically, while namespaces have been introduced
in the early 2.6 kernels, we do not advise to try any kernel before 2.6.32
to run LXC containers. Note that 2.6.32 has some documented issues regarding
network namespace setup and teardown; those issues are not a risk if you
run containers in a private environment, but can lead to denial-of-service
attacks if you want to run untrusted code in your containers. For more details,
see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
Kernels 2.6.38, and every version since 3.2, have been deployed successfully
to run containerized production workloads. Feature-wise, there is no huge
improvement between 2.6.38 and up to 3.6 (as far as docker is concerned!).
Extra Cgroup Controllers Extra Cgroup Controllers
------------------------ ------------------------
@ -115,39 +113,3 @@ And replace it by the following one::
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount" GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount"
Then run ``update-grub``, and reboot. Then run ``update-grub``, and reboot.
AUFS
----
Docker currently relies on AUFS, an unioning filesystem.
While AUFS is included in the kernels built by the Debian and Ubuntu
distributions, is not part of the standard kernel. This means that if
you decide to roll your own kernel, you will have to patch your
kernel tree to add AUFS. The process is documented on
`AUFS webpage <http://aufs.sourceforge.net/>`_.
Note: the AUFS patch is fairly intrusive, but for the record, people have
successfully applied GRSEC and AUFS together, to obtain hardened production
kernels.
If you want more information about that topic, there is an
`article about AUFS on dotCloud's blog
<http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-34-a>`_.
BTRFS, ZFS, OverlayFS...
------------------------
There is ongoing development on docker, to implement support for
`BTRFS <http://en.wikipedia.org/wiki/Btrfs>`_
(see github issue `#443 <https://github.com/dotcloud/docker/issues/443>`_).
People have also showed interest for `ZFS <http://en.wikipedia.org/wiki/ZFS>`_
(using e.g. `ZFS-on-Linux <http://zfsonlinux.org/>`_) and OverlayFS.
The latter is functionally close to AUFS, and it might end up being included
in the stock kernel; so it's a strong candidate!
Would you like to `contribute
<https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_
support for your favorite filesystem?

View file

@ -198,6 +198,35 @@
<div class="container"> <div class="container">
<div class="row">
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/2707460527/252a64411a339184ff375a96fb68dcb0_bigger.png">
<em>Mitchell Hashimoto@mitchellh:</em> Docker launched today. It is incredible. Theyre also working RIGHT NOW on a Vagrant provider. LXC is COMING!!
</section>
</div>
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/1108290260/Adam_Jacob-114x150_original_bigger.jpg">
<em>Adam Jacob@adamhjk:</em> Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think.
</section>
</div>
</div>
<div class="row">
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/14872832/twitter_pic_bigger.jpg">
<em>Matt Townsend@mtownsend:</em> I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego.
</section>
</div>
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/1312352395/rupert-259x300_bigger.jpg">
<em>Rob Harrop@robertharrop:</em> Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter.
</section>
</div>
</div>
<div class="row"> <div class="row">
<div class="span6"> <div class="span6">
<section class="contentblock twitterblock"> <section class="contentblock twitterblock">

View file

@ -165,7 +165,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root) return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root)
} }
// Mktemp creates a temporary sub-directory inside the graph's filesystem. // Mktemp creates a temporary sub-directory inside the graph's filesystem.

View file

@ -1,37 +1,44 @@
lxc-docker (0.3.3-1) precise; urgency=low
- Registry: Fix push regression
- Various bugfixes
-- dotCloud <ops@dotcloud.com> Thu, 23 May 2013 00:00:00 -0700
lxc-docker (0.3.2-1) precise; urgency=low lxc-docker (0.3.2-1) precise; urgency=low
- Runtime: Store the actual archive on commit - Runtime: Store the actual archive on commit
- Registry: Improve the checksum process - Registry: Improve the checksum process
- Registry: Use the size to have a good progress bar while pushing - Registry: Use the size to have a good progress bar while pushing
- Registry: Use the actual archive if it exists in order to speed up the push - Registry: Use the actual archive if it exists in order to speed up the push
- Registry: Fix error 400 on push - Registry: Fix error 400 on push
-- dotCloud <ops@dotcloud.com> Fri, 9 May 2013 00:00:00 -0700 -- dotCloud <ops@dotcloud.com> Fri, 9 May 2013 00:00:00 -0700
lxc-docker (0.3.1-1) precise; urgency=low lxc-docker (0.3.1-1) precise; urgency=low
- Builder: Implement the autorun capability within docker builder - Builder: Implement the autorun capability within docker builder
- Builder: Add caching to docker builder - Builder: Add caching to docker builder
- Builder: Add support for docker builder with native API as top level command - Builder: Add support for docker builder with native API as top level command
- Runtime: Add go version to debug infos - Runtime: Add go version to debug infos
- Builder: Implement ENV within docker builder - Builder: Implement ENV within docker builder
- Registry: Add docker search top level command in order to search a repository - Registry: Add docker search top level command in order to search a repository
- Images: output graph of images to dot (graphviz) - Images: output graph of images to dot (graphviz)
- Documentation: new introduction and high-level overview - Documentation: new introduction and high-level overview
- Documentation: Add the documentation for docker builder - Documentation: Add the documentation for docker builder
- Website: new high-level overview - Website: new high-level overview
- Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc - Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
- Images: fix ByParent function - Images: fix ByParent function
- Builder: Check the command existance prior create and add Unit tests for the case - Builder: Check the command existance prior create and add Unit tests for the case
- Registry: Fix pull for official images with specific tag - Registry: Fix pull for official images with specific tag
- Registry: Fix issue when login in with a different user and trying to push - Registry: Fix issue when login in with a different user and trying to push
- Documentation: CSS fix for docker documentation to make REST API docs look better. - Documentation: CSS fix for docker documentation to make REST API docs look better.
- Documentation: Fixed CouchDB example page header mistake - Documentation: Fixed CouchDB example page header mistake
- Documentation: fixed README formatting - Documentation: fixed README formatting
- Registry: Improve checksum - async calculation - Registry: Improve checksum - async calculation
- Runtime: kernel version - don't show the dash if flavor is empty - Runtime: kernel version - don't show the dash if flavor is empty
- Documentation: updated www.docker.io website. - Documentation: updated www.docker.io website.
- Builder: use any whitespaces instead of tabs - Builder: use any whitespaces instead of tabs
- Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker - Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
-- dotCloud <ops@dotcloud.com> Fri, 8 May 2013 00:00:00 -0700 -- dotCloud <ops@dotcloud.com> Fri, 8 May 2013 00:00:00 -0700

View file

@ -428,9 +428,14 @@ func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
r.client.Jar = cookiejar.NewCookieJar() r.client.Jar = cookiejar.NewCookieJar()
} }
func (r *Registry) GetAuthConfig() *auth.AuthConfig { func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
password := ""
if withPasswd {
password = r.authConfig.Password
}
return &auth.AuthConfig{ return &auth.AuthConfig{
Username: r.authConfig.Username, Username: r.authConfig.Username,
Password: password,
Email: r.authConfig.Email, Email: r.authConfig.Email,
} }
} }

View file

@ -91,7 +91,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error {
return err return err
} }
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil { if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
return err return err
} }
// FIXME: Handle custom repo, tag comment, author // FIXME: Handle custom repo, tag comment, author
@ -291,16 +291,17 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
return nil return nil
} }
func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []string) error { func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []string, json bool) error {
history, err := srv.registry.GetRemoteHistory(imgId, registry, token) history, err := srv.registry.GetRemoteHistory(imgId, registry, token)
if err != nil { if err != nil {
return err return err
} }
// FIXME: Try to stream the images? // FIXME: Try to stream the images?
// FIXME: Launch the getRemoteImage() in goroutines // FIXME: Launch the getRemoteImage() in goroutines
for _, id := range history { for _, id := range history {
if !srv.runtime.graph.Exists(id) { if !srv.runtime.graph.Exists(id) {
fmt.Fprintf(out, "{\"status\" :\"Pulling %s metadata\"}", id) fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
imgJson, err := srv.registry.GetRemoteImageJson(id, registry, token) imgJson, err := srv.registry.GetRemoteImageJson(id, registry, token)
if err != nil { if err != nil {
// FIXME: Keep goging in case of error? // FIXME: Keep goging in case of error?
@ -312,12 +313,12 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
} }
// Get the layer // Get the layer
fmt.Fprintf(out, "{\"status\" :\"Pulling %s fs layer\"}", img.Id) fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
layer, contentLength, err := srv.registry.GetRemoteImageLayer(img.Id, registry, token) layer, contentLength, err := srv.registry.GetRemoteImageLayer(img.Id, registry, token)
if err != nil { if err != nil {
return err return err
} }
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, ""), false, img); err != nil { if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
return err return err
} }
} }
@ -325,8 +326,8 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
return nil return nil
} }
func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error { func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json bool) error {
fmt.Fprintf(out, "{\"status\":\"Pulling repository %s from %s\"}", remote, auth.IndexServerAddress()) fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
repoData, err := srv.registry.GetRepositoryData(remote) repoData, err := srv.registry.GetRepositoryData(remote)
if err != nil { if err != nil {
return err return err
@ -363,11 +364,11 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id) utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
continue continue
} }
fmt.Fprintf(out, "{\"status\":\"Pulling image %s (%s) from %s\"}", img.Id, img.Tag, remote) fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
success := false success := false
for _, ep := range repoData.Endpoints { for _, ep := range repoData.Endpoints {
if err := srv.pullImage(out, img.Id, "https://"+ep+"/v1", repoData.Tokens); err != nil { if err := srv.pullImage(out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
fmt.Fprintf(out, "Error while retrieving image for tag: %s (%s); checking next endpoint\n", askedTag, err) fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
continue continue
} }
success = true success = true
@ -392,18 +393,19 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
return nil return nil
} }
func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error { func (srv *Server) ImagePull(name, tag, registry string, out io.Writer, json bool) error {
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
if registry != "" { if registry != "" {
if err := srv.pullImage(out, name, registry, nil); err != nil { if err := srv.pullImage(out, name, registry, nil, json); err != nil {
return err return err
} }
return nil return nil
} }
if err := srv.pullRepository(out, name, tag); err != nil { if err := srv.pullRepository(out, name, tag, json); err != nil {
return err return err
} }
return nil return nil
} }
@ -567,7 +569,7 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st
} }
// Send the layer // Send the layer
if err := srv.registry.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, ""), ep, token); err != nil { if err := srv.registry.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
return err return err
} }
return nil return nil
@ -618,7 +620,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
if err != nil { if err != nil {
return err return err
} }
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)") archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false)
} }
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
if err != nil { if err != nil {

View file

@ -49,10 +49,10 @@ func CompareConfig(a, b *Config) bool {
} }
func MergeConfig(userConf, imageConf *Config) { func MergeConfig(userConf, imageConf *Config) {
if userConf.Hostname != "" { if userConf.Hostname == "" {
userConf.Hostname = imageConf.Hostname userConf.Hostname = imageConf.Hostname
} }
if userConf.User != "" { if userConf.User == "" {
userConf.User = imageConf.User userConf.User = imageConf.User
} }
if userConf.Memory == 0 { if userConf.Memory == 0 {

View file

@ -69,6 +69,7 @@ type progressReader struct {
readProgress int // How much has been read so far (bytes) readProgress int // How much has been read so far (bytes)
lastUpdate int // How many bytes read at least update lastUpdate int // How many bytes read at least update
template string // Template to print. Default "%v/%v (%v)" template string // Template to print. Default "%v/%v (%v)"
json bool
} }
func (r *progressReader) Read(p []byte) (n int, err error) { func (r *progressReader) Read(p []byte) (n int, err error) {
@ -84,22 +85,27 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
} }
if r.readProgress-r.lastUpdate > updateEvery || err != nil { if r.readProgress-r.lastUpdate > updateEvery || err != nil {
if r.readTotal > 0 { if r.readTotal > 0 {
fmt.Fprintf(r.output, r.template, r.readProgress, r.readTotal) fmt.Fprintf(r.output, r.template, r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
} else { } else {
fmt.Fprintf(r.output, r.template, r.readProgress, "?") fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
} }
r.lastUpdate = r.readProgress r.lastUpdate = r.readProgress
} }
// Send newline when complete
if err != nil {
fmt.Fprintf(r.output, FormatStatus("", r.json))
}
return read, err return read, err
} }
func (r *progressReader) Close() error { func (r *progressReader) Close() error {
return io.ReadCloser(r.reader).Close() return io.ReadCloser(r.reader).Close()
} }
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader { func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
if template == "" { if template == "" {
template = "{\"progress\":\"%v/%v\"}" template = "%v/%v (%v)\r"
} }
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template} return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
} }
// HumanDuration returns a human-readable approximation of a duration // HumanDuration returns a human-readable approximation of a duration
@ -550,3 +556,19 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
} }
return &WriteFlusher{w: w, flusher: flusher} return &WriteFlusher{w: w, flusher: flusher}
} }
func FormatStatus(str string, json bool) string {
if json {
return "{\"status\" : \"" + str + "\"}"
}
return str + "\r\n"
}
func FormatProgress(str string, json bool) string {
if json {
return "{\"progress\" : \"" + str + "\"}"
}
return "Downloading " + str + "\r"
}