first version of Pull
This commit is contained in:
commit
3c7bca7a21
14 changed files with 251 additions and 214 deletions
|
@ -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
90
api.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
52
api_test.go
52
api_test.go
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
16
commands.go
16
commands.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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?
|
|
||||||
|
|
|
@ -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. They’re 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">
|
||||||
|
|
2
graph.go
2
graph.go
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
server.go
32
server.go
|
@ -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 {
|
||||||
|
|
4
utils.go
4
utils.go
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue