bump to master again

This commit is contained in:
Victor Vieux 2013-06-05 16:01:36 +00:00
commit bf63cb9045
67 changed files with 1430 additions and 983 deletions

View file

@ -15,6 +15,7 @@ Brian McCallister <brianm@skife.org>
Bruno Bigras <bigras.bruno@gmail.com>
Caleb Spare <cespare@gmail.com>
Charles Hooper <charles.hooper@dotcloud.com>
Daniel Gasienica <daniel@gasienica.ch>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
Daniel Robinson <gottagetmac@gmail.com>
Daniel Von Fange <daniel@leancoder.com>

View file

@ -1,5 +1,19 @@
# Changelog
## 0.4.0 (2013-06-03)
+ Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
+ Introducing Remote API: control Docker programmatically using a simple HTTP/json API
* Runtime: various reliability and usability improvements
## 0.3.4 (2013-05-30)
+ Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
+ Runtime: interactive TTYs correctly handle window resize
* Runtime: fix how configuration is merged between layers
+ Remote API: split stdout and stderr on 'docker run'
+ Remote API: optionally listen on a different IP and port (use at your own risk)
* Documentation: improved install instructions.
## 0.3.3 (2013-05-23)
- Registry: Fix push regression
- Various bugfixes

129
api.go
View file

@ -13,7 +13,7 @@ import (
"strings"
)
const API_VERSION = 1.1
const APIVERSION = 1.1
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack()
@ -45,12 +45,14 @@ func httpError(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusNotFound)
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
http.Error(w, err.Error(), http.StatusBadRequest)
} else if strings.HasPrefix(err.Error(), "Impossible") {
http.Error(w, err.Error(), http.StatusNotAcceptable)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func writeJson(w http.ResponseWriter, b []byte) {
func writeJSON(w http.ResponseWriter, b []byte) {
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
@ -80,7 +82,7 @@ func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -109,11 +111,11 @@ func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reque
}
if status != "" {
b, err := json.Marshal(&ApiAuth{Status: status})
b, err := json.Marshal(&APIAuth{Status: status})
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
w.WriteHeader(http.StatusNoContent)
@ -126,7 +128,7 @@ func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -155,7 +157,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r
return nil
}
func getImagesJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
@ -174,7 +176,7 @@ func getImagesJson(srv *Server, version float64, w http.ResponseWriter, r *http.
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -191,7 +193,7 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -208,7 +210,7 @@ func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *ht
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -225,11 +227,11 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
func getContainersJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
@ -249,7 +251,7 @@ func getContainersJson(srv *Server, version float64, w http.ResponseWriter, r *h
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -292,12 +294,12 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
b, err := json.Marshal(&ApiId{id})
b, err := json.Marshal(&APIID{id})
if err != nil {
return err
}
w.WriteHeader(http.StatusCreated)
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -312,16 +314,25 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
tag := r.Form.Get("tag")
repo := r.Form.Get("repo")
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0)
if image != "" { //pull
registry := r.Form.Get("registry")
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
if err := srv.ImagePull(image, tag, registry, w, version > 1.0); err != nil {
if err := srv.ImagePull(image, tag, registry, w, sf); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
return err
}
} else { //import
if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil {
if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
return err
}
}
@ -342,7 +353,7 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -357,16 +368,22 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
imgId, err := srv.ImageInsert(name, url, path, w)
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0)
imgID, err := srv.ImageInsert(name, url, path, w, sf)
if err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
}
b, err := json.Marshal(&APIID{ID: imgID})
if err != nil {
return err
}
b, err := json.Marshal(&ApiId{Id: imgId})
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -380,8 +397,15 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ImagePush(name, registry, w); err != nil {
if version > 1.0 {
w.Header().Set("Content-Type", "application/json")
}
sf := utils.NewStreamFormatter(version > 1.0)
if err := srv.ImagePush(name, registry, w, sf); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
}
return err
}
return nil
@ -397,8 +421,8 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
return err
}
out := &ApiRun{
Id: id,
out := &APIRun{
ID: id,
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
@ -413,7 +437,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
return err
}
w.WriteHeader(http.StatusCreated)
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -510,11 +534,11 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *
if err != nil {
return err
}
b, err := json.Marshal(&ApiWait{StatusCode: status})
b, err := json.Marshal(&APIWait{StatusCode: status})
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -601,7 +625,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -619,17 +643,17 @@ func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *htt
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
apiConfig := &ApiImageConfig{}
apiConfig := &APIImageConfig{}
if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
return err
}
image, err := srv.ImageGetCached(apiConfig.Id, apiConfig.Config)
image, err := srv.ImageGetCached(apiConfig.ID, apiConfig.Config)
if err != nil {
return err
}
@ -637,12 +661,12 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *
w.WriteHeader(http.StatusNotFound)
return nil
}
apiId := &ApiId{Id: image.Id}
b, err := json.Marshal(apiId)
apiID := &APIID{ID: image.ID}
b, err := json.Marshal(apiID)
if err != nil {
return err
}
writeJson(w, b)
writeJSON(w, b)
return nil
}
@ -650,6 +674,13 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
if err := r.ParseMultipartForm(4096); err != nil {
return err
}
remote := r.FormValue("t")
tag := ""
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
tag = remoteParts[1]
remote = remoteParts[0]
}
dockerfile, _, err := r.FormFile("Dockerfile")
if err != nil {
@ -664,8 +695,10 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
}
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
if _, err := b.Build(dockerfile, context); err != nil {
if id, err := b.Build(dockerfile, context); err != nil {
fmt.Fprintf(w, "Error build: %s\n", err)
} else if remote != "" {
srv.runtime.repositories.Set(remote, tag, id, false)
}
return nil
}
@ -679,13 +712,13 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
"/auth": getAuth,
"/version": getVersion,
"/info": getInfo,
"/images/json": getImagesJson,
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJson,
"/containers/json": getContainersJson,
"/containers/ps": getContainersJSON,
"/containers/json": getContainersJSON,
"/containers/{name:.*}/export": getContainersExport,
"/containers/{name:.*}/changes": getContainersChanges,
"/containers/{name:.*}/json": getContainersByName,
@ -734,9 +767,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
}
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
if err != nil {
version = API_VERSION
version = APIVERSION
}
if version == 0 || version > API_VERSION {
if version == 0 || version > APIVERSION {
w.WriteHeader(http.StatusNotFound)
return
}

View file

@ -1,75 +1,75 @@
package docker
type ApiHistory struct {
Id string
type APIHistory struct {
ID string `json:"Id"`
Created int64
CreatedBy string
CreatedBy string `json:",omitempty"`
}
type ApiImages struct {
type APIImages struct {
Repository string `json:",omitempty"`
Tag string `json:",omitempty"`
Id string
Created int64 `json:",omitempty"`
ID string `json:"Id"`
Created int64
Size int64
ParentSize int64
}
type ApiInfo struct {
Containers int
Version string
Images int
type APIInfo struct {
Debug bool
GoVersion string
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
}
type ApiContainers struct {
Id string
Image string `json:",omitempty"`
Command string `json:",omitempty"`
Created int64 `json:",omitempty"`
Status string `json:",omitempty"`
Ports string `json:",omitempty"`
type APIContainers struct {
ID string `json:"Id"`
Image string
Command string
Created int64
Status string
Ports string
SizeRw int64
SizeRootFs int64
}
type ApiSearch struct {
type APISearch struct {
Name string
Description string
}
type ApiId struct {
Id string
type APIID struct {
ID string `json:"Id"`
}
type ApiRun struct {
Id string
Warnings []string
type APIRun struct {
ID string `json:"Id"`
Warnings []string `json:",omitempty"`
}
type ApiPort struct {
type APIPort struct {
Port string
}
type ApiVersion struct {
Version string
GitCommit string
MemoryLimit bool
SwapLimit bool
type APIVersion struct {
Version string
GitCommit string `json:",omitempty"`
GoVersion string `json:",omitempty"`
}
type ApiWait struct {
type APIWait struct {
StatusCode int
}
type ApiAuth struct {
type APIAuth struct {
Status string
}
type ApiImageConfig struct {
Id string
type APIImageConfig struct {
ID string `json:"Id"`
*Config
}

View file

@ -37,17 +37,17 @@ func TestGetAuth(t *testing.T) {
Email: "utest@yopmail.com",
}
authConfigJson, err := json.Marshal(authConfig)
authConfigJSON, err := json.Marshal(authConfig)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJson))
req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJSON))
if err != nil {
t.Fatal(err)
}
if err := postAuth(srv, API_VERSION, r, req, nil); err != nil {
if err := postAuth(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
@ -73,11 +73,11 @@ func TestGetVersion(t *testing.T) {
r := httptest.NewRecorder()
if err := getVersion(srv, API_VERSION, r, nil, nil); err != nil {
if err := getVersion(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
v := &ApiVersion{}
v := &APIVersion{}
if err = json.Unmarshal(r.Body.Bytes(), v); err != nil {
t.Fatal(err)
}
@ -97,21 +97,21 @@ func TestGetInfo(t *testing.T) {
r := httptest.NewRecorder()
if err := getInfo(srv, API_VERSION, r, nil, nil); err != nil {
if err := getInfo(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
infos := &ApiInfo{}
infos := &APIInfo{}
err = json.Unmarshal(r.Body.Bytes(), infos)
if err != nil {
t.Fatal(err)
}
if infos.Version != VERSION {
t.Errorf("Excepted version %s, %s found", VERSION, infos.Version)
if infos.Images != 1 {
t.Errorf("Excepted images: %d, %d found", 1, infos.Images)
}
}
func TestGetImagesJson(t *testing.T) {
func TestGetImagesJSON(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@ -128,11 +128,11 @@ func TestGetImagesJson(t *testing.T) {
r := httptest.NewRecorder()
if err := getImagesJson(srv, API_VERSION, r, req, nil); err != nil {
if err := getImagesJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
images := []ApiImages{}
images := []APIImages{}
if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil {
t.Fatal(err)
}
@ -153,11 +153,11 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err)
}
if err := getImagesJson(srv, API_VERSION, r2, req2, nil); err != nil {
if err := getImagesJSON(srv, APIVERSION, r2, req2, nil); err != nil {
t.Fatal(err)
}
images2 := []ApiImages{}
images2 := []APIImages{}
if err := json.Unmarshal(r2.Body.Bytes(), &images2); err != nil {
t.Fatal(err)
}
@ -166,8 +166,8 @@ func TestGetImagesJson(t *testing.T) {
t.Errorf("Excepted 1 image, %d found", len(images2))
}
if images2[0].Id != GetTestImage(runtime).Id {
t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).Id, images2[0].Id)
if images2[0].ID != GetTestImage(runtime).ID {
t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).ID, images2[0].ID)
}
r3 := httptest.NewRecorder()
@ -178,11 +178,11 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err)
}
if err := getImagesJson(srv, API_VERSION, r3, req3, nil); err != nil {
if err := getImagesJSON(srv, APIVERSION, r3, req3, nil); err != nil {
t.Fatal(err)
}
images3 := []ApiImages{}
images3 := []APIImages{}
if err := json.Unmarshal(r3.Body.Bytes(), &images3); err != nil {
t.Fatal(err)
}
@ -199,7 +199,7 @@ func TestGetImagesJson(t *testing.T) {
t.Fatal(err)
}
err = getImagesJson(srv, API_VERSION, r4, req4, nil)
err = getImagesJSON(srv, APIVERSION, r4, req4, nil)
if err == nil {
t.Fatalf("Error expected, received none")
}
@ -220,7 +220,7 @@ func TestGetImagesViz(t *testing.T) {
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
if err := getImagesViz(srv, API_VERSION, r, nil, nil); err != nil {
if err := getImagesViz(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
@ -256,11 +256,11 @@ func TestGetImagesSearch(t *testing.T) {
t.Fatal(err)
}
if err := getImagesSearch(srv, API_VERSION, r, req, nil); err != nil {
if err := getImagesSearch(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
results := []ApiSearch{}
results := []APISearch{}
if err := json.Unmarshal(r.Body.Bytes(), &results); err != nil {
t.Fatal(err)
}
@ -280,11 +280,11 @@ func TestGetImagesHistory(t *testing.T) {
r := httptest.NewRecorder()
if err := getImagesHistory(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
if err := getImagesHistory(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
t.Fatal(err)
}
history := []ApiHistory{}
history := []APIHistory{}
if err := json.Unmarshal(r.Body.Bytes(), &history); err != nil {
t.Fatal(err)
}
@ -303,7 +303,7 @@ func TestGetImagesByName(t *testing.T) {
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
if err := getImagesByName(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
if err := getImagesByName(srv, APIVERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
t.Fatal(err)
}
@ -311,12 +311,12 @@ func TestGetImagesByName(t *testing.T) {
if err := json.Unmarshal(r.Body.Bytes(), img); err != nil {
t.Fatal(err)
}
if img.Id != GetTestImage(runtime).Id || img.Comment != "Imported from http://get.docker.io/images/busybox" {
if img.ID != GetTestImage(runtime).ID || img.Comment != "Imported from http://get.docker.io/images/busybox" {
t.Errorf("Error inspecting image")
}
}
func TestGetContainersJson(t *testing.T) {
func TestGetContainersJSON(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@ -326,7 +326,7 @@ func TestGetContainersJson(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
})
if err != nil {
@ -340,18 +340,18 @@ func TestGetContainersJson(t *testing.T) {
}
r := httptest.NewRecorder()
if err := getContainersJson(srv, API_VERSION, r, req, nil); err != nil {
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
containers := []ApiContainers{}
containers := []APIContainers{}
if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
t.Fatal(err)
}
if len(containers) != 1 {
t.Fatalf("Excepted %d container, %d found", 1, len(containers))
}
if containers[0].Id != container.Id {
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.Id, containers[0].Id)
if containers[0].ID != container.ID {
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID)
}
}
@ -369,7 +369,7 @@ func TestGetContainersExport(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
)
@ -383,7 +383,7 @@ func TestGetContainersExport(t *testing.T) {
}
r := httptest.NewRecorder()
if err = getContainersExport(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
@ -424,7 +424,7 @@ func TestGetContainersChanges(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
)
@ -438,7 +438,7 @@ func TestGetContainersChanges(t *testing.T) {
}
r := httptest.NewRecorder()
if err := getContainersChanges(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := getContainersChanges(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
changes := []Change{}
@ -472,7 +472,7 @@ func TestGetContainersByName(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
},
)
@ -482,15 +482,15 @@ func TestGetContainersByName(t *testing.T) {
defer runtime.Destroy(container)
r := httptest.NewRecorder()
if err := getContainersByName(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := getContainersByName(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
outContainer := &Container{}
if err := json.Unmarshal(r.Body.Bytes(), outContainer); err != nil {
t.Fatal(err)
}
if outContainer.Id != container.Id {
t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.Id, outContainer.Id)
if outContainer.ID != container.ID {
t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.ID, outContainer.ID)
}
}
@ -514,7 +514,7 @@ func TestPostAuth(t *testing.T) {
auth.SaveConfig(runtime.root, authStr, config.Email)
r := httptest.NewRecorder()
if err := getAuth(srv, API_VERSION, r, nil, nil); err != nil {
if err := getAuth(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
@ -542,7 +542,7 @@ func TestPostCommit(t *testing.T) {
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
)
@ -555,24 +555,24 @@ func TestPostCommit(t *testing.T) {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.Id, bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+container.ID, bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postCommit(srv, API_VERSION, r, req, nil); err != nil {
if err := postCommit(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusCreated {
t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
}
apiId := &ApiId{}
if err := json.Unmarshal(r.Body.Bytes(), apiId); err != nil {
apiID := &APIID{}
if err := json.Unmarshal(r.Body.Bytes(), apiID); err != nil {
t.Fatal(err)
}
if _, err := runtime.graph.Get(apiId.Id); err != nil {
if _, err := runtime.graph.Get(apiID.ID); err != nil {
t.Fatalf("The image has not been commited")
}
}
@ -715,7 +715,7 @@ func TestPostImagesInsert(t *testing.T) {
// t.Fatalf("The test file has not been found")
// }
// if err := srv.runtime.graph.Delete(img.Id); err != nil {
// if err := srv.runtime.graph.Delete(img.ID); err != nil {
// t.Fatal(err)
// }
}
@ -824,8 +824,8 @@ func TestPostContainersCreate(t *testing.T) {
srv := &Server{runtime: runtime}
configJson, err := json.Marshal(&Config{
Image: GetTestImage(runtime).Id,
configJSON, err := json.Marshal(&Config{
Image: GetTestImage(runtime).ID,
Memory: 33554432,
Cmd: []string{"touch", "/test"},
})
@ -833,25 +833,25 @@ func TestPostContainersCreate(t *testing.T) {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJson))
req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersCreate(srv, API_VERSION, r, req, nil); err != nil {
if err := postContainersCreate(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusCreated {
t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
}
apiRun := &ApiRun{}
apiRun := &APIRun{}
if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil {
t.Fatal(err)
}
container := srv.runtime.Get(apiRun.Id)
container := srv.runtime.Get(apiRun.ID)
if container == nil {
t.Fatalf("Container not created")
}
@ -880,7 +880,7 @@ func TestPostContainersKill(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@ -902,7 +902,7 @@ func TestPostContainersKill(t *testing.T) {
}
r := httptest.NewRecorder()
if err := postContainersKill(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := postContainersKill(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@ -924,7 +924,7 @@ func TestPostContainersRestart(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@ -945,12 +945,12 @@ func TestPostContainersRestart(t *testing.T) {
t.Errorf("Container should be running")
}
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/restart?t=1", bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/restart?t=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersRestart(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := postContainersRestart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@ -980,7 +980,7 @@ func TestPostContainersStart(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@ -991,7 +991,7 @@ func TestPostContainersStart(t *testing.T) {
defer runtime.Destroy(container)
r := httptest.NewRecorder()
if err := postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@ -1006,7 +1006,7 @@ func TestPostContainersStart(t *testing.T) {
}
r = httptest.NewRecorder()
if err = postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err == nil {
if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@ -1026,7 +1026,7 @@ func TestPostContainersStop(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@ -1048,12 +1048,12 @@ func TestPostContainersStop(t *testing.T) {
}
// Note: as it is a POST request, it requires a body.
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/stop?t=1", bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/stop?t=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersStop(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := postContainersStop(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@ -1075,7 +1075,7 @@ func TestPostContainersWait(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sleep", "1"},
OpenStdin: true,
},
@ -1091,10 +1091,10 @@ func TestPostContainersWait(t *testing.T) {
setTimeout(t, "Wait timed out", 3*time.Second, func() {
r := httptest.NewRecorder()
if err := postContainersWait(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
if err := postContainersWait(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
apiWait := &ApiWait{}
apiWait := &APIWait{}
if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil {
t.Fatal(err)
}
@ -1119,7 +1119,7 @@ func TestPostContainersAttach(t *testing.T) {
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@ -1148,12 +1148,12 @@ func TestPostContainersAttach(t *testing.T) {
out: stdoutPipe,
}
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
if err := postContainersAttach(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := postContainersAttach(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
}()
@ -1206,7 +1206,7 @@ func TestDeleteContainers(t *testing.T) {
srv := &Server{runtime: runtime}
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
})
if err != nil {
@ -1218,19 +1218,19 @@ func TestDeleteContainers(t *testing.T) {
t.Fatal(err)
}
req, err := http.NewRequest("DELETE", "/containers/"+container.Id, nil)
req, err := http.NewRequest("DELETE", "/containers/"+container.ID, nil)
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := deleteContainers(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
if err := deleteContainers(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
}
if c := runtime.Get(container.Id); c != nil {
if c := runtime.Get(container.ID); c != nil {
t.Fatalf("The container as not been deleted")
}

View file

@ -54,6 +54,9 @@ func Tar(path string, compression Compression) (io.Reader, error) {
func Untar(archive io.Reader, path string) error {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
cmd.Stdin = archive
// Hardcode locale environment for predictable outcome regardless of host configuration.
// (see https://github.com/dotcloud/docker/issues/355)
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%s: %s", err, output)

View file

@ -16,12 +16,12 @@ import (
const CONFIGFILE = ".dockercfg"
// the registry server we want to login against
const INDEX_SERVER = "https://index.docker.io/v1"
const INDEXSERVER = "https://index.docker.io/v1"
//const INDEX_SERVER = "http://indexstaging-docker.dotcloud.com/"
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
var (
ErrConfigFileMissing error = errors.New("The Auth config file is missing")
ErrConfigFileMissing = errors.New("The Auth config file is missing")
)
type AuthConfig struct {
@ -44,7 +44,7 @@ func IndexServerAddress() string {
if os.Getenv("DOCKER_INDEX_URL") != "" {
return os.Getenv("DOCKER_INDEX_URL") + "/v1"
}
return INDEX_SERVER
return INDEXSERVER
}
// create a base64 encoded auth string to store in config

View file

@ -40,7 +40,7 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
}
// Generate id
id := GenerateId()
id := GenerateID()
// Generate default hostname
// FIXME: the lxc template no longer needs to set a default hostname
if config.Hostname == "" {
@ -49,17 +49,17 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
container := &Container{
// FIXME: we should generate the ID here instead of receiving it as an argument
Id: id,
ID: id,
Created: time.Now(),
Path: config.Cmd[0],
Args: config.Cmd[1:], //FIXME: de-duplicate from config
Config: config,
Image: img.Id, // Always use the resolved image id
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath: sysInitPath,
}
container.root = builder.runtime.containerRoot(container.Id)
container.root = builder.runtime.containerRoot(container.ID)
// Step 1: create the container directory.
// This doubles as a barrier to avoid race conditions.
if err := os.Mkdir(container.root, 0700); err != nil {
@ -110,7 +110,7 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a
}
// Register the image if needed
if repository != "" {
if err := builder.repositories.Set(repository, tag, img.Id, true); err != nil {
if err := builder.repositories.Set(repository, tag, img.ID, true); err != nil {
return img, err
}
}

View file

@ -63,11 +63,11 @@ func (b *builderClient) CmdFrom(name string) error {
return err
}
img := &ApiId{}
img := &APIID{}
if err := json.Unmarshal(obj, img); err != nil {
return err
}
b.image = img.Id
b.image = img.ID
utils.Debugf("Using image %s", b.image)
return nil
}
@ -91,19 +91,19 @@ func (b *builderClient) CmdRun(args string) error {
b.config.Cmd = nil
MergeConfig(b.config, config)
body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config})
body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config})
if err != nil {
if statusCode != 404 {
return err
}
}
if statusCode != 404 {
apiId := &ApiId{}
if err := json.Unmarshal(body, apiId); err != nil {
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
utils.Debugf("Use cached version")
b.image = apiId.Id
b.image = apiID.ID
return nil
}
cid, err := b.run()
@ -163,7 +163,7 @@ func (b *builderClient) CmdInsert(args string) error {
// return err
// }
// apiId := &ApiId{}
// apiId := &APIId{}
// if err := json.Unmarshal(body, apiId); err != nil {
// return err
// }
@ -182,7 +182,7 @@ func (b *builderClient) run() (string, error) {
return "", err
}
apiRun := &ApiRun{}
apiRun := &APIRun{}
if err := json.Unmarshal(body, apiRun); err != nil {
return "", err
}
@ -191,18 +191,18 @@ func (b *builderClient) run() (string, error) {
}
//start the container
_, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/start", nil)
_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
if err != nil {
return "", err
}
b.tmpContainers[apiRun.Id] = struct{}{}
b.tmpContainers[apiRun.ID] = struct{}{}
// Wait for it to finish
body, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/wait", nil)
body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
if err != nil {
return "", err
}
apiWait := &ApiWait{}
apiWait := &APIWait{}
if err := json.Unmarshal(body, apiWait); err != nil {
return "", err
}
@ -210,7 +210,7 @@ func (b *builderClient) run() (string, error) {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
}
return apiRun.Id, nil
return apiRun.ID, nil
}
func (b *builderClient) commit(id string) error {
@ -222,11 +222,11 @@ func (b *builderClient) commit(id string) error {
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
if cid, err := b.run(); err != nil {
cid, err := b.run()
if err != nil {
return err
} else {
id = cid
}
id = cid
b.config.Cmd = cmd
}
@ -239,12 +239,12 @@ func (b *builderClient) commit(id string) error {
if err != nil {
return err
}
apiId := &ApiId{}
if err := json.Unmarshal(body, apiId); err != nil {
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
b.tmpImages[apiId.Id] = struct{}{}
b.image = apiId.Id
b.tmpImages[apiID.ID] = struct{}{}
b.image = apiID.ID
b.needCommit = false
return nil
}

View file

@ -32,8 +32,6 @@ type buildFile struct {
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
needCommit bool
out io.Writer
}
@ -63,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
remote = name
}
if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false)); err != nil {
return err
}
@ -75,15 +73,14 @@ func (b *buildFile) CmdFrom(name string) error {
return err
}
}
b.image = image.Id
b.image = image.ID
b.config = &Config{}
return nil
}
func (b *buildFile) CmdMaintainer(name string) error {
b.needCommit = true
b.maintainer = name
return nil
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
}
func (b *buildFile) CmdRun(args string) error {
@ -95,28 +92,34 @@ func (b *buildFile) CmdRun(args string) error {
return err
}
cmd, env := b.config.Cmd, b.config.Env
cmd := b.config.Cmd
b.config.Cmd = nil
MergeConfig(b.config, config)
if cache, err := b.srv.ImageGetCached(b.image, config); err != nil {
utils.Debugf("Command to be executed: %v", b.config.Cmd)
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
utils.Debugf("Use cached version")
b.image = cache.Id
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
cid, err := b.run()
if err != nil {
return err
}
b.config.Cmd, b.config.Env = cmd, env
return b.commit(cid)
if err := b.commit(cid, cmd, "run"); err != nil {
return err
}
b.config.Cmd = cmd
return nil
}
func (b *buildFile) CmdEnv(args string) error {
b.needCommit = true
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
@ -131,60 +134,34 @@ func (b *buildFile) CmdEnv(args string) error {
}
}
b.config.Env = append(b.config.Env, key+"="+value)
return nil
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
}
func (b *buildFile) CmdCmd(args string) error {
b.needCommit = true
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
b.config.Cmd = []string{"/bin/sh", "-c", args}
} else {
b.config.Cmd = cmd
cmd = []string{"/bin/sh", "-c", args}
}
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
return err
}
b.config.Cmd = cmd
return nil
}
func (b *buildFile) CmdExpose(args string) error {
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return nil
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
}
func (b *buildFile) CmdInsert(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to insert")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid INSERT format")
}
sourceUrl := strings.Trim(tmp[0], " ")
destPath := strings.Trim(tmp[1], " ")
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
}
file, err := utils.Download(sourceUrl, b.out)
if err != nil {
return err
}
defer file.Body.Close()
b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath}
cid, err := b.run()
if err != nil {
return err
}
container := b.runtime.Get(cid)
if container == nil {
return fmt.Errorf("An error occured while creating the container")
}
if err := container.Inject(file.Body, destPath); err != nil {
return err
}
return b.commit(cid)
func (b *buildFile) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}
func (b *buildFile) CmdAdd(args string) error {
@ -193,12 +170,13 @@ func (b *buildFile) CmdAdd(args string) error {
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid INSERT format")
return fmt.Errorf("Invalid ADD format")
}
orig := strings.Trim(tmp[0], " ")
dest := strings.Trim(tmp[1], " ")
b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest}
cmd := b.config.Cmd
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
cid, err := b.run()
if err != nil {
return err
@ -208,19 +186,23 @@ func (b *buildFile) CmdAdd(args string) error {
if container == nil {
return fmt.Errorf("Error while creating the container (CmdAdd)")
}
if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil {
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
origPath := path.Join(b.context, orig)
destPath := path.Join(container.rwPath(), dest)
destPath := path.Join(container.RootfsPath(), dest)
fi, err := os.Stat(origPath)
if err != nil {
return err
}
if fi.IsDir() {
if err := os.MkdirAll(destPath, 0700); err != nil {
return err
}
files, err := ioutil.ReadDir(path.Join(b.context, orig))
if err != nil {
return err
@ -231,12 +213,18 @@ func (b *buildFile) CmdAdd(args string) error {
}
}
} else {
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
return err
}
if err := utils.CopyDirectory(origPath, destPath); err != nil {
return err
}
}
return b.commit(cid)
if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
return err
}
b.config.Cmd = cmd
return nil
}
func (b *buildFile) run() (string, error) {
@ -250,7 +238,7 @@ func (b *buildFile) run() (string, error) {
if err != nil {
return "", err
}
b.tmpContainers[c.Id] = struct{}{}
b.tmpContainers[c.ID] = struct{}{}
//start the container
if err := c.Start(); err != nil {
@ -262,23 +250,33 @@ func (b *buildFile) run() (string, error) {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
}
return c.Id, nil
return c.ID, nil
}
func (b *buildFile) commit(id string) error {
// Commit the container <id> with the autorun command <autoCmd>
func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to commit")
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
if cid, err := b.run(); err != nil {
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
} else {
id = cid
utils.Debugf("[BUILDER] Cache miss")
}
b.config.Cmd = cmd
cid, err := b.run()
if err != nil {
return err
}
id = cid
}
container := b.runtime.Get(id)
@ -286,20 +284,20 @@ func (b *buildFile) commit(id string) error {
return fmt.Errorf("An error occured while creating the container")
}
// Note: Actually copy the struct
autoConfig := *b.config
autoConfig.Cmd = autoCmd
// Commit the container
image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil)
image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
if err != nil {
return err
}
b.tmpImages[image.Id] = struct{}{}
b.image = image.Id
b.needCommit = false
b.tmpImages[image.ID] = struct{}{}
b.image = image.ID
return nil
}
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
defer b.clearTmp(b.tmpContainers, b.tmpImages)
if context != nil {
name, err := ioutil.TempDir("/tmp", "docker-build")
if err != nil {
@ -337,6 +335,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists {
fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
continue
}
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
@ -345,22 +344,10 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
fmt.Fprintf(b.out, "===> %v\n", b.image)
}
if b.needCommit {
if err := b.commit(""); err != nil {
return "", err
}
}
if b.image != "" {
// The build is successful, keep the temporary containers and images
for i := range b.tmpImages {
delete(b.tmpImages, i)
}
fmt.Fprintf(b.out, "Build success.\n Image id:\n%s\n", b.image)
fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
return b.image, nil
}
for i := range b.tmpContainers {
delete(b.tmpContainers, i)
}
return "", fmt.Errorf("An error occured during the build\n")
}

View file

@ -26,7 +26,7 @@ func TestBuild(t *testing.T) {
buildfile := NewBuildFile(srv, &utils.NopWriter{})
imgId, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
if err != nil {
t.Fatal(err)
}
@ -34,7 +34,7 @@ func TestBuild(t *testing.T) {
builder := NewBuilder(runtime)
container, err := builder.Create(
&Config{
Image: imgId,
Image: imgID,
Cmd: []string{"cat", "/tmp/passwd"},
},
)
@ -53,7 +53,7 @@ func TestBuild(t *testing.T) {
container2, err := builder.Create(
&Config{
Image: imgId,
Image: imgID,
Cmd: []string{"ls", "-d", "/var/run/sshd"},
},
)

View file

@ -65,7 +65,7 @@ func Changes(layers []string, rw string) ([]Change, error) {
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := strings.TrimLeft(file, ".wh.")
originalFile := file[len(".wh."):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {

View file

@ -17,6 +17,7 @@ import (
"net/url"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"strconv"
@ -27,10 +28,10 @@ import (
"unicode"
)
const VERSION = "0.3.3"
const VERSION = "0.4.0"
var (
GIT_COMMIT string
GITCOMMIT string
)
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
@ -101,7 +102,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"stop", "Stop a running container"},
{"tag", "Tag an image into a repository"},
{"version", "Show the docker version information"},
{"wait", "Block until a container stops}, then print its exit code"},
{"wait", "Block until a container stops, then print its exit code"},
} {
help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
}
@ -130,16 +131,20 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
}
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile")
fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin")
cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH")
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
var (
file io.ReadCloser
multipartBody io.Reader
err error
file io.ReadCloser
contextPath string
)
// Init the needed component for the Multipart
@ -148,27 +153,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
w := multipart.NewWriter(buff)
boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
// Create a FormFile multipart for the Dockerfile
if *fileName == "-" {
compression := Bzip2
if cmd.Arg(0) == "-" {
file = os.Stdin
} else {
file, err = os.Open(*fileName)
// Send Dockerfile from arg/Dockerfile (deprecate later)
f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile"))
if err != nil {
return err
}
defer file.Close()
}
if wField, err := w.CreateFormFile("Dockerfile", *fileName); err != nil {
return err
} else {
io.Copy(wField, file)
}
multipartBody = io.MultiReader(multipartBody, boundary)
compression := Bzip2
// Create a FormFile multipart for the context if needed
if cmd.Arg(0) != "" {
file = f
// Send context from arg
// Create a FormFile multipart for the context if needed
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
context, err := Tar(cmd.Arg(0), compression)
if err != nil {
@ -179,23 +176,32 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if err != nil {
return err
}
if wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension()); err != nil {
wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension())
if err != nil {
return err
} else {
// FIXME: Find a way to have a progressbar for the upload too
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false))
}
// FIXME: Find a way to have a progressbar for the upload too
sf := utils.NewStreamFormatter(false)
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
multipartBody = io.MultiReader(multipartBody, boundary)
}
// Create a FormFile multipart for the Dockerfile
wField, err := w.CreateFormFile("Dockerfile", "Dockerfile")
if err != nil {
return err
}
io.Copy(wField, file)
multipartBody = io.MultiReader(multipartBody, boundary)
v := &url.Values{}
v.Set("t", *tag)
// Send the multipart request with correct content-type
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), multipartBody)
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
if err != nil {
return err
}
req.Header.Set("Content-Type", w.FormDataContentType())
if cmd.Arg(0) != "" {
if contextPath != "" {
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
fmt.Println("Uploading Context...")
}
@ -270,9 +276,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
oldState, err := term.SetRawTerminal()
if err != nil {
return err
} else {
defer term.RestoreTerminal(oldState)
}
defer term.RestoreTerminal(oldState)
cmd := Subcmd("login", "", "Register or Login to the docker registry server")
if err := cmd.Parse(args); err != nil {
@ -325,7 +330,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
return err
}
var out2 ApiAuth
var out2 APIAuth
err = json.Unmarshal(body, &out2)
if err != nil {
return err
@ -352,7 +357,7 @@ func (cli *DockerCli) CmdWait(args ...string) error {
if err != nil {
fmt.Printf("%s", err)
} else {
var out ApiWait
var out APIWait
err = json.Unmarshal(body, &out)
if err != nil {
return err
@ -380,21 +385,20 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
return err
}
var out ApiVersion
var out APIVersion
err = json.Unmarshal(body, &out)
if err != nil {
utils.Debugf("Error unmarshal: body: %s, err: %s\n", body, err)
return err
}
fmt.Println("Version:", out.Version)
fmt.Println("Git Commit:", out.GitCommit)
if !out.MemoryLimit {
fmt.Println("WARNING: No memory limit support")
fmt.Println("Client version:", VERSION)
fmt.Println("Server version:", out.Version)
if out.GitCommit != "" {
fmt.Println("Git commit:", out.GitCommit)
}
if !out.SwapLimit {
fmt.Println("WARNING: No swap limit support")
if out.GoVersion != "" {
fmt.Println("Go version:", out.GoVersion)
}
return nil
}
@ -414,15 +418,24 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
return err
}
var out ApiInfo
err = json.Unmarshal(body, &out)
if err != nil {
var out APIInfo
if err := json.Unmarshal(body, &out); err != nil {
return err
}
fmt.Printf("containers: %d\nversion: %s\nimages: %d\nGo version: %s\n", out.Containers, out.Version, out.Images, out.GoVersion)
if out.Debug {
fmt.Println("debug mode enabled")
fmt.Printf("fds: %d\ngoroutines: %d\n", out.NFd, out.NGoroutines)
fmt.Printf("Containers: %d\n", out.Containers)
fmt.Printf("Images: %d\n", out.Images)
if out.Debug || os.Getenv("DEBUG") != "" {
fmt.Printf("Debug mode (server): %v\n", out.Debug)
fmt.Printf("Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
fmt.Printf("Fds: %d\n", out.NFd)
fmt.Printf("Goroutines: %d\n", out.NGoroutines)
}
if !out.MemoryLimit {
fmt.Println("WARNING: No memory limit support")
}
if !out.SwapLimit {
fmt.Println("WARNING: No swap limit support")
}
return nil
}
@ -590,7 +603,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
return err
}
var outs []ApiHistory
var outs []APIHistory
err = json.Unmarshal(body, &outs)
if err != nil {
return err
@ -599,7 +612,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
for _, out := range outs {
fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.Id, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
}
w.Flush()
return nil
@ -725,12 +738,6 @@ func (cli *DockerCli) CmdPull(args ...string) error {
remote = remoteParts[0]
}
if strings.Contains(remote, "/") {
if _, err := cli.checkIfLogged(true, "pull"); err != nil {
return err
}
}
v := url.Values{}
v.Set("fromImage", remote)
v.Set("tag", *tag)
@ -778,7 +785,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
return err
}
var outs []ApiImages
var outs []APIImages
err = json.Unmarshal(body, &outs)
if err != nil {
return err
@ -800,9 +807,9 @@ func (cli *DockerCli) CmdImages(args ...string) error {
if !*quiet {
fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag)
if *noTrunc {
fmt.Fprintf(w, "%s\t", out.Id)
fmt.Fprintf(w, "%s\t", out.ID)
} else {
fmt.Fprintf(w, "%s\t", utils.TruncateId(out.Id))
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
}
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
if out.ParentSize > 0 {
@ -812,9 +819,9 @@ func (cli *DockerCli) CmdImages(args ...string) error {
}
} else {
if *noTrunc {
fmt.Fprintln(w, out.Id)
fmt.Fprintln(w, out.ID)
} else {
fmt.Fprintln(w, utils.TruncateId(out.Id))
fmt.Fprintln(w, utils.TruncateID(out.ID))
}
}
}
@ -861,7 +868,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return err
}
var outs []ApiContainers
var outs []APIContainers
err = json.Unmarshal(body, &outs)
if err != nil {
return err
@ -874,9 +881,9 @@ func (cli *DockerCli) CmdPs(args ...string) error {
for _, out := range outs {
if !*quiet {
if *noTrunc {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.Id, out.Image, out.Command, out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
}
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
@ -885,9 +892,9 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
} else {
if *noTrunc {
fmt.Fprintln(w, out.Id)
fmt.Fprintln(w, out.ID)
} else {
fmt.Fprintln(w, utils.TruncateId(out.Id))
fmt.Fprintln(w, utils.TruncateID(out.ID))
}
}
}
@ -930,13 +937,13 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
return err
}
apiId := &ApiId{}
err = json.Unmarshal(body, apiId)
apiID := &APIID{}
err = json.Unmarshal(body, apiID)
if err != nil {
return err
}
fmt.Println(apiId.Id)
fmt.Println(apiID.ID)
return nil
}
@ -993,12 +1000,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil
}
v := url.Values{}
v.Set("logs", "1")
v.Set("stdout", "1")
v.Set("stderr", "1")
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false, nil, os.Stdout); err != nil {
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
return err
}
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
return err
}
return nil
@ -1075,7 +1080,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
return err
}
outs := []ApiSearch{}
outs := []APISearch{}
err = json.Unmarshal(body, &outs)
if err != nil {
return err
@ -1207,7 +1212,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return err
}
out := &ApiRun{}
out := &APIRun{}
err = json.Unmarshal(body, out)
if err != nil {
return err
@ -1228,18 +1233,21 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
//start the container
_, _, err = cli.call("POST", "/containers/"+out.Id+"/start", nil)
_, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil)
if err != nil {
return err
}
if !config.AttachStdout && !config.AttachStderr {
fmt.Println(out.ID)
}
if connections > 0 {
chErrors := make(chan error, connections)
cli.monitorTtySize(out.Id)
cli.monitorTtySize(out.ID)
if splitStderr && config.AttachStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
}()
}
@ -1257,7 +1265,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
v.Set("stderr", "1")
}
go func() {
chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
}()
for connections > 0 {
err := <-chErrors
@ -1267,9 +1275,6 @@ func (cli *DockerCli) CmdRun(args ...string) error {
connections -= 1
}
}
if !config.AttachStdout && !config.AttachStderr {
fmt.Println(out.Id)
}
return nil
}
@ -1317,7 +1322,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf)
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), params)
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
if err != nil {
return nil, -1, err
}
@ -1349,7 +1354,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), in)
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
if err != nil {
return err
}
@ -1374,20 +1379,18 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
}
if resp.Header.Get("Content-Type") == "application/json" {
type Message struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
}
dec := json.NewDecoder(resp.Body)
for {
var m Message
var m utils.JSONMessage
if err := dec.Decode(&m); err == io.EOF {
break
} else if err != nil {
return err
}
if m.Progress != "" {
fmt.Fprintf(out, "Downloading %s\r", m.Progress)
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
} else if m.Error != "" {
return fmt.Errorf(m.Error)
} else {
fmt.Fprintf(out, "%s\n", m.Status)
}
@ -1401,7 +1404,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
if err != nil {
return err
}
@ -1422,12 +1425,12 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
return err
})
if in != nil && setRawTerminal && term.IsTerminal(int(in.Fd())) && os.Getenv("NORAW") == "" {
if oldState, err := term.SetRawTerminal(); err != nil {
if in != nil && setRawTerminal && term.IsTerminal(in.Fd()) && os.Getenv("NORAW") == "" {
oldState, err := term.SetRawTerminal()
if err != nil {
return err
} else {
defer term.RestoreTerminal(oldState)
}
defer term.RestoreTerminal(oldState)
}
sendStdin := utils.Go(func() error {
_, err := io.Copy(rwc, in)
@ -1441,7 +1444,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.Fi
return err
}
if !term.IsTerminal(int(os.Stdin.Fd())) {
if !term.IsTerminal(os.Stdin.Fd()) {
if err := <-sendStdin; err != nil {
return err
}

View file

@ -24,7 +24,7 @@ import (
type Container struct {
root string
Id string
ID string
Created time.Time
@ -168,8 +168,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
}
type NetworkSettings struct {
IpAddress string
IpPrefixLen int
IPAddress string
IPPrefixLen int
Gateway string
Bridge string
PortMapping map[string]string
@ -410,7 +410,7 @@ func (container *Container) Start() error {
defer container.State.unlock()
if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.Id)
return fmt.Errorf("The container %s is already running.", container.ID)
}
if err := container.EnsureMounted(); err != nil {
return err
@ -432,24 +432,24 @@ func (container *Container) Start() error {
// Create the requested volumes volumes
for volPath := range container.Config.Volumes {
if c, err := container.runtime.volumes.Create(nil, container, "", "", nil); err != nil {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
} else {
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
container.Volumes[volPath] = c.Id
}
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
container.Volumes[volPath] = c.ID
}
if container.Config.VolumesFrom != "" {
c := container.runtime.Get(container.Config.VolumesFrom)
if c == nil {
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.Id)
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID)
}
for volPath, id := range c.Volumes {
if _, exists := container.Volumes[volPath]; exists {
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.Id)
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.ID)
}
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
@ -463,7 +463,7 @@ func (container *Container) Start() error {
}
params := []string{
"-n", container.Id,
"-n", container.ID,
"-f", container.lxcConfigPath(),
"--",
"/sbin/init",
@ -574,17 +574,17 @@ func (container *Container) allocateNetwork() error {
}
container.NetworkSettings.PortMapping = make(map[string]string)
for _, spec := range container.Config.PortSpecs {
if nat, err := iface.AllocatePort(spec); err != nil {
nat, err := iface.AllocatePort(spec)
if err != nil {
iface.Release()
return err
} else {
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
}
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
}
container.network = iface
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.Gateway = iface.Gateway.String()
return nil
}
@ -598,16 +598,16 @@ func (container *Container) releaseNetwork() {
// FIXME: replace this with a control socket within docker-init
func (container *Container) waitLxc() error {
for {
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
if err != nil {
return err
} else {
if !strings.Contains(string(output), "RUNNING") {
return nil
}
}
if !strings.Contains(string(output), "RUNNING") {
return nil
}
time.Sleep(500 * time.Millisecond)
}
return nil
panic("Unreachable")
}
func (container *Container) monitor() {
@ -617,17 +617,17 @@ func (container *Container) monitor() {
// If the command does not exists, try to wait via lxc
if container.cmd == nil {
if err := container.waitLxc(); err != nil {
utils.Debugf("%s: Process: %s", container.Id, err)
utils.Debugf("%s: Process: %s", container.ID, err)
}
} else {
if err := container.cmd.Wait(); err != nil {
// Discard the error as any signals or non 0 returns will generate an error
utils.Debugf("%s: Process: %s", container.Id, err)
utils.Debugf("%s: Process: %s", container.ID, err)
}
}
utils.Debugf("Process finished")
var exitCode int = -1
exitCode := -1
if container.cmd != nil {
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
}
@ -636,24 +636,24 @@ func (container *Container) monitor() {
container.releaseNetwork()
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil {
utils.Debugf("%s: Error close stdin: %s", container.Id, err)
utils.Debugf("%s: Error close stdin: %s", container.ID, err)
}
}
if err := container.stdout.CloseWriters(); err != nil {
utils.Debugf("%s: Error close stdout: %s", container.Id, err)
utils.Debugf("%s: Error close stdout: %s", container.ID, err)
}
if err := container.stderr.CloseWriters(); err != nil {
utils.Debugf("%s: Error close stderr: %s", container.Id, err)
utils.Debugf("%s: Error close stderr: %s", container.ID, err)
}
if container.ptyMaster != nil {
if err := container.ptyMaster.Close(); err != nil {
utils.Debugf("%s: Error closing Pty master: %s", container.Id, err)
utils.Debugf("%s: Error closing Pty master: %s", container.ID, err)
}
}
if err := container.Unmount(); err != nil {
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
log.Printf("%v: Failed to umount filesystem: %v", container.ID, err)
}
// Re-create a brand new stdin pipe once the container exited
@ -674,7 +674,7 @@ func (container *Container) monitor() {
// This is because State.setStopped() has already been called, and has caused Wait()
// to return.
// FIXME: why are we serializing running state to disk in the first place?
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err)
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err)
}
}
@ -684,17 +684,17 @@ func (container *Container) kill() error {
}
// Sending SIGKILL to the process via lxc
output, err := exec.Command("lxc-kill", "-n", container.Id, "9").CombinedOutput()
output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput()
if err != nil {
log.Printf("error killing container %s (%s, %s)", container.Id, output, err)
log.Printf("error killing container %s (%s, %s)", container.ID, output, err)
}
// 2. Wait for the process to die, in last resort, try to kill the process directly
if err := container.WaitTimeout(10 * time.Second); err != nil {
if container.cmd == nil {
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.Id)
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID)
}
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.Id)
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID)
if err := container.cmd.Process.Kill(); err != nil {
return err
}
@ -722,7 +722,7 @@ func (container *Container) Stop(seconds int) error {
}
// 1. Send a SIGTERM
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil {
log.Print(string(output))
log.Print("Failed to send SIGTERM to the process, force killing")
if err := container.kill(); err != nil {
@ -732,7 +732,7 @@ func (container *Container) Stop(seconds int) error {
// 2. Wait for the process to exit on its own
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.Id, seconds)
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
if err := container.kill(); err != nil {
return err
}
@ -796,7 +796,8 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
case <-done:
return nil
}
panic("unreachable")
panic("Unreachable")
}
func (container *Container) EnsureMounted() error {
@ -839,16 +840,16 @@ func (container *Container) Unmount() error {
return Unmount(container.RootfsPath())
}
// ShortId returns a shorthand version of the container's id for convenience.
// ShortID returns a shorthand version of the container's id for convenience.
// A collision with other container shorthands is very unlikely, but possible.
// In case of a collision a lookup with Runtime.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length container Id.
func (container *Container) ShortId() string {
return utils.TruncateId(container.Id)
func (container *Container) ShortID() string {
return utils.TruncateID(container.ID)
}
func (container *Container) logPath(name string) string {
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name))
}
func (container *Container) ReadLog(name string) (io.Reader, error) {
@ -888,7 +889,7 @@ func (container *Container) rwPath() string {
return path.Join(container.root, "rw")
}
func validateId(id string) error {
func validateID(id string) error {
if id == "" {
return fmt.Errorf("Invalid empty id")
}

View file

@ -14,7 +14,7 @@ import (
"time"
)
func TestIdFormat(t *testing.T) {
func TestIDFormat(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@ -22,19 +22,19 @@ func TestIdFormat(t *testing.T) {
defer nuke(runtime)
container1, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
},
)
if err != nil {
t.Fatal(err)
}
match, err := regexp.Match("^[0-9a-f]{64}$", []byte(container1.Id))
match, err := regexp.Match("^[0-9a-f]{64}$", []byte(container1.ID))
if err != nil {
t.Fatal(err)
}
if !match {
t.Fatalf("Invalid container ID: %s", container1.Id)
t.Fatalf("Invalid container ID: %s", container1.ID)
}
}
@ -46,7 +46,7 @@ func TestMultipleAttachRestart(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c",
"i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"},
},
@ -153,7 +153,7 @@ func TestDiff(t *testing.T) {
// Create a container and remove a file
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
)
@ -194,7 +194,7 @@ func TestDiff(t *testing.T) {
// Create a new container from the commited image
container2, err := builder.Create(
&Config{
Image: img.Id,
Image: img.ID,
Cmd: []string{"cat", "/etc/passwd"},
},
)
@ -217,6 +217,37 @@ func TestDiff(t *testing.T) {
t.Fatalf("/etc/passwd should not be present in the diff after commit.")
}
}
// Create a new containere
container3, err := builder.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"rm", "/bin/httpd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container3)
if err := container3.Run(); err != nil {
t.Fatal(err)
}
// Check the changelog
c, err = container3.Changes()
if err != nil {
t.Fatal(err)
}
success = false
for _, elem := range c {
if elem.Path == "/bin/httpd" && elem.Kind == 2 {
success = true
}
}
if !success {
t.Fatalf("/bin/httpd should be present in the diff after commit.")
}
}
func TestCommitAutoRun(t *testing.T) {
@ -229,7 +260,7 @@ func TestCommitAutoRun(t *testing.T) {
builder := NewBuilder(runtime)
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
},
)
@ -260,7 +291,7 @@ func TestCommitAutoRun(t *testing.T) {
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
container2, err := builder.Create(
&Config{
Image: img.Id,
Image: img.ID,
},
)
if err != nil {
@ -309,7 +340,7 @@ func TestCommitRun(t *testing.T) {
container1, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
},
)
@ -341,7 +372,7 @@ func TestCommitRun(t *testing.T) {
container2, err := builder.Create(
&Config{
Image: img.Id,
Image: img.ID,
Cmd: []string{"cat", "/world"},
},
)
@ -388,7 +419,7 @@ func TestStart(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Memory: 33554432,
CpuShares: 1000,
Cmd: []string{"/bin/cat"},
@ -432,7 +463,7 @@ func TestRun(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@ -460,7 +491,7 @@ func TestOutput(t *testing.T) {
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
)
@ -484,7 +515,7 @@ func TestKillDifferentUser(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"tail", "-f", "/etc/resolv.conf"},
User: "daemon",
},
@ -532,7 +563,7 @@ func TestKill(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
},
)
@ -580,7 +611,7 @@ func TestExitCode(t *testing.T) {
builder := NewBuilder(runtime)
trueContainer, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""},
})
if err != nil {
@ -595,7 +626,7 @@ func TestExitCode(t *testing.T) {
}
falseContainer, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""},
})
if err != nil {
@ -617,7 +648,7 @@ func TestRestart(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
)
@ -650,7 +681,7 @@ func TestRestartStdin(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
@ -732,7 +763,7 @@ func TestUser(t *testing.T) {
// Default user must be root
container, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
},
)
@ -750,7 +781,7 @@ func TestUser(t *testing.T) {
// Set a username
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "root",
@ -770,7 +801,7 @@ func TestUser(t *testing.T) {
// Set a UID
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "0",
@ -790,7 +821,7 @@ func TestUser(t *testing.T) {
// Set a different user by uid
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "1",
@ -812,7 +843,7 @@ func TestUser(t *testing.T) {
// Set a different user by username
container, err = builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "daemon",
@ -841,7 +872,7 @@ func TestMultipleContainers(t *testing.T) {
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
},
)
@ -851,7 +882,7 @@ func TestMultipleContainers(t *testing.T) {
defer runtime.Destroy(container1)
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/dev/zero"},
},
)
@ -897,7 +928,7 @@ func TestStdin(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
@ -944,7 +975,7 @@ func TestTty(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
@ -991,7 +1022,7 @@ func TestEnv(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/usr/bin/env"},
},
)
@ -1069,7 +1100,7 @@ func TestLXCConfig(t *testing.T) {
cpuMax := 10000
cpu := cpuMin + rand.Intn(cpuMax-cpuMin)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true"},
Hostname: "foobar",
@ -1097,7 +1128,7 @@ func BenchmarkRunSequencial(b *testing.B) {
defer nuke(runtime)
for i := 0; i < b.N; i++ {
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
)
@ -1132,7 +1163,7 @@ func BenchmarkRunParallel(b *testing.B) {
tasks = append(tasks, complete)
go func(i int, complete chan error) {
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
)

View file

@ -11,13 +11,13 @@ import (
"time"
)
var DOCKER_PATH string = path.Join(os.Getenv("DOCKERPATH"), "docker")
var DOCKERPATH = path.Join(os.Getenv("DOCKERPATH"), "docker")
// WARNING: this crashTest will 1) crash your host, 2) remove all containers
func runDaemon() (*exec.Cmd, error) {
os.Remove("/var/run/docker.pid")
exec.Command("rm", "-rf", "/var/lib/docker/containers").Run()
cmd := exec.Command(DOCKER_PATH, "-d")
cmd := exec.Command(DOCKERPATH, "-d")
outPipe, err := cmd.StdoutPipe()
if err != nil {
return nil, err
@ -77,7 +77,7 @@ func crashTest() error {
stop = false
for i := 0; i < 100 && !stop; {
func() error {
cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
cmd := exec.Command(DOCKERPATH, "run", "base", "echo", fmt.Sprintf("%d", totalTestCount))
i++
totalTestCount++
outPipe, err := cmd.StdoutPipe()

View file

@ -2,18 +2,15 @@
set -e
# these should match the names found at http://www.debian.org/releases/
stableSuite='squeeze'
testingSuite='wheezy'
stableSuite='wheezy'
testingSuite='jessie'
unstableSuite='sid'
# if suite is equal to this, it gets the "latest" tag
latestSuite="$testingSuite"
variant='minbase'
include='iproute,iputils-ping'
repo="$1"
suite="${2:-$latestSuite}"
suite="${2:-$stableSuite}"
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
if [ ! "$repo" ]; then
@ -41,17 +38,14 @@ img=$(sudo tar -c . | docker import -)
# tag suite
docker tag $img $repo $suite
if [ "$suite" = "$latestSuite" ]; then
# tag latest
docker tag $img $repo latest
fi
# test the image
docker run -i -t $repo:$suite echo success
# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag
if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
# tag the specific version
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
# tag latest
docker tag $img $repo latest
# tag the specific debian release version
ver=$(docker run $repo:$suite cat /etc/debian_version)
docker tag $img $repo $ver
fi

View file

@ -15,7 +15,7 @@ import (
)
var (
GIT_COMMIT string
GITCOMMIT string
)
func main() {
@ -59,7 +59,7 @@ func main() {
if *flDebug {
os.Setenv("DEBUG", "1")
}
docker.GIT_COMMIT = GIT_COMMIT
docker.GITCOMMIT = GITCOMMIT
if *flDaemon {
if flag.NArg() != 0 {
flag.Usage()

View file

@ -6,6 +6,7 @@ SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
PYTHON = python
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
@ -38,6 +39,7 @@ help:
# @echo " linkcheck to check all external links for integrity"
# @echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " docs to build the docs and copy the static files to the outputdir"
@echo " server to serve the docs in your browser under \`http://localhost:8000\`"
@echo " publish to publish the app to dotcloud"
clean:
@ -49,6 +51,8 @@ docs:
@echo
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
server:
@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
site:
cp -r website $(BUILDDIR)/
@ -59,7 +63,7 @@ site:
connect:
@echo connecting dotcloud to www.docker.io website, make sure to use user 1
@cd _build/website/ ; \
dotcloud connect dockerwebsite ;
dotcloud connect dockerwebsite ; \
dotcloud list
push:

View file

@ -14,20 +14,22 @@ Installation
------------
* Work in your own fork of the code, we accept pull requests.
* Install sphinx: ``pip install sphinx``
* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
* Install sphinx: `pip install sphinx`
* Mac OS X: `[sudo] pip-2.7 install sphinx`)
* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
Usage
-----
* change the .rst files with your favorite editor to your liking
* run *make docs* to clean up old files and generate new ones
* your static website can now be found in the _build dir
* to preview what you have generated, cd into _build/html and then run 'python -m SimpleHTTPServer 8000'
* Change the `.rst` files with your favorite editor to your liking.
* Run `make docs` to clean up old files and generate new ones.
* Your static website can now be found in the `_build` directory.
* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser.
Working using github's file editor
Working using GitHub's file editor
----------------------------------
Alternatively, for small changes and typo's you might want to use github's built in file editor. It allows
Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows
you to preview your changes right online. Just be carefull not to create many commits.
Images
@ -72,4 +74,4 @@ Guides on using sphinx
* Code examples
Start without $, so it's easy to copy and paste.
Start without $, so it's easy to copy and paste.

View file

@ -15,10 +15,17 @@ Docker Remote API
- Default port in the docker deamon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
2. Endpoints
2. Version
==========
The current verson of the API is 1.1
Calling /images/<name>/insert is the same as calling /v1.1/images/<name>/insert
You can still call an old version of the api using /v1.0/images/<name>/insert
3. Endpoints
============
2.1 Containers
3.1 Containers
--------------
List containers
@ -132,6 +139,7 @@ Create a container
:jsonparam config: the container's configuration
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 406: impossible to attach (container not running)
:statuscode 500: server error
@ -459,7 +467,7 @@ Remove a container
:statuscode 500: server error
2.2 Images
3.2 Images
----------
List Images
@ -548,7 +556,19 @@ Create an image
POST /images/create?fromImage=base HTTP/1.1
**Example response**:
**Example response v1.1**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"Pulling..."}
{"status":"Pulling", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...
**Example response v1.0**:
.. sourcecode:: http
@ -579,7 +599,19 @@ Insert a file in a image
POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
**Example response**:
**Example response v1.1**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"Inserting..."}
{"status":"Inserting", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...
**Example response v1.0**:
.. sourcecode:: http
@ -694,7 +726,19 @@ Push an image on the registry
POST /images/test/push HTTP/1.1
**Example response**:
**Example response v1.1**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"Pushing..."}
{"status":"Pushing", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...
**Example response v1.0**:
.. sourcecode:: http
@ -800,7 +844,7 @@ Search images
:statuscode 500: server error
2.3 Misc
3.3 Misc
--------
Build an image from Dockerfile via stdin
@ -826,6 +870,7 @@ Build an image from Dockerfile via stdin
{{ STREAM }}
:query t: tag to be applied to the resulting image in case of success
:statuscode 200: no error
:statuscode 500: server error
@ -912,10 +957,12 @@ Display system-wide information
{
"Containers":11,
"Version":"0.2.2",
"Images":16,
"GoVersion":"go1.0.3",
"Debug":false
"Debug":false,
"NFd": 11,
"NGoroutines":21,
"MemoryLimit":true,
"SwapLimit":false
}
:statuscode 200: no error
@ -941,12 +988,11 @@ Show the docker version information
HTTP/1.1 200 OK
Content-Type: application/json
{
"Version":"0.2.2",
"GitCommit":"5a2a5cc+CHANGES",
"MemoryLimit":true,
"SwapLimit":false
"GoVersion":"go1.0.3"
}
:statuscode 200: no error

View file

@ -2,8 +2,8 @@
:description: docker documentation
:keywords: docker, ipa, documentation
API's
=============
APIs
====
This following :

View file

@ -246,7 +246,6 @@ The Index has two main purposes (along with its fancy social features):
- Resolve short names (to avoid passing absolute URLs all the time)
- username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/
- team/projectname -> \https://registry.docker.io/team/<team>/repositories/<projectname>/
- Authenticate a user as a repos owner (for a central referenced repository)
3.1 Without an Index

View file

@ -2,12 +2,27 @@
:description: Build a new image from the Dockerfile passed via stdin
:keywords: build, docker, container, documentation
========================================================
``build`` -- Build a container from Dockerfile via stdin
========================================================
================================================
``build`` -- Build a container from a Dockerfile
================================================
::
Usage: docker build -
Example: cat Dockerfile | docker build -
Build a new image from the Dockerfile passed via stdin
Usage: docker build [OPTIONS] PATH | -
Build a new container image from the source code at PATH
-t="": Tag to be applied to the resulting image in case of success.
Examples
--------
.. code-block:: bash
docker build .
This will take the local Dockerfile
.. code-block:: bash
docker build -
This will read a Dockerfile form Stdin without context

View file

@ -5,8 +5,8 @@
Introduction
============
Docker - The Linux container runtime
------------------------------------
Docker -- The Linux container runtime
-------------------------------------
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.

View file

@ -1,8 +1,8 @@
:title: Setting up a dev environment
:title: Setting Up a Dev Environment
:description: Guides on how to contribute to docker
:keywords: Docker, documentation, developers, contributing, dev environment
Setting up a dev environment
Setting Up a Dev Environment
============================
Instructions that have been verified to work on Ubuntu 12.10,

View file

@ -4,8 +4,8 @@
.. _running_couchdb_service:
Create a CouchDB service
========================
CouchDB Service
===============
.. include:: example_header.inc

View file

@ -1,6 +1,6 @@
:title: Docker Examples
:description: Examples on how to use Docker
:keywords: docker, hello world, examples
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples
@ -16,6 +16,7 @@ Contents:
hello_world
hello_world_daemon
python_web_app
nodejs_web_app
running_redis_service
running_ssh_service
couchdb_data_volumes

View file

@ -0,0 +1,236 @@
:title: Running a Node.js app on CentOS
:description: Installing and running a Node.js app on CentOS
:keywords: docker, example, package installation, node, centos
.. _nodejs_web_app:
Node.js Web App
===============
.. include:: example_header.inc
The goal of this example is to show you how you can build your own docker images
from a parent image using a ``Dockerfile`` . We will do that by making a simple
Node.js hello world web application running on CentOS. You can get the full
source code at https://github.com/gasi/docker-node-hello.
Create Node.js app
++++++++++++++++++
First, create a ``package.json`` file that describes your app and its
dependencies:
.. code-block:: json
{
"name": "docker-centos-hello",
"private": true,
"version": "0.0.1",
"description": "Node.js Hello World app on CentOS using docker",
"author": "Daniel Gasienica <daniel@gasienica.ch>",
"dependencies": {
"express": "3.2.4"
}
}
Then, create an ``index.js`` file that defines a web app using the
`Express.js <http://expressjs.com/>`_ framework:
.. code-block:: javascript
var express = require('express');
// Constants
var PORT = 8080;
// App
var app = express();
app.get('/', function (req, res) {
res.send('Hello World\n');
});
app.listen(PORT)
console.log('Running on http://localhost:' + PORT);
In the next steps, well look at how you can run this app inside a CentOS
container using docker. First, youll need to build a docker image of your app.
Creating a ``Dockerfile``
+++++++++++++++++++++++++
Create an empty file called ``Dockerfile``:
.. code-block:: bash
touch Dockerfile
Open the ``Dockerfile`` in your favorite text editor and add the following line
that defines the version of docker the image requires to build
(this example uses docker 0.3.4):
.. code-block:: bash
# DOCKER-VERSION 0.3.4
Next, define the parent image you want to use to build your own image on top of.
Here, well use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``)
available on the `docker index`_:
.. code-block:: bash
FROM centos:6.4
Since were building a Node.js app, youll have to install Node.js as well as
npm on your CentOS image. Node.js is required to run your app and npm to install
your apps dependencies defined in ``package.json``.
To install the right package for CentOS, well use the instructions from the
`Node.js wiki`_:
.. code-block:: bash
# Enable EPEL for Node.js
RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# Install Node.js and npm
RUN yum install -y npm-1.2.17-5.el6
To bundle your apps source code inside the docker image, use the ``ADD``
command:
.. code-block:: bash
# Bundle app source
ADD . /src
Install your app dependencies using npm:
.. code-block:: bash
# Install app dependencies
RUN cd /src; npm install
Your app binds to port ``8080`` so youll use the ``EXPOSE`` command to have it
mapped by the docker daemon:
.. code-block:: bash
EXPOSE 8080
Last but not least, define the command to run your app using ``CMD`` which
defines your runtime, i.e. ``node``, and the path to our app, i.e.
``src/index.js`` (see the step where we added the source to the container):
.. code-block:: bash
CMD ["node", "/src/index.js"]
Your ``Dockerfile`` should now look like this:
.. code-block:: bash
# DOCKER-VERSION 0.3.4
FROM centos:6.4
# Enable EPEL for Node.js
RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# Install Node.js and npm
RUN yum install -y npm-1.2.17-5.el6
# Bundle app source
ADD . /src
# Install app dependencies
RUN cd /src; npm install
EXPOSE 8080
CMD ["node", "/src/index.js"]
Building your image
+++++++++++++++++++
Go to the directory that has your ``Dockerfile`` and run the following command
to build a docker image. The ``-t`` flag lets you tag your image so its easier
to find later using the ``docker images`` command:
.. code-block:: bash
docker build -t <your username>/centos-node-hello .
Your image will now be listed by docker:
.. code-block:: bash
docker images
> # Example
> REPOSITORY TAG ID CREATED
> centos 6.4 539c0211cd76 8 weeks ago
> gasi/centos-node-hello latest d64d3505b0d2 2 hours ago
Run the image
+++++++++++++
Running your image with ``-d`` runs the container in detached mode, leaving the
container running in the background. Run the image you previously built:
.. code-block:: bash
docker run -d <your username>/centos-node-hello
Print the output of your app:
.. code-block:: bash
# Get container ID
docker ps
# Print app output
docker logs <container id>
> # Example
> Running on http://localhost:8080
Test
++++
To test your app, get the the port of your app that docker mapped:
.. code-block:: bash
docker ps
> # Example
> ID IMAGE COMMAND ... PORTS
> ecce33b30ebf gasi/centos-node-hello:latest node /src/index.js 49160->8080
In the example above, docker mapped the ``8080`` port of the container to
``49160``.
Now you can call your app using ``curl`` (install if needed via:
``sudo apt-get install curl``):
.. code-block:: bash
curl -i localhost:49160
> HTTP/1.1 200 OK
> X-Powered-By: Express
> Content-Type: text/html; charset=utf-8
> Content-Length: 12
> Date: Sun, 02 Jun 2013 03:53:22 GMT
> Connection: keep-alive
>
> Hello World
We hope this tutorial helped you get up and running with Node.js and CentOS on
docker. You can get the full source code at
https://github.com/gasi/docker-node-hello.
Continue to :ref:`running_redis_service`.
.. _Node.js wiki: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#rhelcentosscientific-linux-6
.. _docker index: https://index.docker.io/

View file

@ -4,8 +4,8 @@
.. _python_web_app:
Building a python web app
=========================
Python Web App
==============
.. include:: example_header.inc

View file

@ -4,7 +4,7 @@
.. _running_examples:
Running The Examples
Running the Examples
--------------------
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:

View file

@ -4,8 +4,8 @@
.. _running_redis_service:
Create a redis service
======================
Redis Service
=============
.. include:: example_header.inc
@ -34,7 +34,7 @@ Snapshot the installation
.. code-block:: bash
docker ps -a # grab the container id (this will be the last one in the list)
docker ps -a # grab the container id (this will be the first one in the list)
docker commit <container_id> <your username>/redis
Run the service

View file

@ -4,8 +4,8 @@
.. _running_ssh_service:
Create an ssh daemon service
============================
SSH Daemon Service
==================
.. include:: example_header.inc
@ -20,8 +20,7 @@ minutes and not entirely smooth, but gives you a good idea.
<div style="margin-top:10px;">
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
</div>
You can also get this sshd container by using
::
@ -30,3 +29,49 @@ You can also get this sshd container by using
The password is 'screencast'
**Video's Transcription:**
.. code-block:: bash
# Hello! We are going to try and install openssh on a container and run it as a servic
# let's pull base to get a base ubuntu image.
$ docker pull base
# I had it so it was quick
# now let's connect using -i for interactive and with -t for terminal
# we execute /bin/bash to get a prompt.
$ docker run -i -t base /bin/bash
# now let's commit it
# which container was it?
$ docker ps -a |more
$ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd
# I gave the name dhrp/sshd for the container
# now we can run it again
$ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode
# is it running?
$ docker ps
# yes!
# let's stop it
$ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
$ docker ps
# and reconnect, but now open a port to it
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
# it has now given us a port to connect to
# we have to connect using a public ip of our host
$ hostname
$ ifconfig
$ ssh root@192.168.33.10 -p 49153
# Ah! forgot to set root passwd
$ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd
$ docker ps -a
$ docker run -i -t dhrp/sshd /bin/bash
$ passwd
$ exit
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
$ ifconfig
$ ssh root@192.168.33.10 -p 49154
# Thanks for watching, Thatcher thatcher@dotcloud.com

View file

@ -19,7 +19,8 @@ Most frequently asked questions.
3. **Does Docker run on Mac OS X or Windows?**
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the MacOSX_ and Windows_ installation guides.
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a
virtual machine on your box, and get the best of both worlds. Check out the :ref:`install_using_vagrant` and :ref:`windows` installation guides.
4. **How do containers compare to virtual machines?**
@ -39,10 +40,8 @@ Most frequently asked questions.
* `Ask questions on Stackoverflow`_
* `Join the conversation on Twitter`_
.. _Windows: ../installation/windows/
.. _MacOSX: ../installation/vagrant/
.. _the repo: http://www.github.com/dotcloud/docker
.. _IRC\: docker on freenode: irc://chat.freenode.net#docker
.. _IRC: docker on freenode: docker on freenode: irc://chat.freenode.net#docker
.. _Github: http://www.github.com/dotcloud/docker
.. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
.. _Join the conversation on Twitter: http://twitter.com/getdocker

View file

@ -7,8 +7,8 @@
Introduction
============
Docker - The Linux container runtime
------------------------------------
Docker -- The Linux container runtime
-------------------------------------
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.

View file

@ -92,6 +92,16 @@ have AUFS filesystem support enabled, so we need to install it.
sudo apt-get update
sudo apt-get install linux-image-extra-`uname -r`
**add-apt-repository support**
Some installations of Ubuntu 13.04 require ``software-properties-common`` to be
installed before being able to use add-apt-repository.
.. code-block:: bash
sudo apt-get install software-properties-common
Installation
------------

View file

@ -2,6 +2,7 @@
:description: Docker's tutorial to run docker on Windows
:keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
.. _windows:
Using Vagrant (Windows)
=======================

View file

@ -3,8 +3,8 @@
:keywords: Examples, Usage, basic commands, docker, documentation, examples
The basics
=============
The Basics
==========
Starting Docker
---------------

View file

@ -125,8 +125,14 @@ curl was installed within the image.
.. note::
The path must include the file name.
.. note::
This instruction has temporarily disabled
2.8 ADD
-------
``ADD <src> <dest>``
The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path
of the container.
The context must be set in order to use this instruction. (see examples)
3. Dockerfile Examples
======================

View file

@ -4,8 +4,8 @@
.. _working_with_the_repository:
Working with the repository
============================
Working with the Repository
===========================
Top-level repositories and user repositories
@ -14,9 +14,9 @@ Top-level repositories and user repositories
Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind
Docker, and user repositories.
* Top-level repositories can easily be recognized by not having a / (slash) in their name. These repositories can
* Top-level repositories can easily be recognized by not having a ``/`` (slash) in their name. These repositories can
generally be trusted.
* User repositories always come in the form of <username>/<repo_name>. This is what your published images will look like.
* User repositories always come in the form of ``<username>/<repo_name>``. This is what your published images will look like.
* User images are not checked, it is therefore up to you whether or not you trust the creator of this image.

View file

@ -62,7 +62,7 @@
</div>
<div class="container">
<div class="alert alert-info">
<div class="alert alert-info" style="margin-bottom: 0;">
<strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
</div>
<div class="row">
@ -133,13 +133,13 @@
</section>
<section class="contentblock">
<h2>More resources</h2>
<ul>
<li><a href="irc://chat.freenode.net#docker">IRC: docker on freenode</a></li>
<li><a href="http://www.github.com/dotcloud/docker">Github</a></li>
<li><a href="http://stackoverflow.com/tags/docker/">Ask questions on Stackoverflow</a></li>
<li><a href="http://twitter.com/getdocker/">Join the conversation on Twitter</a></li>
</ul>
<h2>Questions? Want to get in touch?</h2>
<p>There are several ways to get in touch:</p>
<p><strong>Join the discussion on IRC.</strong> We can be found in the <a href="irc://chat.freenode.net#docker">#docker</a> channel on chat.freenode.net</p>
<p><strong>Discussions</strong> happen on our google group: <a href="https://groups.google.com/d/forum/docker-club">docker-club at googlegroups.com</a></p>
<p>All our <strong>development and decisions</strong> are made out in the open on Github <a href="http://www.github.com/dotcloud/docker">github.com/dotcloud/docker</a></p>
<p><strong>Get help on using Docker</strong> by asking on <a href="http://stackoverflow.com/tags/docker/">Stackoverflow</a></p>
<p>And of course, <strong>tweet</strong> your tweets to <a href="http://twitter.com/getdocker/">twitter.com/getdocker</a></p>
</section>

View file

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

View file

@ -86,8 +86,8 @@ func (graph *Graph) Get(name string) (*Image, error) {
if err != nil {
return nil, err
}
if img.Id != id {
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.Id)
if img.ID != id {
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
}
img.graph = graph
if img.Size == 0 {
@ -101,8 +101,8 @@ func (graph *Graph) Get(name string) (*Image, error) {
}
graph.lockSumMap.Lock()
defer graph.lockSumMap.Unlock()
if _, exists := graph.checksumLock[img.Id]; !exists {
graph.checksumLock[img.Id] = &sync.Mutex{}
if _, exists := graph.checksumLock[img.ID]; !exists {
graph.checksumLock[img.ID] = &sync.Mutex{}
}
return img, nil
}
@ -110,16 +110,17 @@ func (graph *Graph) Get(name string) (*Image, error) {
// Create creates a new image and registers it in the graph.
func (graph *Graph) Create(layerData Archive, container *Container, comment, author string, config *Config) (*Image, error) {
img := &Image{
Id: GenerateId(),
ID: GenerateID(),
Comment: comment,
Created: time.Now(),
DockerVersion: VERSION,
Author: author,
Config: config,
Architecture: "x86_64",
}
if container != nil {
img.Parent = container.Image
img.Container = container.Id
img.Container = container.ID
img.ContainerConfig = *container.Config
}
if err := graph.Register(layerData, layerData != nil, img); err != nil {
@ -132,12 +133,12 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
// Register imports a pre-existing image into the graph.
// FIXME: pass img as first argument
func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
if err := ValidateId(img.Id); err != nil {
if err := ValidateID(img.ID); err != nil {
return err
}
// (This is a convenience to save time. Race conditions are taken care of by os.Rename)
if graph.Exists(img.Id) {
return fmt.Errorf("Image %s already exists", img.Id)
if graph.Exists(img.ID) {
return fmt.Errorf("Image %s already exists", img.ID)
}
tmp, err := graph.Mktemp("")
defer os.RemoveAll(tmp)
@ -148,12 +149,12 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
return err
}
// Commit
if err := os.Rename(tmp, graph.imageRoot(img.Id)); err != nil {
if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {
return err
}
img.graph = graph
graph.idIndex.Add(img.Id)
graph.checksumLock[img.Id] = &sync.Mutex{}
graph.idIndex.Add(img.ID)
graph.checksumLock[img.ID] = &sync.Mutex{}
return nil
}
@ -174,13 +175,14 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
if err != nil {
return nil, err
}
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root)
sf := utils.NewStreamFormatter(false)
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
}
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
func (graph *Graph) Mktemp(id string) (string, error) {
if id == "" {
id = GenerateId()
id = GenerateID()
}
tmp, err := graph.tmp()
if err != nil {
@ -237,7 +239,7 @@ func (graph *Graph) Map() (map[string]*Image, error) {
}
images := make(map[string]*Image, len(all))
for _, image := range all {
images[image.Id] = image
images[image.ID] = image
}
return images, nil
}
@ -280,10 +282,10 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) {
if err != nil {
return
}
if children, exists := byParent[parent.Id]; exists {
byParent[parent.Id] = []*Image{image}
if children, exists := byParent[parent.ID]; exists {
byParent[parent.ID] = []*Image{image}
} else {
byParent[parent.Id] = append(children, image)
byParent[parent.ID] = append(children, image)
}
})
return byParent, err
@ -300,8 +302,8 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
err = graph.WalkAll(func(image *Image) {
// If it's not in the byParent lookup table, then
// it's not a parent -> so it's a head!
if _, exists := byParent[image.Id]; !exists {
heads[image.Id] = image
if _, exists := byParent[image.ID]; !exists {
heads[image.ID] = image
}
})
return heads, err
@ -324,11 +326,11 @@ func (graph *Graph) getStoredChecksums() (map[string]string, error) {
}
func (graph *Graph) storeChecksums(checksums map[string]string) error {
checksumJson, err := json.Marshal(checksums)
checksumJSON, err := json.Marshal(checksums)
if err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJson, 0600); err != nil {
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJSON, 0600); err != nil {
return err
}
return nil

View file

@ -34,14 +34,14 @@ func TestInterruptedRegister(t *testing.T) {
defer os.RemoveAll(graph.Root)
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
image := &Image{
Id: GenerateId(),
ID: GenerateID(),
Comment: "testing",
Created: time.Now(),
}
go graph.Register(badArchive, false, image)
time.Sleep(200 * time.Millisecond)
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
if _, err := graph.Get(image.Id); err == nil {
if _, err := graph.Get(image.ID); err == nil {
t.Fatal("Image should not exist after Register is interrupted")
}
// Registering the same image again should succeed if the first register was interrupted
@ -67,7 +67,7 @@ func TestGraphCreate(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := ValidateId(image.Id); err != nil {
if err := ValidateID(image.ID); err != nil {
t.Fatal(err)
}
if image.Comment != "Testing" {
@ -91,7 +91,7 @@ func TestRegister(t *testing.T) {
t.Fatal(err)
}
image := &Image{
Id: GenerateId(),
ID: GenerateID(),
Comment: "testing",
Created: time.Now(),
}
@ -104,11 +104,11 @@ func TestRegister(t *testing.T) {
} else if l := len(images); l != 1 {
t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l)
}
if resultImg, err := graph.Get(image.Id); err != nil {
if resultImg, err := graph.Get(image.ID); err != nil {
t.Fatal(err)
} else {
if resultImg.Id != image.Id {
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.Id, resultImg.Id)
if resultImg.ID != image.ID {
t.Fatalf("Wrong image ID. Should be '%s', not '%s'", image.ID, resultImg.ID)
}
if resultImg.Comment != image.Comment {
t.Fatalf("Wrong image comment. Should be '%s', not '%s'", image.Comment, resultImg.Comment)
@ -156,7 +156,7 @@ func TestDeletePrefix(t *testing.T) {
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
img := createTestImage(graph, t)
if err := graph.Delete(utils.TruncateId(img.Id)); err != nil {
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
@ -187,7 +187,7 @@ func TestDelete(t *testing.T) {
t.Fatal(err)
}
assertNImages(graph, t, 1)
if err := graph.Delete(img.Id); err != nil {
if err := graph.Delete(img.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 0)
@ -201,7 +201,7 @@ func TestDelete(t *testing.T) {
t.Fatal(err)
}
assertNImages(graph, t, 2)
if err := graph.Delete(img1.Id); err != nil {
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)
@ -216,7 +216,7 @@ func TestDelete(t *testing.T) {
if err := graph.Register(archive, false, img1); err != nil {
t.Fatal(err)
}
if err := graph.Delete(img1.Id); err != nil {
if err := graph.Delete(img1.ID); err != nil {
t.Fatal(err)
}
assertNImages(graph, t, 1)

View file

@ -1,23 +1,31 @@
# This will build a container capable of producing an official binary build of docker and
# uploading it to S3
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
from ubuntu:12.10
# Workaround the upstart issue
run dpkg-divert --local --rename --add /sbin/initctl
run ln -s /bin/true /sbin/initctl
# Enable universe and gophers PPA
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
run add-apt-repository -y ppa:gophers/go/ubuntu
run apt-get update
# Packages required to checkout, build and upload docker
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
# Packages required to checkout and build docker
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.linux-amd64.tar.gz
run tar -C /usr/local -xzf /go.tar.gz
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc
run echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
# Packages required to build an ubuntu package
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
copy fake_initctl /usr/local/bin/initctl
run apt-get install -y -q devscripts
add . /src
# Copy dockerbuilder files into the container
add . /src
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
run cp /src/s3cfg /.s3cfg
cmd ["dockerbuilder"]

View file

@ -2,7 +2,7 @@
set -x
set -e
export PATH=$PATH:/usr/local/go/bin
export PATH=/usr/local/go/bin:$PATH
PACKAGE=github.com/dotcloud/docker
@ -36,5 +36,6 @@ else
fi
if [ -z "$NO_UBUNTU" ]; then
export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
(cd packaging/ubuntu && make ubuntu)
fi

View file

@ -1,3 +0,0 @@
#!/bin/sh
echo Whatever you say, man

View file

@ -0,0 +1,2 @@
Ken Cochrane <ken@dotcloud.com>
Jerome Petazzoni <jerome@dotcloud.com>

View file

@ -0,0 +1,5 @@
# Docker project infrastructure
This directory holds all information about the technical infrastructure of the docker project; servers, dns, email, and all the corresponding tools and configuration.
Obviously credentials should not be stored in this repo, but how to obtain and use them should be documented here.

View file

@ -19,7 +19,7 @@ import (
)
type Image struct {
Id string `json:"id"`
ID string `json:"id"`
Parent string `json:"parent,omitempty"`
Comment string `json:"comment,omitempty"`
Created time.Time `json:"created"`
@ -28,6 +28,7 @@ type Image struct {
DockerVersion string `json:"docker_version,omitempty"`
Author string `json:"author,omitempty"`
Config *Config `json:"config,omitempty"`
Architecture string `json:"architecture,omitempty"`
graph *Graph
Size int64
ParentSize int64
@ -44,18 +45,17 @@ func LoadImage(root string) (*Image, error) {
if err := json.Unmarshal(jsonData, img); err != nil {
return nil, err
}
if err := ValidateId(img.Id); err != nil {
if err := ValidateID(img.ID); err != nil {
return nil, err
}
// Check that the filesystem layer exists
if stat, err := os.Stat(layerPath(root)); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.Id)
} else {
return nil, err
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID)
}
return nil, err
} else if !stat.IsDir() {
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.ID, layerPath(root))
}
return img, nil
}
@ -63,7 +63,7 @@ func LoadImage(root string) (*Image, error) {
func StoreImage(img *Image, layerData Archive, root string, store bool) error {
// Check that root doesn't already exist
if _, err := os.Stat(root); err == nil {
return fmt.Errorf("Image %s already exists", img.Id)
return fmt.Errorf("Image %s already exists", img.ID)
} else if !os.IsNotExist(err) {
return err
}
@ -195,11 +195,11 @@ func (image *Image) Changes(rw string) ([]Change, error) {
return Changes(layers, rw)
}
func (image *Image) ShortId() string {
return utils.TruncateId(image.Id)
func (image *Image) ShortID() string {
return utils.TruncateID(image.ID)
}
func ValidateId(id string) error {
func ValidateID(id string) error {
if id == "" {
return fmt.Errorf("Image id can't be empty")
}
@ -209,7 +209,7 @@ func ValidateId(id string) error {
return nil
}
func GenerateId() string {
func GenerateID() string {
id := make([]byte, 32)
_, err := io.ReadFull(rand.Reader, id)
if err != nil {
@ -255,7 +255,7 @@ func (img *Image) layers() ([]string, error) {
return nil, e
}
if len(list) == 0 {
return nil, fmt.Errorf("No layer found for image %s\n", img.Id)
return nil, fmt.Errorf("No layer found for image %s\n", img.ID)
}
return list, nil
}
@ -290,7 +290,7 @@ func (img *Image) root() (string, error) {
if img.graph == nil {
return "", fmt.Errorf("Can't lookup root of unregistered image")
}
return img.graph.imageRoot(img.Id), nil
return img.graph.imageRoot(img.ID), nil
}
// Return the path of an image's layer
@ -303,8 +303,8 @@ func (img *Image) layer() (string, error) {
}
func (img *Image) Checksum() (string, error) {
img.graph.checksumLock[img.Id].Lock()
defer img.graph.checksumLock[img.Id].Unlock()
img.graph.checksumLock[img.ID].Lock()
defer img.graph.checksumLock[img.ID].Unlock()
root, err := img.root()
if err != nil {
@ -315,7 +315,7 @@ func (img *Image) Checksum() (string, error) {
if err != nil {
return "", err
}
if checksum, ok := checksums[img.Id]; ok {
if checksum, ok := checksums[img.ID]; ok {
return checksum, nil
}
@ -366,7 +366,7 @@ func (img *Image) Checksum() (string, error) {
return "", err
}
checksums[img.Id] = hash
checksums[img.ID] = hash
// Dump the checksums to disc
if err := img.graph.storeChecksums(checksums); err != nil {
@ -386,7 +386,7 @@ func (img *Image) getVirtualSize(size int64) int64 {
}
// Build an Image object from raw json data
func NewImgJson(src []byte) (*Image, error) {
func NewImgJSON(src []byte) (*Image, error) {
ret := &Image{}
utils.Debugf("Json string: {%s}\n", src)

View file

@ -19,7 +19,7 @@ lxc.network.flags = up
lxc.network.link = {{.NetworkSettings.Bridge}}
lxc.network.name = eth0
lxc.network.mtu = 1500
lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}}
lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}}
# root filesystem
{{$ROOTFS := .RootfsPath}}

View file

@ -52,7 +52,7 @@ func ipToInt(ip net.IP) int32 {
}
// Converts 32 bit integer into a 4 bytes IP address
func intToIp(n int32) net.IP {
func intToIP(n int32) net.IP {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(n))
return net.IP(b)
@ -132,9 +132,8 @@ func CreateBridgeIface(ifaceName string) error {
}
if ifaceAddr == "" {
return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
} else {
utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
}
utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
@ -258,7 +257,7 @@ func proxy(listener net.Listener, proto, address string) error {
utils.Debugf("Connected to backend, splicing")
splice(src, dst)
}
return nil
panic("Unreachable")
}
func halfSplice(dst, src net.Conn) error {
@ -398,7 +397,7 @@ func (alloc *IPAllocator) run() {
}
}
ip := allocatedIP{ip: intToIp(newNum)}
ip := allocatedIP{ip: intToIP(newNum)}
if inUse {
ip.err = errors.New("No unallocated IP available")
}
@ -465,11 +464,11 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
return nil, err
}
// Allocate a random port if Frontend==0
if extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend); err != nil {
extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend)
if err != nil {
return nil, err
} else {
nat.Frontend = extPort
}
nat.Frontend = extPort
if err := iface.manager.portMapper.Map(nat.Frontend, net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}); err != nil {
iface.manager.portAllocator.Release(nat.Frontend)
return nil, err

View file

@ -137,7 +137,7 @@ func TestConversion(t *testing.T) {
if i == 0 {
t.Fatal("converted to zero")
}
conv := intToIp(i)
conv := intToIP(i)
if !ip.Equal(conv) {
t.Error(conv.String())
}

View file

@ -1,3 +1,14 @@
lxc-docker (0.3.4-1) UNRELEASED; urgency=low
- Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
- Runtime: interactive TTYs correctly handle window resize
- Runtime: fix how configuration is merged between layers
- Remote API: split stdout and stderr on 'docker run'
- Remote API: optionally listen on a different IP and port (use at your own risk)
- Documentation: improved install instructions.
-- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700
lxc-docker (0.3.2-1) UNRELEASED; urgency=low
- Runtime: Store the actual archive on commit
- Registry: Improve the checksum process

View file

@ -1,3 +1,22 @@
lxc-docker (0.4.0-1) precise; urgency=low
- Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Introducing Remote API: control Docker programmatically using a simple HTTP/json API
- Runtime: various reliability and usability improvements
-- dotCloud <ops@dotcloud.com> Mon, 03 Jun 2013 00:00:00 -0700
lxc-docker (0.3.4-1) precise; urgency=low
- Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
- Runtime: interactive TTYs correctly handle window resize
- Runtime: fix how configuration is merged between layers
- Remote API: split stdout and stderr on 'docker run'
- Remote API: optionally listen on a different IP and port (use at your own risk)
- Documentation: improved install instructions.
-- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700
lxc-docker (0.3.3-1) precise; urgency=low
- Registry: Fix push regression
- Various bugfixes

View file

@ -5,6 +5,5 @@ stop on starting rc RUNLEVEL=[016]
respawn
script
# FIXME: docker should not depend on the system having en_US.UTF-8
LC_ALL='en_US.UTF-8' /usr/bin/docker -d
/usr/bin/docker -d
end script

View file

@ -15,7 +15,7 @@ import (
"strings"
)
var ErrAlreadyExists error = errors.New("Image already exists")
var ErrAlreadyExists = errors.New("Image already exists")
func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
for _, cookie := range c.Jar.Cookies(req.URL) {
@ -64,7 +64,11 @@ func (r *Registry) LookupRemoteImage(imgId, registry string, authConfig *auth.Au
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := rt.RoundTrip(req)
return err == nil && res.StatusCode == 307
if err != nil {
return false
}
res.Body.Close()
return res.StatusCode == 307
}
func (r *Registry) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
@ -103,8 +107,8 @@ func (r *Registry) getImagesInRepository(repository string, authConfig *auth.Aut
// Retrieve an image from the Registry.
// Returns the Image object as well as the layer as an Archive (io.Reader)
func (r *Registry) GetRemoteImageJson(imgId, registry string, token []string) ([]byte, error) {
// Get the Json
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, error) {
// Get the JSON
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
if err != nil {
return nil, fmt.Errorf("Failed to download json: %s", err)
@ -152,21 +156,24 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
}
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req)
defer res.Body.Close()
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
if err != nil || (res.StatusCode != 200 && res.StatusCode != 404) {
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 404 {
continue
} else if res.StatusCode == 404 {
return nil, fmt.Errorf("Repository not found")
}
result := make(map[string]string)
rawJson, err := ioutil.ReadAll(res.Body)
rawJSON, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err := json.Unmarshal(rawJson, &result); err != nil {
if err := json.Unmarshal(rawJSON, &result); err != nil {
return nil, err
}
return result, nil
@ -212,19 +219,19 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
return nil, fmt.Errorf("Index response didn't contain any endpoints")
}
checksumsJson, err := ioutil.ReadAll(res.Body)
checksumsJSON, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
remoteChecksums := []*ImgData{}
if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
if err := json.Unmarshal(checksumsJSON, &remoteChecksums); err != nil {
return nil, err
}
// Forge a better object from the retrieved data
imgsData := make(map[string]*ImgData)
for _, elem := range remoteChecksums {
imgsData[elem.Id] = elem
imgsData[elem.ID] = elem
}
return &RepositoryData{
@ -235,10 +242,10 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
}
// Push a local image to the registry
func (r *Registry) PushImageJsonRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
registry = "https://" + registry + "/v1"
// FIXME: try json with UTF8
req, err := http.NewRequest("PUT", registry+"/images/"+imgData.Id+"/json", strings.NewReader(string(jsonRaw)))
req, err := http.NewRequest("PUT", registry+"/images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw)))
if err != nil {
return err
}
@ -246,7 +253,7 @@ func (r *Registry) PushImageJsonRegistry(imgData *ImgData, jsonRaw []byte, regis
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
utils.Debugf("Setting checksum for %s: %s", imgData.Id, imgData.Checksum)
utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum)
res, err := doWithCookies(r.client, req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
@ -321,8 +328,8 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
return nil
}
func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validate bool) (*RepositoryData, error) {
imgListJson, err := json.Marshal(imgList)
func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool) (*RepositoryData, error) {
imgListJSON, err := json.Marshal(imgList)
if err != nil {
return nil, err
}
@ -331,14 +338,14 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
suffix = "images"
}
utils.Debugf("Image list pushed to index:\n%s\n", imgListJson)
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJson))
req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.ContentLength = int64(len(imgListJSON))
req.Header.Set("X-Docker-Token", "true")
res, err := r.client.Do(req)
@ -350,12 +357,12 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
// Redirect if necessary
for res.StatusCode >= 300 && res.StatusCode < 400 {
utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.ContentLength = int64(len(imgListJSON))
req.Header.Set("X-Docker-Token", "true")
res, err = r.client.Do(req)
@ -389,11 +396,11 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
}
if validate {
if res.StatusCode != 204 {
if errBody, err := ioutil.ReadAll(res.Body); err != nil {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
} else {
return nil, fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody)
}
return nil, fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody)
}
}
@ -456,7 +463,7 @@ type RepositoryData struct {
}
type ImgData struct {
Id string `json:"id"`
ID string `json:"id"`
Checksum string `json:"checksum,omitempty"`
Tag string `json:",omitempty"`
}
@ -470,9 +477,16 @@ func NewRegistry(root string) *Registry {
// If the auth file does not exist, keep going
authConfig, _ := auth.LoadConfig(root)
httpTransport := &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
}
r := &Registry{
authConfig: authConfig,
client: &http.Client{},
client: &http.Client{
Transport: httpTransport,
},
}
r.client.Jar = cookiejar.NewCookieJar()
return r

View file

@ -51,7 +51,7 @@ func (runtime *Runtime) List() []*Container {
func (runtime *Runtime) getContainerElement(id string) *list.Element {
for e := runtime.containers.Front(); e != nil; e = e.Next() {
container := e.Value.(*Container)
if container.Id == id {
if container.ID == id {
return e
}
}
@ -83,8 +83,8 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if err := container.FromDisk(); err != nil {
return nil, err
}
if container.Id != id {
return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
if container.ID != id {
return container, fmt.Errorf("Container %s is stored at %s", container.ID, id)
}
if container.State.Running {
container.State.Ghost = true
@ -95,12 +95,12 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
return container, nil
}
// Register makes a container object usable by the runtime as <container.Id>
// Register makes a container object usable by the runtime as <container.ID>
func (runtime *Runtime) Register(container *Container) error {
if container.runtime != nil || runtime.Exists(container.Id) {
if container.runtime != nil || runtime.Exists(container.ID) {
return fmt.Errorf("Container is already loaded")
}
if err := validateId(container.Id); err != nil {
if err := validateID(container.ID); err != nil {
return err
}
@ -123,7 +123,7 @@ func (runtime *Runtime) Register(container *Container) error {
}
// done
runtime.containers.PushBack(container)
runtime.idIndex.Add(container.Id)
runtime.idIndex.Add(container.ID)
// When we actually restart, Start() do the monitoring.
// However, when we simply 'reattach', we have to restart a monitor
@ -133,25 +133,25 @@ func (runtime *Runtime) Register(container *Container) error {
// if so, then we need to restart monitor and init a new lock
// If the container is supposed to be running, make sure of it
if container.State.Running {
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
if err != nil {
return err
} else {
if !strings.Contains(string(output), "RUNNING") {
utils.Debugf("Container %s was supposed to be running be is not.", container.Id)
if runtime.autoRestart {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
if err := container.Start(); err != nil {
return err
}
nomonitor = true
} else {
utils.Debugf("Marking as stopped")
container.State.setStopped(-127)
if err := container.ToDisk(); err != nil {
return err
}
}
if !strings.Contains(string(output), "RUNNING") {
utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
if runtime.autoRestart {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
if err := container.Start(); err != nil {
return err
}
nomonitor = true
} else {
utils.Debugf("Marking as stopped")
container.State.setStopped(-127)
if err := container.ToDisk(); err != nil {
return err
}
}
}
@ -182,9 +182,9 @@ func (runtime *Runtime) Destroy(container *Container) error {
return fmt.Errorf("The given container is <nil>")
}
element := runtime.getContainerElement(container.Id)
element := runtime.getContainerElement(container.ID)
if element == nil {
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID)
}
if err := container.Stop(3); err != nil {
@ -194,14 +194,14 @@ func (runtime *Runtime) Destroy(container *Container) error {
return err
} else if mounted {
if err := container.Unmount(); err != nil {
return fmt.Errorf("Unable to unmount container %v: %v", container.Id, err)
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err)
}
}
// Deregister the container before removing its directory, to avoid race conditions
runtime.idIndex.Delete(container.Id)
runtime.idIndex.Delete(container.ID)
runtime.containers.Remove(element)
if err := os.RemoveAll(container.root); err != nil {
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.Id, err)
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
}
return nil
}
@ -218,7 +218,7 @@ func (runtime *Runtime) restore() error {
utils.Debugf("Failed to load container %v: %v", id, err)
continue
}
utils.Debugf("Loaded container %v", container.Id)
utils.Debugf("Loaded container %v", container.ID)
}
return nil
}

View file

@ -68,7 +68,7 @@ func init() {
runtime: runtime,
}
// Retrieve the Image
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, false); err != nil {
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false)); err != nil {
panic(err)
}
}
@ -120,7 +120,7 @@ func TestRuntimeCreate(t *testing.T) {
builder := NewBuilder(runtime)
container, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@ -140,29 +140,29 @@ func TestRuntimeCreate(t *testing.T) {
}
// Make sure the container List() returns is the right one
if runtime.List()[0].Id != container.Id {
if runtime.List()[0].ID != container.ID {
t.Errorf("Unexpected container %v returned by List", runtime.List()[0])
}
// Make sure we can get the container with Get()
if runtime.Get(container.Id) == nil {
if runtime.Get(container.ID) == nil {
t.Errorf("Unable to get newly created container")
}
// Make sure it is the right container
if runtime.Get(container.Id) != container {
if runtime.Get(container.ID) != container {
t.Errorf("Get() returned the wrong container")
}
// Make sure Exists returns it as existing
if !runtime.Exists(container.Id) {
if !runtime.Exists(container.ID) {
t.Errorf("Exists() returned false for a newly created container")
}
// Make sure crete with bad parameters returns an error
_, err = builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
},
)
if err == nil {
@ -171,7 +171,7 @@ func TestRuntimeCreate(t *testing.T) {
_, err = builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{},
},
)
@ -187,7 +187,7 @@ func TestDestroy(t *testing.T) {
}
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@ -210,7 +210,7 @@ func TestDestroy(t *testing.T) {
}
// Make sure runtime.Get() refuses to return the unexisting container
if runtime.Get(container.Id) != nil {
if runtime.Get(container.ID) != nil {
t.Errorf("Unable to get newly created container")
}
@ -237,7 +237,7 @@ func TestGet(t *testing.T) {
builder := NewBuilder(runtime)
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@ -247,7 +247,7 @@ func TestGet(t *testing.T) {
defer runtime.Destroy(container1)
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@ -257,7 +257,7 @@ func TestGet(t *testing.T) {
defer runtime.Destroy(container2)
container3, err := builder.Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "-al"},
},
)
@ -266,16 +266,16 @@ func TestGet(t *testing.T) {
}
defer runtime.Destroy(container3)
if runtime.Get(container1.Id) != container1 {
t.Errorf("Get(test1) returned %v while expecting %v", runtime.Get(container1.Id), container1)
if runtime.Get(container1.ID) != container1 {
t.Errorf("Get(test1) returned %v while expecting %v", runtime.Get(container1.ID), container1)
}
if runtime.Get(container2.Id) != container2 {
t.Errorf("Get(test2) returned %v while expecting %v", runtime.Get(container2.Id), container2)
if runtime.Get(container2.ID) != container2 {
t.Errorf("Get(test2) returned %v while expecting %v", runtime.Get(container2.ID), container2)
}
if runtime.Get(container3.Id) != container3 {
t.Errorf("Get(test3) returned %v while expecting %v", runtime.Get(container3.Id), container3)
if runtime.Get(container3.ID) != container3 {
t.Errorf("Get(test3) returned %v while expecting %v", runtime.Get(container3.ID), container3)
}
}
@ -283,7 +283,7 @@ func TestGet(t *testing.T) {
func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
strPort := strconv.Itoa(port)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
PortSpecs: []string{strPort},
},
@ -379,7 +379,7 @@ func TestRestore(t *testing.T) {
// Create a container with one instance of docker
container1, err := builder.Create(&Config{
Image: GetTestImage(runtime1).Id,
Image: GetTestImage(runtime1).ID,
Cmd: []string{"ls", "-al"},
},
)
@ -390,7 +390,7 @@ func TestRestore(t *testing.T) {
// Create a second container meant to be killed
container2, err := builder.Create(&Config{
Image: GetTestImage(runtime1).Id,
Image: GetTestImage(runtime1).ID,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
@ -406,7 +406,7 @@ func TestRestore(t *testing.T) {
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
@ -426,7 +426,7 @@ func TestRestore(t *testing.T) {
}
if !container2.State.Running {
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
// Here are are simulating a docker restart - that is, reloading all containers
@ -442,14 +442,14 @@ func TestRestore(t *testing.T) {
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
t.Errorf("Running container found: %v (%v)", c.Id, c.Path)
t.Errorf("Running container found: %v (%v)", c.ID, c.Path)
runningCount++
}
}
if runningCount != 0 {
t.Fatalf("Expected 0 container alive, %d found", runningCount)
}
container3 := runtime2.Get(container1.Id)
container3 := runtime2.Get(container1.ID)
if container3 == nil {
t.Fatal("Unable to Get container")
}

204
server.go
View file

@ -16,8 +16,12 @@ import (
"strings"
)
func (srv *Server) DockerVersion() ApiVersion {
return ApiVersion{VERSION, GIT_COMMIT, srv.runtime.capabilities.MemoryLimit, srv.runtime.capabilities.SwapLimit}
func (srv *Server) DockerVersion() APIVersion {
return APIVersion{
Version: VERSION,
GitCommit: GITCOMMIT,
GoVersion: runtime.Version(),
}
}
func (srv *Server) ContainerKill(name string) error {
@ -48,16 +52,16 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
return fmt.Errorf("No such container: %s", name)
}
func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
results, err := registry.NewRegistry(srv.runtime.root).SearchRepositories(term)
if err != nil {
return nil, err
}
var outs []ApiSearch
var outs []APISearch
for _, repo := range results.Results {
var out ApiSearch
var out APISearch
out.Description = repo["description"]
if len(out.Description) > 45 {
out.Description = utils.Trunc(out.Description, 42) + "..."
@ -68,7 +72,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
return outs, nil
}
func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) {
func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
out = utils.NewWriteFlusher(out)
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
@ -81,7 +85,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
}
defer file.Body.Close()
config, _, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, srv.runtime.capabilities)
config, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
if err != nil {
return "", err
}
@ -92,7 +96,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
return "", err
}
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
return "", err
}
// FIXME: Handle custom repo, tag comment, author
@ -100,8 +104,8 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
if err != nil {
return "", err
}
fmt.Fprintf(out, "%s\n", img.Id)
return img.ShortId(), nil
out.Write(sf.FormatStatus(img.ID))
return img.ShortID(), nil
}
func (srv *Server) ImagesViz(out io.Writer) error {
@ -121,9 +125,9 @@ func (srv *Server) ImagesViz(out io.Writer) error {
return fmt.Errorf("Error while getting parent image: %v", err)
}
if parentImage != nil {
out.Write([]byte(" \"" + parentImage.ShortId() + "\" -> \"" + image.ShortId() + "\"\n"))
out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n"))
} else {
out.Write([]byte(" base -> \"" + image.ShortId() + "\" [style=invis]\n"))
out.Write([]byte(" base -> \"" + image.ShortID() + "\" [style=invis]\n"))
}
}
@ -131,7 +135,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
for name, repository := range srv.runtime.repositories.Repositories {
for tag, id := range repository {
reporefs[utils.TruncateId(id)] = append(reporefs[utils.TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
reporefs[utils.TruncateID(id)] = append(reporefs[utils.TruncateID(id)], fmt.Sprintf("%s:%s", name, tag))
}
}
@ -142,7 +146,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
return nil
}
func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
var (
allImages map[string]*Image
err error
@ -155,13 +159,13 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
if err != nil {
return nil, err
}
outs := []ApiImages{} //produce [] when empty instead of 'null'
outs := []APIImages{} //produce [] when empty instead of 'null'
for name, repository := range srv.runtime.repositories.Repositories {
if filter != "" && name != filter {
continue
}
for tag, id := range repository {
var out ApiImages
var out APIImages
image, err := srv.runtime.graph.Get(id)
if err != nil {
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
@ -170,7 +174,7 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
delete(allImages, id)
out.Repository = name
out.Tag = tag
out.Id = image.Id
out.ID = image.ID
out.Created = image.Created.Unix()
out.Size = image.Size
out.ParentSize = image.getVirtualSize(0)
@ -180,8 +184,8 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
// Display images which aren't part of a
if filter == "" {
for _, image := range allImages {
var out ApiImages
out.Id = image.Id
var out APIImages
out.ID = image.ID
out.Created = image.Created.Unix()
out.Size = image.Size
out.ParentSize = image.getVirtualSize(0)
@ -191,7 +195,7 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
return outs, nil
}
func (srv *Server) DockerInfo() ApiInfo {
func (srv *Server) DockerInfo() *APIInfo {
images, _ := srv.runtime.graph.All()
var imgcount int
if images == nil {
@ -199,29 +203,27 @@ func (srv *Server) DockerInfo() ApiInfo {
} else {
imgcount = len(images)
}
var out ApiInfo
out.Containers = len(srv.runtime.List())
out.Version = VERSION
out.Images = imgcount
out.GoVersion = runtime.Version()
if os.Getenv("DEBUG") != "" {
out.Debug = true
out.NFd = utils.GetTotalUsedFds()
out.NGoroutines = runtime.NumGoroutine()
return &APIInfo{
Containers: len(srv.runtime.List()),
Images: imgcount,
MemoryLimit: srv.runtime.capabilities.MemoryLimit,
SwapLimit: srv.runtime.capabilities.SwapLimit,
Debug: os.Getenv("DEBUG") != "",
NFd: utils.GetTotalUsedFds(),
NGoroutines: runtime.NumGoroutine(),
}
return out
}
func (srv *Server) ImageHistory(name string) ([]ApiHistory, error) {
func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
image, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return nil, err
}
var outs []ApiHistory = []ApiHistory{} //produce [] when empty instead of 'null'
outs := []APIHistory{} //produce [] when empty instead of 'null'
err = image.WalkHistory(func(img *Image) error {
var out ApiHistory
out.Id = srv.runtime.repositories.ImageName(img.ShortId())
var out APIHistory
out.ID = srv.runtime.repositories.ImageName(img.ShortID())
out.Created = img.Created.Unix()
out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
outs = append(outs, out)
@ -238,17 +240,17 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) {
return nil, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) Containers(all bool, n int, since, before string) []ApiContainers {
func (srv *Server) Containers(all bool, n int, since, before string) []APIContainers {
var foundBefore bool
var displayed int
retContainers := []ApiContainers{}
retContainers := []APIContainers{}
for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
continue
}
if before != "" {
if container.ShortId() == before {
if container.ShortID() == before {
foundBefore = true
continue
}
@ -259,13 +261,13 @@ func (srv *Server) Containers(all bool, n int, since, before string) []ApiContai
if displayed == n {
break
}
if container.ShortId() == since {
if container.ShortID() == since {
break
}
displayed++
c := ApiContainers{
Id: container.Id,
c := APIContainers{
ID: container.ID,
}
c.Image = srv.runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
@ -288,7 +290,7 @@ func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, conf
if err != nil {
return "", err
}
return img.ShortId(), err
return img.ShortID(), err
}
func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
@ -298,7 +300,7 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
return nil
}
func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, json bool) error {
func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, sf *utils.StreamFormatter) error {
history, err := r.GetRemoteHistory(imgId, endpoint, token)
if err != nil {
return err
@ -308,24 +310,25 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
// FIXME: Launch the getRemoteImage() in goroutines
for _, id := range history {
if !srv.runtime.graph.Exists(id) {
fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
imgJson, err := r.GetRemoteImageJson(id, endpoint, token)
out.Write(sf.FormatStatus("Pulling %s metadata", id))
imgJSON, err := r.GetRemoteImageJSON(id, endpoint, token)
if err != nil {
// FIXME: Keep goging in case of error?
return err
}
img, err := NewImgJson(imgJson)
img, err := NewImgJSON(imgJSON)
if err != nil {
return fmt.Errorf("Failed to parse json: %s", err)
}
// Get the layer
fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token)
out.Write(sf.FormatStatus("Pulling %s fs layer", id))
layer, contentLength, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
if err != nil {
return err
}
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
defer layer.Close()
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
return err
}
}
@ -333,8 +336,8 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
return nil
}
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, json bool) error {
fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, sf *utils.StreamFormatter) error {
out.Write(sf.FormatStatus("Pulling repository %s from %s", remote, auth.IndexServerAddress()))
repoData, err := r.GetRepositoryData(remote)
if err != nil {
return err
@ -359,23 +362,23 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
}
} else {
// Otherwise, check that the tag exists and use only that one
if id, exists := tagsList[askedTag]; !exists {
id, exists := tagsList[askedTag]
if !exists {
return fmt.Errorf("Tag %s not found in repositoy %s", askedTag, remote)
} else {
repoData.ImgList[id].Tag = askedTag
}
repoData.ImgList[id].Tag = askedTag
}
for _, img := range repoData.ImgList {
if askedTag != "" && img.Tag != askedTag {
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
}
fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, remote))
success := false
for _, ep := range repoData.Endpoints {
if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
if err := srv.pullImage(r, out, img.ID, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
continue
}
success = true
@ -400,17 +403,17 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
return nil
}
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, json bool) error {
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
r := registry.NewRegistry(srv.runtime.root)
out = utils.NewWriteFlusher(out)
if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, json); err != nil {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
return err
}
return nil
}
if err := srv.pullRepository(r, out, name, tag, json); err != nil {
if err := srv.pullRepository(r, out, name, tag, sf); err != nil {
return err
}
@ -464,16 +467,16 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
return nil, err
}
img.WalkHistory(func(img *Image) error {
if _, exists := imageSet[img.Id]; exists {
if _, exists := imageSet[img.ID]; exists {
return nil
}
imageSet[img.Id] = struct{}{}
checksum, err := srv.getChecksum(img.Id)
imageSet[img.ID] = struct{}{}
checksum, err := srv.getChecksum(img.ID)
if err != nil {
return err
}
imgList = append([]*registry.ImgData{{
Id: img.Id,
ID: img.ID,
Checksum: checksum,
Tag: tag,
}}, imgList...)
@ -483,52 +486,52 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
return imgList, nil
}
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string) error {
func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out)
fmt.Fprintf(out, "Processing checksums\n")
out.Write(sf.FormatStatus("Processing checksums"))
imgList, err := srv.getImageList(localRepo)
if err != nil {
return err
}
fmt.Fprintf(out, "Sending images list\n")
out.Write(sf.FormatStatus("Sending image list"))
repoData, err := r.PushImageJsonIndex(name, imgList, false)
repoData, err := r.PushImageJSONIndex(name, imgList, false)
if err != nil {
return err
}
for _, ep := range repoData.Endpoints {
fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo))
out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
// For each image within the repo, push them
for _, elem := range imgList {
if _, exists := repoData.ImgList[elem.Id]; exists {
fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
if _, exists := repoData.ImgList[elem.ID]; exists {
out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
continue
}
if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens); err != nil {
if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil {
// FIXME: Continue on error?
return err
}
fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/users/"+name+"/"+elem.Tag))
if err := r.PushRegistryTag(name, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
return err
}
}
}
if _, err := r.PushImageJsonIndex(name, imgList, true); err != nil {
if _, err := r.PushImageJSONIndex(name, imgList, true); err != nil {
return err
}
return nil
}
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string) error {
func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out)
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
if err != nil {
return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
}
fmt.Fprintf(out, "Pushing %s\r\n", imgId)
out.Write(sf.FormatStatus("Pushing %s", imgId))
// Make sure we have the image's checksum
checksum, err := srv.getChecksum(imgId)
@ -536,14 +539,14 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
return err
}
imgData := &registry.ImgData{
Id: imgId,
ID: imgId,
Checksum: checksum,
}
// Send the json
if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
if err == registry.ErrAlreadyExists {
fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id)
out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.ID))
return nil
}
return err
@ -576,22 +579,22 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
}
// Send the layer
if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%v/%v (%v)"), sf), ep, token); err != nil {
return err
}
return nil
}
func (srv *Server) ImagePush(name, endpoint string, out io.Writer) error {
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root)
if err != nil {
fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
// If it fails, try to get the repository
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
if err := srv.pushRepository(r, out, name, localRepo); err != nil {
if err := srv.pushRepository(r, out, name, localRepo, sf); err != nil {
return err
}
return nil
@ -599,14 +602,14 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer) error {
return err
}
fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
if err := srv.pushImage(r, out, name, img.Id, endpoint, nil); err != nil {
out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
if err := srv.pushImage(r, out, name, img.ID, endpoint, nil, sf); err != nil {
return err
}
return nil
}
func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer, sf *utils.StreamFormatter) error {
var archive io.Reader
var resp *http.Response
@ -615,21 +618,21 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
} else {
u, err := url.Parse(src)
if err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
return err
}
if u.Scheme == "" {
u.Scheme = "http"
u.Host = src
u.Path = ""
}
fmt.Fprintf(out, "Downloading from %s\n", u)
out.Write(sf.FormatStatus("Downloading from %s", u))
// Download with curl (pretty progress bar)
// If curl is not available, fallback to http.Get()
resp, err = utils.Download(u.String(), out)
if err != nil {
return err
}
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false)
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
}
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
if err != nil {
@ -637,11 +640,11 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
}
// Optionally register the image at REPO/TAG
if repo != "" {
if err := srv.runtime.repositories.Set(repo, tag, img.Id, true); err != nil {
if err := srv.runtime.repositories.Set(repo, tag, img.ID, true); err != nil {
return err
}
}
fmt.Fprintf(out, "%s\n", img.ShortId())
out.Write(sf.FormatStatus(img.ShortID()))
return nil
}
@ -662,7 +665,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
}
return "", err
}
return container.ShortId(), nil
return container.ShortID(), nil
}
func (srv *Server) ContainerRestart(name string, t int) error {
@ -699,7 +702,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
for volumeId := range volumes {
// If the requested volu
if c, exists := usedVolumes[volumeId]; exists {
log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.ID)
continue
}
if err := srv.runtime.volumes.Delete(volumeId); err != nil {
@ -717,10 +720,9 @@ func (srv *Server) ImageDelete(name string) error {
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return fmt.Errorf("No such image: %s", name)
} else {
if err := srv.runtime.graph.Delete(img.Id); err != nil {
return fmt.Errorf("Error deleting image %s: %s", name, err.Error())
}
}
if err := srv.runtime.graph.Delete(img.ID); err != nil {
return fmt.Errorf("Error deleting image %s: %s", name, err.Error())
}
return nil
}
@ -739,7 +741,7 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
if _, exists := imageMap[img.Parent]; !exists {
imageMap[img.Parent] = make(map[string]struct{})
}
imageMap[img.Parent][img.Id] = struct{}{}
imageMap[img.Parent][img.ID] = struct{}{}
}
// Loop on the children of the given image and check the config
@ -796,7 +798,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container == nil {
return fmt.Errorf("No such container: %s", name)
}
//logs
if logs {
if stdout {
@ -822,6 +823,9 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container.State.Ghost {
return fmt.Errorf("Impossible to attach to a ghost container")
}
if !container.State.Running {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
var (
cStdin io.ReadCloser

View file

@ -13,7 +13,7 @@ func TestCreateRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "echo test"}, nil)
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
@ -46,7 +46,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "/bin/cat"}, nil)
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}

26
tags.go
View file

@ -11,7 +11,7 @@ import (
"strings"
)
const DEFAULT_TAG = "latest"
const DEFAULTTAG = "latest"
type TagStore struct {
path string
@ -72,7 +72,7 @@ func (store *TagStore) LookupImage(name string) (*Image, error) {
// (so we can pass all errors here)
repoAndTag := strings.SplitN(name, ":", 2)
if len(repoAndTag) == 1 {
repoAndTag = append(repoAndTag, DEFAULT_TAG)
repoAndTag = append(repoAndTag, DEFAULTTAG)
}
if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil {
return nil, err
@ -87,27 +87,27 @@ func (store *TagStore) LookupImage(name string) (*Image, error) {
// Return a reverse-lookup table of all the names which refer to each image
// Eg. {"43b5f19b10584": {"base:latest", "base:v1"}}
func (store *TagStore) ById() map[string][]string {
byId := make(map[string][]string)
func (store *TagStore) ByID() map[string][]string {
byID := make(map[string][]string)
for repoName, repository := range store.Repositories {
for tag, id := range repository {
name := repoName + ":" + tag
if _, exists := byId[id]; !exists {
byId[id] = []string{name}
if _, exists := byID[id]; !exists {
byID[id] = []string{name}
} else {
byId[id] = append(byId[id], name)
sort.Strings(byId[id])
byID[id] = append(byID[id], name)
sort.Strings(byID[id])
}
}
}
return byId
return byID
}
func (store *TagStore) ImageName(id string) string {
if names, exists := store.ById()[id]; exists && len(names) > 0 {
if names, exists := store.ByID()[id]; exists && len(names) > 0 {
return names[0]
}
return utils.TruncateId(id)
return utils.TruncateID(id)
}
func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
@ -116,7 +116,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
return err
}
if tag == "" {
tag = DEFAULT_TAG
tag = DEFAULTTAG
}
if err := validateRepoName(repoName); err != nil {
return err
@ -137,7 +137,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
}
store.Repositories[repoName] = repo
}
repo[tag] = img.Id
repo[tag] = img.ID
return store.Save()
}

2
term/MAINTAINERS Normal file
View file

@ -0,0 +1,2 @@
Guillaume Charmes <guillaume@dotcloud.com>
Solomon Hykes <solomon@dotcloud.com>

View file

@ -7,104 +7,6 @@ import (
"unsafe"
)
type Termios struct {
Iflag uintptr
Oflag uintptr
Cflag uintptr
Lflag uintptr
Cc [20]byte
Ispeed uintptr
Ospeed uintptr
}
const (
// Input flags
inpck = 0x010
istrip = 0x020
icrnl = 0x100
ixon = 0x200
// Output flags
opost = 0x1
// Control flags
cs8 = 0x300
// Local flags
icanon = 0x100
iexten = 0x400
)
const (
HUPCL = 0x4000
ICANON = 0x100
ICRNL = 0x100
IEXTEN = 0x400
BRKINT = 0x2
CFLUSH = 0xf
CLOCAL = 0x8000
CREAD = 0x800
CS5 = 0x0
CS6 = 0x100
CS7 = 0x200
CS8 = 0x300
CSIZE = 0x300
CSTART = 0x11
CSTATUS = 0x14
CSTOP = 0x13
CSTOPB = 0x400
CSUSP = 0x1a
IGNBRK = 0x1
IGNCR = 0x80
IGNPAR = 0x4
IMAXBEL = 0x2000
INLCR = 0x40
INPCK = 0x10
ISIG = 0x80
ISTRIP = 0x20
IUTF8 = 0x4000
IXANY = 0x800
IXOFF = 0x400
IXON = 0x200
NOFLSH = 0x80000000
OCRNL = 0x10
OFDEL = 0x20000
OFILL = 0x80
ONLCR = 0x2
ONLRET = 0x40
ONOCR = 0x20
ONOEOT = 0x8
OPOST = 0x1
RENB = 0x1000
PARMRK = 0x8
PARODD = 0x2000
TOSTOP = 0x400000
VDISCARD = 0xf
VDSUSP = 0xb
VEOF = 0x0
VEOL = 0x1
VEOL2 = 0x2
VERASE = 0x3
VINTR = 0x8
VKILL = 0x5
VLNEXT = 0xe
VMIN = 0x10
VQUIT = 0x9
VREPRINT = 0x6
VSTART = 0xc
VSTATUS = 0x12
VSTOP = 0xd
VSUSP = 0xa
VT0 = 0x0
VT1 = 0x10000
VTDLY = 0x10000
VTIME = 0x11
ECHO = 0x00000008
PENDIN = 0x20000000
)
type State struct {
termios Termios
}
@ -128,21 +30,21 @@ func SetWinsize(fd uintptr, ws *Winsize) error {
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
func IsTerminal(fd uintptr) bool {
var termios Termios
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)))
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&termios)))
return err == 0
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd int, state *State) error {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
func Restore(fd uintptr, state *State) error {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
return err
}
func SetRawTerminal() (*State, error) {
oldState, err := MakeRaw(int(os.Stdin.Fd()))
oldState, err := MakeRaw(os.Stdin.Fd())
if err != nil {
return nil, err
}
@ -150,12 +52,12 @@ func SetRawTerminal() (*State, error) {
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
Restore(int(os.Stdin.Fd()), oldState)
Restore(os.Stdin.Fd(), oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *State) {
Restore(int(os.Stdin.Fd()), state)
Restore(os.Stdin.Fd(), state)
}

View file

@ -8,23 +8,45 @@ import (
const (
getTermios = syscall.TIOCGETA
setTermios = syscall.TIOCSETA
ECHO = 0x00000008
ONLCR = 0x2
ISTRIP = 0x20
INLCR = 0x40
ISIG = 0x80
IGNCR = 0x80
ICANON = 0x100
ICRNL = 0x100
IXOFF = 0x400
IXON = 0x200
)
type Termios struct {
Iflag uint64
Oflag uint64
Cflag uint64
Lflag uint64
Cc [20]byte
Ispeed uint64
Ospeed uint64
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
func MakeRaw(fd uintptr) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF)
newState.Iflag |= ICRNL
newState.Oflag |= ONLCR
newState.Lflag &^= ECHO | ICANON | ISIG
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
newState.Lflag &^= (ECHO | ICANON | ISIG)
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
return nil, err
}

View file

@ -5,54 +5,40 @@ import (
"unsafe"
)
// #include <termios.h>
// #include <sys/ioctl.h>
/*
void MakeRaw(int fd) {
struct termios t;
// FIXME: Handle errors?
ioctl(fd, TCGETS, &t);
t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
t.c_oflag &= ~OPOST;
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= CS8;
ioctl(fd, TCSETS, &t);
}
*/
import "C"
const (
getTermios = syscall.TCGETS
setTermios = syscall.TCSETS
)
type Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]byte
Ispeed uint32
Ospeed uint32
}
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd int) (*State, error) {
func MakeRaw(fd uintptr) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TCGETS, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
return nil, err
}
newState := oldState.termios
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
newState.Oflag &^= syscall.OPOST
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
newState.Cflag |= syscall.CS8
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
return nil, err
}
C.MakeRaw(C.int(fd))
return &oldState, nil
// FIXME: post on goland issues this: very same as the C function bug non-working
// newState := oldState.termios
// newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
// newState.Oflag &^= OPOST
// newState.Lflag &^= (ECHO | syscall.ECHONL | ICANON | ISIG | IEXTEN)
// newState.Cflag &^= (CSIZE | syscall.PARENB)
// newState.Cflag |= CS8
// if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TCSETS, uintptr(unsafe.Pointer(&newState))); err != 0 {
// return nil, err
// }
// return &oldState, nil
}

View file

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"index/suffixarray"
@ -33,7 +34,7 @@ func Go(f func() error) chan error {
// Request a given URL and return an io.Reader
func Download(url string, stderr io.Writer) (*http.Response, error) {
var resp *http.Response
var err error = nil
var err error
if resp, err = http.Get(url); err != nil {
return nil, err
}
@ -69,7 +70,7 @@ type progressReader struct {
readProgress int // How much has been read so far (bytes)
lastUpdate int // How many bytes read at least update
template string // Template to print. Default "%v/%v (%v)"
json bool
sf *StreamFormatter
}
func (r *progressReader) Read(p []byte) (n int, err error) {
@ -93,7 +94,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
}
// Send newline when complete
if err != nil {
fmt.Fprintf(r.output, FormatStatus("", r.json))
r.output.Write(r.sf.FormatStatus(""))
}
return read, err
@ -101,11 +102,12 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
func (r *progressReader) Close() error {
return io.ReadCloser(r.reader).Close()
}
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
if template == "" {
template = "%v/%v (%v)\r"
func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
tpl := string(template)
if tpl == "" {
tpl = string(sf.FormatProgress("", "%v/%v (%v)"))
}
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
}
// HumanDuration returns a human-readable approximation of a duration
@ -361,11 +363,11 @@ func (idx *TruncIndex) Get(s string) (string, error) {
return string(idx.bytes[before:after]), err
}
// TruncateId returns a shorthand version of a string identifier for convenience.
// TruncateID returns a shorthand version of a string identifier for convenience.
// A collision with other shorthands is very unlikely, but possible.
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length Id.
func TruncateId(id string) string {
func TruncateID(id string) string {
shortLen := 12
if len(id) < shortLen {
shortLen = len(id)
@ -578,16 +580,57 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
return &WriteFlusher{w: w, flusher: flusher}
}
func FormatStatus(str string, json bool) string {
if json {
return "{\"status\" : \"" + str + "\"}"
}
return str + "\r\n"
type JSONMessage struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
Error string `json:"error,omitempty"`
}
func FormatProgress(str string, json bool) string {
if json {
return "{\"progress\" : \"" + str + "\"}"
}
return "Downloading " + str + "\r"
type StreamFormatter struct {
json bool
used bool
}
func NewStreamFormatter(json bool) *StreamFormatter {
return &StreamFormatter{json, false}
}
func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte {
sf.used = true
str := fmt.Sprintf(format, a...)
if sf.json {
b, err := json.Marshal(&JSONMessage{Status:str});
if err != nil {
return sf.FormatError(err)
}
return b
}
return []byte(str + "\r\n")
}
func (sf *StreamFormatter) FormatError(err error) []byte {
sf.used = true
if sf.json {
if b, err := json.Marshal(&JSONMessage{Error:err.Error()}); err == nil {
return b
}
return []byte("{\"error\":\"format error\"}")
}
return []byte("Error: " + err.Error() + "\r\n")
}
func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
sf.used = true
if sf.json {
b, err := json.Marshal(&JSONMessage{Status: action, Progress:str})
if err != nil {
return nil
}
return b
}
return []byte(action + " " + str + "\r")
}
func (sf *StreamFormatter) Used() bool {
return sf.used
}