Merge branch 'master' into add_last_version-feature
This commit is contained in:
commit
39ff542142
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,5 +1,18 @@
|
|||
# Changelog
|
||||
|
||||
## 0.5.0 (2013-07-17)
|
||||
+ Runtime: List all processes running inside a container with 'docker top'
|
||||
+ Runtime: Host directories can be mounted as volumes with 'docker run -v'
|
||||
+ Runtime: Containers can expose public UDP ports (eg, '-p 123/udp')
|
||||
+ Runtime: Optionally specify an exact public port (eg. '-p 80:4500')
|
||||
+ Registry: New image naming scheme inspired by Go packaging convention allows arbitrary combinations of registries
|
||||
+ Builder: ENTRYPOINT instruction sets a default binary entry point to a container
|
||||
+ Builder: VOLUME instruction marks a part of the container as persistent data
|
||||
* Builder: 'docker build' displays the full output of a build by default
|
||||
* Runtime: 'docker login' supports additional options
|
||||
- Runtime: Dont save a container's hostname when committing an image.
|
||||
- Registry: Fix issues when uploading images to a private registry
|
||||
|
||||
## 0.4.8 (2013-07-01)
|
||||
+ Builder: New build operation ENTRYPOINT adds an executable entry point to the container.
|
||||
- Runtime: Fix a bug which caused 'docker run -d' to no longer print the container ID.
|
||||
|
|
1
Makefile
1
Makefile
|
@ -49,6 +49,7 @@ whichrelease:
|
|||
release: $(BINRELEASE)
|
||||
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
|
||||
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
|
||||
s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker
|
||||
|
||||
srcrelease: $(SRCRELEASE)
|
||||
deps: $(DOCKER_DIR)
|
||||
|
|
44
api.go
44
api.go
|
@ -47,21 +47,22 @@ func parseMultipartForm(r *http.Request) error {
|
|||
}
|
||||
|
||||
func httpError(w http.ResponseWriter, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
if strings.HasPrefix(err.Error(), "No such") {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
statusCode = http.StatusNotFound
|
||||
} else if strings.HasPrefix(err.Error(), "Bad parameter") {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
statusCode = http.StatusBadRequest
|
||||
} else if strings.HasPrefix(err.Error(), "Conflict") {
|
||||
http.Error(w, err.Error(), http.StatusConflict)
|
||||
statusCode = http.StatusConflict
|
||||
} else if strings.HasPrefix(err.Error(), "Impossible") {
|
||||
http.Error(w, err.Error(), http.StatusNotAcceptable)
|
||||
statusCode = http.StatusNotAcceptable
|
||||
} else if strings.HasPrefix(err.Error(), "Wrong login/password") {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
statusCode = http.StatusUnauthorized
|
||||
} else if strings.Contains(err.Error(), "hasn't been activated") {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
statusCode = http.StatusForbidden
|
||||
}
|
||||
utils.Debugf("[error %d] %s", statusCode, err)
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, b []byte) {
|
||||
|
@ -250,6 +251,23 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r
|
|||
return nil
|
||||
}
|
||||
|
||||
func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
name := vars["name"]
|
||||
procsStr, err := srv.ContainerTop(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(procsStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -756,6 +774,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
remoteURL := r.FormValue("remote")
|
||||
repoName := r.FormValue("t")
|
||||
rawSuppressOutput := r.FormValue("q")
|
||||
tag := ""
|
||||
if strings.Contains(repoName, ":") {
|
||||
remoteParts := strings.Split(repoName, ":")
|
||||
|
@ -802,7 +821,13 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
context = c
|
||||
}
|
||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
|
||||
|
||||
suppressOutput, err := getBoolParam(rawSuppressOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput)
|
||||
id, err := b.Build(context)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error build: %s\n", err)
|
||||
|
@ -842,6 +867,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|||
"/containers/{name:.*}/export": getContainersExport,
|
||||
"/containers/{name:.*}/changes": getContainersChanges,
|
||||
"/containers/{name:.*}/json": getContainersByName,
|
||||
"/containers/{name:.*}/top": getContainersTop,
|
||||
},
|
||||
"POST": {
|
||||
"/auth": postAuth,
|
||||
|
|
|
@ -26,6 +26,13 @@ type APIInfo struct {
|
|||
SwapLimit bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APITop struct {
|
||||
PID string
|
||||
Tty string
|
||||
Time string
|
||||
Cmd string
|
||||
}
|
||||
|
||||
type APIRmi struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
|
|
398
api_test.go
398
api_test.go
|
@ -41,10 +41,8 @@ func TestGetBoolParam(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var err error
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -65,10 +63,7 @@ func TestGetVersion(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetInfo(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -95,10 +90,7 @@ func TestGetInfo(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetImagesJSON(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -220,10 +212,7 @@ func TestGetImagesJSON(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetImagesViz(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -248,10 +237,7 @@ func TestGetImagesViz(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetImagesHistory(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -272,10 +258,7 @@ func TestGetImagesHistory(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetImagesByName(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -295,10 +278,7 @@ func TestGetImagesByName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetContainersJSON(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -334,10 +314,7 @@ func TestGetContainersJSON(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetContainersExport(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -389,10 +366,7 @@ func TestGetContainersExport(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetContainersChanges(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -436,7 +410,7 @@ func TestGetContainersChanges(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetContainersByName(t *testing.T) {
|
||||
func TestGetContainersTop(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -447,6 +421,58 @@ func TestGetContainersByName(t *testing.T) {
|
|||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
container, err := builder.Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/sh", "-c", "sleep 2"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Give some time to the process to start
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
|
||||
if !container.State.Running {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
if err := getContainersTop(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
procs := []APITop{}
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(procs) != 2 {
|
||||
t.Fatalf("Expected 2 processes, found %d.", len(procs))
|
||||
}
|
||||
|
||||
if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" {
|
||||
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd)
|
||||
}
|
||||
|
||||
if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" {
|
||||
t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContainersByName(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
||||
// Create a container and remove a file
|
||||
container, err := builder.Create(
|
||||
&Config{
|
||||
|
@ -473,10 +499,7 @@ func TestGetContainersByName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostCommit(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -521,249 +544,8 @@ func TestPostCommit(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPostImagesCreate(t *testing.T) {
|
||||
// FIXME: Use the staging in order to perform tests
|
||||
|
||||
// runtime, err := newTestRuntime()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer nuke(runtime)
|
||||
|
||||
// srv := &Server{runtime: runtime}
|
||||
|
||||
// stdin, stdinPipe := io.Pipe()
|
||||
// stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// c1 := make(chan struct{})
|
||||
// go func() {
|
||||
// defer close(c1)
|
||||
|
||||
// r := &hijackTester{
|
||||
// ResponseRecorder: httptest.NewRecorder(),
|
||||
// in: stdin,
|
||||
// out: stdoutPipe,
|
||||
// }
|
||||
|
||||
// req, err := http.NewRequest("POST", "/images/create?fromImage="+unitTestImageName, bytes.NewReader([]byte{}))
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// body, err := postImagesCreate(srv, r, req, nil)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if body != nil {
|
||||
// t.Fatalf("No body expected, received: %s\n", body)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // Acknowledge hijack
|
||||
// setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
|
||||
// stdout.Read([]byte{})
|
||||
// stdout.Read(make([]byte, 4096))
|
||||
// })
|
||||
|
||||
// setTimeout(t, "Waiting for imagesCreate output", 5*time.Second, func() {
|
||||
// reader := bufio.NewReader(stdout)
|
||||
// line, err := reader.ReadString('\n')
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if !strings.HasPrefix(line, "Pulling repository d from") {
|
||||
// t.Fatalf("Expected Pulling repository docker-ut from..., found %s", line)
|
||||
// }
|
||||
// })
|
||||
|
||||
// // Close pipes (client disconnects)
|
||||
// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// // Wait for imagesCreate to finish, the client disconnected, therefore, Create finished his job
|
||||
// setTimeout(t, "Waiting for imagesCreate timed out", 10*time.Second, func() {
|
||||
// <-c1
|
||||
// })
|
||||
}
|
||||
|
||||
func TestPostImagesInsert(t *testing.T) {
|
||||
// runtime, err := newTestRuntime()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer nuke(runtime)
|
||||
|
||||
// srv := &Server{runtime: runtime}
|
||||
|
||||
// stdin, stdinPipe := io.Pipe()
|
||||
// stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// // Attach to it
|
||||
// c1 := make(chan struct{})
|
||||
// go func() {
|
||||
// defer close(c1)
|
||||
// r := &hijackTester{
|
||||
// ResponseRecorder: httptest.NewRecorder(),
|
||||
// in: stdin,
|
||||
// out: stdoutPipe,
|
||||
// }
|
||||
|
||||
// req, err := http.NewRequest("POST", "/images/"+unitTestImageName+"/insert?path=%2Ftest&url=https%3A%2F%2Fraw.github.com%2Fdotcloud%2Fdocker%2Fmaster%2FREADME.md", bytes.NewReader([]byte{}))
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if err := postContainersCreate(srv, r, req, nil); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // Acknowledge hijack
|
||||
// setTimeout(t, "hijack acknowledge timed out", 5*time.Second, func() {
|
||||
// stdout.Read([]byte{})
|
||||
// stdout.Read(make([]byte, 4096))
|
||||
// })
|
||||
|
||||
// id := ""
|
||||
// setTimeout(t, "Waiting for imagesInsert output", 10*time.Second, func() {
|
||||
// for {
|
||||
// reader := bufio.NewReader(stdout)
|
||||
// id, err = reader.ReadString('\n')
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
// // Close pipes (client disconnects)
|
||||
// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// // Wait for attach to finish, the client disconnected, therefore, Attach finished his job
|
||||
// setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
|
||||
// <-c1
|
||||
// })
|
||||
|
||||
// img, err := srv.runtime.repositories.LookupImage(id)
|
||||
// if err != nil {
|
||||
// t.Fatalf("New image %s expected but not found", id)
|
||||
// }
|
||||
|
||||
// layer, err := img.layer()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// if _, err := os.Stat(path.Join(layer, "test")); err != nil {
|
||||
// t.Fatalf("The test file has not been found")
|
||||
// }
|
||||
|
||||
// if err := srv.runtime.graph.Delete(img.ID); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
}
|
||||
|
||||
func TestPostImagesPush(t *testing.T) {
|
||||
//FIXME: Use staging in order to perform tests
|
||||
// runtime, err := newTestRuntime()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer nuke(runtime)
|
||||
|
||||
// srv := &Server{runtime: runtime}
|
||||
|
||||
// stdin, stdinPipe := io.Pipe()
|
||||
// stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// c1 := make(chan struct{})
|
||||
// go func() {
|
||||
// r := &hijackTester{
|
||||
// ResponseRecorder: httptest.NewRecorder(),
|
||||
// in: stdin,
|
||||
// out: stdoutPipe,
|
||||
// }
|
||||
|
||||
// req, err := http.NewRequest("POST", "/images/docker-ut/push", bytes.NewReader([]byte{}))
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// body, err := postImagesPush(srv, r, req, map[string]string{"name": "docker-ut"})
|
||||
// close(c1)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if body != nil {
|
||||
// t.Fatalf("No body expected, received: %s\n", body)
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // Acknowledge hijack
|
||||
// setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
|
||||
// stdout.Read([]byte{})
|
||||
// stdout.Read(make([]byte, 4096))
|
||||
// })
|
||||
|
||||
// setTimeout(t, "Waiting for imagesCreate output", 5*time.Second, func() {
|
||||
// reader := bufio.NewReader(stdout)
|
||||
// line, err := reader.ReadString('\n')
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if !strings.HasPrefix(line, "Processing checksum") {
|
||||
// t.Fatalf("Processing checksum..., found %s", line)
|
||||
// }
|
||||
// })
|
||||
|
||||
// // Close pipes (client disconnects)
|
||||
// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// // Wait for imagesPush to finish, the client disconnected, therefore, Push finished his job
|
||||
// setTimeout(t, "Waiting for imagesPush timed out", 10*time.Second, func() {
|
||||
// <-c1
|
||||
// })
|
||||
}
|
||||
|
||||
func TestPostImagesTag(t *testing.T) {
|
||||
// FIXME: Use staging in order to perform tests
|
||||
|
||||
// runtime, err := newTestRuntime()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer nuke(runtime)
|
||||
|
||||
// srv := &Server{runtime: runtime}
|
||||
|
||||
// r := httptest.NewRecorder()
|
||||
|
||||
// req, err := http.NewRequest("POST", "/images/docker-ut/tag?repo=testrepo&tag=testtag", bytes.NewReader([]byte{}))
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// body, err := postImagesTag(srv, r, req, map[string]string{"name": "docker-ut"})
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
|
||||
// if body != nil {
|
||||
// t.Fatalf("No body expected, received: %s\n", body)
|
||||
// }
|
||||
// if r.Code != http.StatusCreated {
|
||||
// t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
|
||||
// }
|
||||
}
|
||||
|
||||
func TestPostContainersCreate(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -814,10 +596,7 @@ func TestPostContainersCreate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostContainersKill(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -859,10 +638,7 @@ func TestPostContainersKill(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostContainersRestart(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -916,10 +692,7 @@ func TestPostContainersRestart(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostContainersStart(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -969,10 +742,7 @@ func TestPostContainersStart(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostContainersStop(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -1019,10 +789,7 @@ func TestPostContainersStop(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostContainersWait(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -1064,10 +831,7 @@ func TestPostContainersWait(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPostContainersAttach(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -1153,10 +917,7 @@ func TestPostContainersAttach(t *testing.T) {
|
|||
// FIXME: Test deleting container with volume
|
||||
// FIXME: Test deleting volume in use by other container
|
||||
func TestDeleteContainers(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -1196,10 +957,7 @@ func TestDeleteContainers(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOptionsRoute(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime, enableCors: true}
|
||||
|
@ -1222,10 +980,7 @@ func TestOptionsRoute(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetEnabledCors(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime, enableCors: true}
|
||||
|
@ -1263,10 +1018,7 @@ func TestGetEnabledCors(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDeleteImages(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
|
39
buildfile.go
39
buildfile.go
|
@ -28,8 +28,8 @@ type buildFile struct {
|
|||
maintainer string
|
||||
config *Config
|
||||
context string
|
||||
verbose bool
|
||||
|
||||
lastContainer *Container
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
|
||||
|
@ -254,7 +254,6 @@ func (b *buildFile) CmdAdd(args string) error {
|
|||
return err
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
b.lastContainer = container
|
||||
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
|
@ -290,7 +289,6 @@ func (b *buildFile) run() (string, error) {
|
|||
return "", err
|
||||
}
|
||||
b.tmpContainers[c.ID] = struct{}{}
|
||||
b.lastContainer = c
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
|
||||
|
||||
// override the entry point that may have been picked up from the base image
|
||||
|
@ -303,6 +301,13 @@ func (b *buildFile) run() (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if b.verbose {
|
||||
err = <-c.Attach(nil, nil, b.out, b.out)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for it to finish
|
||||
if ret := c.Wait(); ret != 0 {
|
||||
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
|
||||
|
@ -337,7 +342,6 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
|||
return err
|
||||
}
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
b.lastContainer = container
|
||||
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
id = container.ID
|
||||
if err := container.EnsureMounted(); err != nil {
|
||||
|
@ -365,29 +369,6 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
|||
}
|
||||
|
||||
func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
defer func() {
|
||||
// If we have an error and a container, the display the logs
|
||||
if b.lastContainer != nil {
|
||||
fmt.Fprintf(b.out, "******** Logs from last container (%s) *******\n", b.lastContainer.ShortID())
|
||||
|
||||
cLog, err := b.lastContainer.ReadLog("stdout")
|
||||
if err != nil {
|
||||
utils.Debugf("Error reading logs (stdout): %s", err)
|
||||
}
|
||||
if _, err := io.Copy(b.out, cLog); err != nil {
|
||||
utils.Debugf("Error streaming logs (stdout): %s", err)
|
||||
}
|
||||
cLog, err = b.lastContainer.ReadLog("stderr")
|
||||
if err != nil {
|
||||
utils.Debugf("Error reading logs (stderr): %s", err)
|
||||
}
|
||||
if _, err := io.Copy(b.out, cLog); err != nil {
|
||||
utils.Debugf("Error streaming logs (stderr): %s", err)
|
||||
}
|
||||
fmt.Fprintf(b.out, "************* End of logs for %s *************\n", b.lastContainer.ShortID())
|
||||
}
|
||||
}()
|
||||
|
||||
// FIXME: @creack any reason for using /tmp instead of ""?
|
||||
// FIXME: @creack "name" is a terrible variable name
|
||||
name, err := ioutil.TempDir("/tmp", "docker-build")
|
||||
|
@ -440,7 +421,6 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
|||
return "", ret.(error)
|
||||
}
|
||||
|
||||
b.lastContainer = nil
|
||||
fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
|
||||
}
|
||||
if b.image != "" {
|
||||
|
@ -450,7 +430,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
|||
return "", fmt.Errorf("An error occured during the build\n")
|
||||
}
|
||||
|
||||
func NewBuildFile(srv *Server, out io.Writer) BuildFile {
|
||||
func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
|
||||
return &buildFile{
|
||||
builder: NewBuilder(srv.runtime),
|
||||
runtime: srv.runtime,
|
||||
|
@ -459,5 +439,6 @@ func NewBuildFile(srv *Server, out io.Writer) BuildFile {
|
|||
out: out,
|
||||
tmpContainers: make(map[string]struct{}),
|
||||
tmpImages: make(map[string]struct{}),
|
||||
verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,26 +105,11 @@ CMD Hello world
|
|||
|
||||
func TestBuild(t *testing.T) {
|
||||
for _, ctx := range testContexts {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard)
|
||||
if _, err := buildfile.Build(mkTestContext(ctx.dockerfile, ctx.files, t)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buildImage(ctx, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolume(t *testing.T) {
|
||||
func buildImage(context testContextTemplate, t *testing.T) *Image {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -136,26 +121,96 @@ func TestVolume(t *testing.T) {
|
|||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
}
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard, false)
|
||||
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard)
|
||||
imgId, err := buildfile.Build(mkTestContext(`
|
||||
from %s
|
||||
VOLUME /test
|
||||
CMD Hello world
|
||||
`, nil, t))
|
||||
id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
img, err := srv.ImageInspect(imgId)
|
||||
|
||||
img, err := srv.ImageInspect(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func TestVolume(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
volume /test
|
||||
cmd Hello world
|
||||
`, nil}, t)
|
||||
|
||||
if len(img.Config.Volumes) == 0 {
|
||||
t.Fail()
|
||||
}
|
||||
for key, _ := range img.Config.Volumes {
|
||||
for key := range img.Config.Volumes {
|
||||
if key != "/test" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMaintainer(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
maintainer dockerio
|
||||
`, nil}, t)
|
||||
|
||||
if img.Author != "dockerio" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEnv(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
env port 4243
|
||||
`,
|
||||
nil}, t)
|
||||
|
||||
if img.Config.Env[0] != "port=4243" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCmd(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
cmd ["/bin/echo", "Hello World"]
|
||||
`,
|
||||
nil}, t)
|
||||
|
||||
if img.Config.Cmd[0] != "/bin/echo" {
|
||||
t.Log(img.Config.Cmd[0])
|
||||
t.Fail()
|
||||
}
|
||||
if img.Config.Cmd[1] != "Hello World" {
|
||||
t.Log(img.Config.Cmd[1])
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildExpose(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
expose 4243
|
||||
`,
|
||||
nil}, t)
|
||||
|
||||
if img.Config.PortSpecs[0] != "4243" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEntrypoint(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
nil}, t)
|
||||
|
||||
if img.Config.Entrypoint[0] != "/bin/echo" {
|
||||
}
|
||||
}
|
||||
|
|
144
commands.go
144
commands.go
|
@ -27,7 +27,7 @@ import (
|
|||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.4.8"
|
||||
const VERSION = "0.5.0-dev"
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
|
@ -89,12 +89,13 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|||
{"login", "Register or Login to the docker registry server"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
||||
{"top", "Lookup the running processes of a container"},
|
||||
{"ps", "List containers"},
|
||||
{"pull", "Pull an image or a repository from the docker registry server"},
|
||||
{"push", "Push an image or a repository to the docker registry server"},
|
||||
{"restart", "Restart a running container"},
|
||||
{"rm", "Remove a container"},
|
||||
{"rmi", "Remove an image"},
|
||||
{"rm", "Remove one or more containers"},
|
||||
{"rmi", "Remove one or more images"},
|
||||
{"run", "Run a command in a new container"},
|
||||
{"search", "Search for an image in the docker index"},
|
||||
{"start", "Start a stopped container"},
|
||||
|
@ -157,6 +158,8 @@ func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
|
|||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "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")
|
||||
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -194,6 +197,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||
// Upload the build context
|
||||
v := &url.Values{}
|
||||
v.Set("t", *tag)
|
||||
|
||||
if *suppressOutput {
|
||||
v.Set("q", "1")
|
||||
}
|
||||
if isRemote {
|
||||
v.Set("remote", cmd.Arg(0))
|
||||
}
|
||||
|
@ -279,47 +286,66 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||
return readStringOnRawTerminal(stdin, stdout, false)
|
||||
}
|
||||
|
||||
oldState, err := term.SetRawTerminal(cli.terminalFd)
|
||||
cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server")
|
||||
flUsername := cmd.String("u", "", "username")
|
||||
flPassword := cmd.String("p", "", "password")
|
||||
flEmail := cmd.String("e", "", "email")
|
||||
err := cmd.Parse(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
|
||||
cmd := Subcmd("login", "", "Register or Login to the docker registry server")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var oldState *term.State
|
||||
if *flUsername == "" || *flPassword == "" || *flEmail == "" {
|
||||
oldState, err = term.SetRawTerminal(cli.terminalFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
}
|
||||
|
||||
var (
|
||||
username string
|
||||
password string
|
||||
email string
|
||||
)
|
||||
|
||||
fmt.Fprintf(cli.out, "Username (%s):", cli.authConfig.Username)
|
||||
username = readAndEchoString(cli.in, cli.out)
|
||||
if username == "" {
|
||||
username = cli.authConfig.Username
|
||||
if *flUsername == "" {
|
||||
fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username)
|
||||
username = readAndEchoString(cli.in, cli.out)
|
||||
if username == "" {
|
||||
username = cli.authConfig.Username
|
||||
}
|
||||
} else {
|
||||
username = *flUsername
|
||||
}
|
||||
if username != cli.authConfig.Username {
|
||||
fmt.Fprintf(cli.out, "Password: ")
|
||||
password = readString(cli.in, cli.out)
|
||||
|
||||
if password == "" {
|
||||
return fmt.Errorf("Error : Password Required")
|
||||
if *flPassword == "" {
|
||||
fmt.Fprintf(cli.out, "Password: ")
|
||||
password = readString(cli.in, cli.out)
|
||||
if password == "" {
|
||||
return fmt.Errorf("Error : Password Required")
|
||||
}
|
||||
} else {
|
||||
password = *flPassword
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email)
|
||||
email = readAndEchoString(cli.in, cli.out)
|
||||
if email == "" {
|
||||
email = cli.authConfig.Email
|
||||
if *flEmail == "" {
|
||||
fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email)
|
||||
email = readAndEchoString(cli.in, cli.out)
|
||||
if email == "" {
|
||||
email = cli.authConfig.Email
|
||||
}
|
||||
} else {
|
||||
email = *flEmail
|
||||
}
|
||||
} else {
|
||||
password = cli.authConfig.Password
|
||||
email = cli.authConfig.Email
|
||||
}
|
||||
term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
|
||||
if oldState != nil {
|
||||
term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
}
|
||||
cli.authConfig.Username = username
|
||||
cli.authConfig.Password = password
|
||||
cli.authConfig.Email = email
|
||||
|
@ -458,7 +484,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||
|
||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
|
||||
nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -483,7 +509,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
|||
|
||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
|
||||
nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -563,6 +589,33 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
cmd := Subcmd("top", "CONTAINER", "Lookup the running processes of a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
if cmd.NArg() != 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var procs []APITop
|
||||
err = json.Unmarshal(body, &procs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
fmt.Fprintln(w, "PID\tTTY\tTIME\tCMD")
|
||||
for _, proc := range procs {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", proc.PID, proc.Tty, proc.Time, proc.Cmd)
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
|
@ -573,6 +626,13 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
port := cmd.Arg(1)
|
||||
proto := "Tcp"
|
||||
parts := strings.SplitN(port, "/", 2)
|
||||
if len(parts) == 2 && len(parts[1]) != 0 {
|
||||
port = parts[0]
|
||||
proto = strings.ToUpper(parts[1][:1]) + strings.ToLower(parts[1][1:])
|
||||
}
|
||||
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -583,7 +643,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if frontend, exists := out.NetworkSettings.PortMapping[cmd.Arg(1)]; exists {
|
||||
if frontend, exists := out.NetworkSettings.PortMapping[proto][port]; exists {
|
||||
fmt.Fprintf(cli.out, "%s\n", frontend)
|
||||
} else {
|
||||
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
|
||||
|
@ -593,7 +653,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||
|
||||
// 'docker rmi IMAGE' removes all images with the name IMAGE
|
||||
func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image")
|
||||
cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -658,7 +718,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
|||
}
|
||||
|
||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container")
|
||||
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
||||
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
|
@ -728,7 +788,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||
}
|
||||
|
||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
cmd := Subcmd("push", "[OPTION] NAME", "Push an image or a repository to the registry")
|
||||
cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -776,7 +836,9 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|||
}
|
||||
|
||||
remote, parsedTag := utils.ParseRepositoryTag(cmd.Arg(0))
|
||||
*tag = parsedTag
|
||||
if *tag == "" {
|
||||
*tag = parsedTag
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("fromImage", remote)
|
||||
|
@ -1202,10 +1264,22 @@ func (opts PathOpts) String() string {
|
|||
}
|
||||
|
||||
func (opts PathOpts) Set(val string) error {
|
||||
if !filepath.IsAbs(val) {
|
||||
return fmt.Errorf("%s is not an absolute path", val)
|
||||
var containerPath string
|
||||
|
||||
splited := strings.SplitN(val, ":", 2)
|
||||
if len(splited) == 1 {
|
||||
containerPath = splited[0]
|
||||
val = filepath.Clean(splited[0])
|
||||
} else {
|
||||
containerPath = splited[1]
|
||||
val = fmt.Sprintf("%s:%s", splited[0], filepath.Clean(splited[1]))
|
||||
}
|
||||
opts[filepath.Clean(val)] = struct{}{}
|
||||
|
||||
if !filepath.IsAbs(containerPath) {
|
||||
utils.Debugf("%s is not an absolute path", containerPath)
|
||||
return fmt.Errorf("%s is not an absolute path", containerPath)
|
||||
}
|
||||
opts[val] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
301
commands_test.go
301
commands_test.go
|
@ -59,79 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
|
|||
return nil
|
||||
}
|
||||
|
||||
/*TODO
|
||||
func cmdImages(srv *Server, args ...string) (string, error) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
go func() {
|
||||
if err := srv.CmdImages(nil, stdoutPipe, args...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// force the pipe closed, so that the code below gets an EOF
|
||||
stdoutPipe.Close()
|
||||
}()
|
||||
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Cleanup pipes
|
||||
return string(output), closeWrap(stdout, stdoutPipe)
|
||||
}
|
||||
|
||||
// TestImages checks that 'docker images' displays information correctly
|
||||
func TestImages(t *testing.T) {
|
||||
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
output, err := cmdImages(srv)
|
||||
|
||||
if !strings.Contains(output, "REPOSITORY") {
|
||||
t.Fatal("'images' should have a header")
|
||||
}
|
||||
if !strings.Contains(output, "docker-ut") {
|
||||
t.Fatal("'images' should show the docker-ut image")
|
||||
}
|
||||
if !strings.Contains(output, "e9aa60c60128") {
|
||||
t.Fatal("'images' should show the docker-ut image id")
|
||||
}
|
||||
|
||||
output, err = cmdImages(srv, "-q")
|
||||
|
||||
if strings.Contains(output, "REPOSITORY") {
|
||||
t.Fatal("'images -q' should not have a header")
|
||||
}
|
||||
if strings.Contains(output, "docker-ut") {
|
||||
t.Fatal("'images' should not show the docker-ut image name")
|
||||
}
|
||||
if !strings.Contains(output, "e9aa60c60128") {
|
||||
t.Fatal("'images' should show the docker-ut image id")
|
||||
}
|
||||
|
||||
output, err = cmdImages(srv, "-viz")
|
||||
|
||||
if !strings.HasPrefix(output, "digraph docker {") {
|
||||
t.Fatal("'images -v' should start with the dot header")
|
||||
}
|
||||
if !strings.HasSuffix(output, "}\n") {
|
||||
t.Fatal("'images -v' should end with a '}'")
|
||||
}
|
||||
if !strings.Contains(output, "base -> \"e9aa60c60128\" [style=invis]") {
|
||||
t.Fatal("'images -v' should have the docker-ut image id node")
|
||||
}
|
||||
|
||||
// todo: add checks for -a
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
|
||||
func TestRunHostname(t *testing.T) {
|
||||
|
@ -164,163 +91,6 @@ func TestRunHostname(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
/*
|
||||
func TestRunExit(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
container := runtime.List()[0]
|
||||
|
||||
// Closing /bin/cat stdin, expect it to exit
|
||||
p, err := container.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := p.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the process exited, CmdRun must finish and unblock. Wait for it
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
cmdWait(srv, container)
|
||||
})
|
||||
|
||||
// Make sure that the client has been disconnected
|
||||
setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
|
||||
// Expecting pipe i/o error, just check that read does not block
|
||||
stdin.Read([]byte{})
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnect(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the pipes are close, we expect the process to die,
|
||||
// therefore CmdRun to unblock. Wait for CmdRun
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should cause stdin to be closed, which should
|
||||
// cause /bin/cat to exit.
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := runtime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnectTty(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdRun returns.
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := runtime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := runtime.List()[0]
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Close pipes (simulate disconnect)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// In tty mode, we expect the process to stay alive even after client's stdin closes.
|
||||
// Do not wait for run to finish
|
||||
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// TestAttachStdin checks attaching to stdin without stdout and stderr.
|
||||
// 'docker run -i -a stdin' should sends the client's stdin to the command,
|
||||
|
@ -387,74 +157,3 @@ func TestRunAttachStdin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Expected behaviour, the process stays alive when the client disconnects
|
||||
func TestAttachDisconnect(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).Id,
|
||||
CpuShares: 1000,
|
||||
Memory: 33554432,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
// Start the process
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
// Attach to it
|
||||
c1 := make(chan struct{})
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdAttach returns.
|
||||
srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
// Close pipes (client disconnects)
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// We closed stdin, expect /bin/cat to still be running
|
||||
// Wait a little bit to make sure container.monitor() did his thing
|
||||
err = container.WaitTimeout(500 * time.Millisecond)
|
||||
if err == nil || !container.State.Running {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
// Try to avoid the timeoout in destroy. Best effort, don't check error
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.Wait()
|
||||
}
|
||||
*/
|
||||
|
|
51
container.go
51
container.go
|
@ -121,14 +121,11 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Attach a data volume")
|
||||
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
||||
|
||||
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
|
||||
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
|
||||
var flBinds ListOpts
|
||||
cmd.Var(&flBinds, "b", "Bind mount a volume from the host (e.g. -b /host:/container)")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
@ -146,11 +143,17 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
}
|
||||
}
|
||||
|
||||
var binds []string
|
||||
|
||||
// add any bind targets to the list of container volumes
|
||||
for _, bind := range flBinds {
|
||||
for bind := range flVolumes {
|
||||
arr := strings.Split(bind, ":")
|
||||
dstDir := arr[1]
|
||||
flVolumes[dstDir] = struct{}{}
|
||||
if len(arr) > 1 {
|
||||
dstDir := arr[1]
|
||||
flVolumes[dstDir] = struct{}{}
|
||||
binds = append(binds, bind)
|
||||
delete(flVolumes, bind)
|
||||
}
|
||||
}
|
||||
|
||||
parsedArgs := cmd.Args()
|
||||
|
@ -187,7 +190,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
Entrypoint: entrypoint,
|
||||
}
|
||||
hostConfig := &HostConfig{
|
||||
Binds: flBinds,
|
||||
Binds: binds,
|
||||
}
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
||||
|
@ -270,6 +273,26 @@ func (container *Container) ToDisk() (err error) {
|
|||
return ioutil.WriteFile(container.jsonPath(), data, 0666)
|
||||
}
|
||||
|
||||
func (container *Container) ReadHostConfig() (*HostConfig, error) {
|
||||
data, err := ioutil.ReadFile(container.hostConfigPath())
|
||||
if err != nil {
|
||||
return &HostConfig{}, err
|
||||
}
|
||||
hostConfig := &HostConfig{}
|
||||
if err := json.Unmarshal(data, hostConfig); err != nil {
|
||||
return &HostConfig{}, err
|
||||
}
|
||||
return hostConfig, nil
|
||||
}
|
||||
|
||||
func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) {
|
||||
data, err := json.Marshal(hostConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
|
||||
}
|
||||
|
||||
func (container *Container) generateLXCConfig() error {
|
||||
fo, err := os.Create(container.lxcConfigPath())
|
||||
if err != nil {
|
||||
|
@ -474,6 +497,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
|
||||
if len(hostConfig.Binds) == 0 {
|
||||
hostConfig, _ = container.ReadHostConfig()
|
||||
}
|
||||
|
||||
if container.State.Running {
|
||||
return fmt.Errorf("The container %s is already running.", container.ID)
|
||||
}
|
||||
|
@ -574,6 +601,9 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
return nil
|
||||
}
|
||||
container.Volumes[volPath] = id
|
||||
if isRW, exists := c.VolumesRW[volPath]; exists {
|
||||
container.VolumesRW[volPath] = isRW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,6 +671,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
container.waitLock = make(chan struct{})
|
||||
|
||||
container.ToDisk()
|
||||
container.SaveHostConfig(hostConfig)
|
||||
go container.monitor()
|
||||
return nil
|
||||
}
|
||||
|
@ -979,6 +1010,10 @@ func (container *Container) ReadLog(name string) (io.Reader, error) {
|
|||
return os.Open(container.logPath(name))
|
||||
}
|
||||
|
||||
func (container *Container) hostConfigPath() string {
|
||||
return path.Join(container.root, "hostconfig.json")
|
||||
}
|
||||
|
||||
func (container *Container) jsonPath() string {
|
||||
return path.Join(container.root, "config.json")
|
||||
}
|
||||
|
|
|
@ -849,6 +849,23 @@ func TestUser(t *testing.T) {
|
|||
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
|
||||
t.Error(string(output))
|
||||
}
|
||||
|
||||
// Test an wrong username
|
||||
container, err = builder.Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"id"},
|
||||
|
||||
User: "unkownuser",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if container.State.ExitCode == 0 {
|
||||
t.Fatal("Starting container with wrong uid should fail but it passed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleContainers(t *testing.T) {
|
||||
|
@ -1046,10 +1063,7 @@ func TestEnv(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEntrypoint(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
|
@ -1125,10 +1139,7 @@ func TestLXCConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
func BenchmarkRunSequencial(b *testing.B) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(b)
|
||||
defer nuke(runtime)
|
||||
for i := 0; i < b.N; i++ {
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
|
@ -1154,10 +1165,7 @@ func BenchmarkRunSequencial(b *testing.B) {
|
|||
}
|
||||
|
||||
func BenchmarkRunParallel(b *testing.B) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(b)
|
||||
defer nuke(runtime)
|
||||
|
||||
var tasks []chan error
|
||||
|
@ -1223,18 +1231,71 @@ func TestBindMounts(t *testing.T) {
|
|||
writeFile(path.Join(tmpDir, "touch-me"), "", t)
|
||||
|
||||
// Test reading from a read-only bind mount
|
||||
stdout, _ := runContainer(r, []string{"-b", fmt.Sprintf("%s:/tmp:ro", tmpDir), "_", "ls", "/tmp"}, t)
|
||||
stdout, _ := runContainer(r, []string{"-v", fmt.Sprintf("%s:/tmp:ro", tmpDir), "_", "ls", "/tmp"}, t)
|
||||
if !strings.Contains(stdout, "touch-me") {
|
||||
t.Fatal("Container failed to read from bind mount")
|
||||
}
|
||||
|
||||
// test writing to bind mount
|
||||
runContainer(r, []string{"-b", fmt.Sprintf("%s:/tmp:rw", tmpDir), "_", "touch", "/tmp/holla"}, t)
|
||||
runContainer(r, []string{"-v", fmt.Sprintf("%s:/tmp:rw", tmpDir), "_", "touch", "/tmp/holla"}, t)
|
||||
readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist
|
||||
|
||||
// test mounting to an illegal destination directory
|
||||
if _, err := runContainer(r, []string{"-b", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil {
|
||||
if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil {
|
||||
t.Fatal("Container bind mounted illegal directory")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test that VolumesRW values are copied to the new container. Regression test for #1201
|
||||
func TestVolumesFromReadonlyMount(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
Volumes: map[string]struct{}{"/test": {}},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
_, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !container.VolumesRW["/test"] {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
container2, err := NewBuilder(runtime).Create(
|
||||
&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/echo", "-n", "foobar"},
|
||||
VolumesFrom: container.ID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
_, err = container2.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if container.Volumes["/test"] != container2.Volumes["/test"] {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
actual, exists := container2.VolumesRW["/test"]
|
||||
if !exists {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if container.VolumesRW["/test"] != actual {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,11 @@ You can still call an old version of the api using /v1.0/images/<name>/insert
|
|||
What's new
|
||||
----------
|
||||
|
||||
Listing processes (/top):
|
||||
|
||||
- List the processes inside a container
|
||||
|
||||
|
||||
Builder (/build):
|
||||
|
||||
- Simplify the upload of the build context
|
||||
|
|
|
@ -220,6 +220,46 @@ Inspect a container
|
|||
:statuscode 500: server error
|
||||
|
||||
|
||||
List processes running inside a container
|
||||
*****************************************
|
||||
|
||||
.. http:get:: /containers/(id)/top
|
||||
|
||||
List processes running inside the container ``id``
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /containers/4fa6e0f0c678/top HTTP/1.1
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"PID":"11935",
|
||||
"Tty":"pts/2",
|
||||
"Time":"00:00:00",
|
||||
"Cmd":"sh"
|
||||
},
|
||||
{
|
||||
"PID":"12140",
|
||||
"Tty":"pts/2",
|
||||
"Time":"00:00:00",
|
||||
"Cmd":"sleep"
|
||||
}
|
||||
]
|
||||
|
||||
:statuscode 200: no error
|
||||
:statuscode 404: no such container
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
Inspect changes on a container's filesystem
|
||||
*******************************************
|
||||
|
||||
|
@ -881,6 +921,7 @@ Build an image from Dockerfile via stdin
|
|||
The Content-type header should be set to "application/tar".
|
||||
|
||||
:query t: tag to be applied to the resulting image in case of success
|
||||
:query q: suppress verbose build output
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
|
|
@ -52,5 +52,6 @@ Available Commands
|
|||
command/start
|
||||
command/stop
|
||||
command/tag
|
||||
command/top
|
||||
command/version
|
||||
command/wait
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
Usage: docker build [OPTIONS] PATH | URL | -
|
||||
Build a new container image from the source code at PATH
|
||||
-t="": Tag to be applied to the resulting image in case of success.
|
||||
-q=false: Suppress verbose build output.
|
||||
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
::
|
||||
|
||||
Usage: docker login
|
||||
Usage: docker login [OPTIONS]
|
||||
|
||||
Register or Login to the docker registry server
|
||||
|
||||
-e="": email
|
||||
-p="": password
|
||||
-u="": username
|
||||
|
|
|
@ -10,4 +10,4 @@
|
|||
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove a container
|
||||
Remove one or more containers
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
|
||||
::
|
||||
|
||||
Usage: docker rmimage [OPTIONS] IMAGE
|
||||
Usage: docker rmi IMAGE [IMAGE...]
|
||||
|
||||
Remove an image
|
||||
Remove one or more images
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
-t=false: Allocate a pseudo-tty
|
||||
-u="": Username or UID
|
||||
-d=[]: Set custom dns servers for the container
|
||||
-v=[]: Creates a new volume and mounts it at the specified path.
|
||||
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
|
||||
-volumes-from="": Mount all volumes from the given container.
|
||||
-b=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]
|
||||
-entrypoint="": Overwrite the default entrypoint set by the image.
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
:title: Top Command
|
||||
:description: Lookup the running processes of a container
|
||||
:keywords: top, docker, container, documentation
|
||||
|
||||
=======================================================
|
||||
``top`` -- Lookup the running processes of a container
|
||||
=======================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker top CONTAINER
|
||||
|
||||
Lookup the running processes of a container
|
|
@ -1,25 +1,27 @@
|
|||
:title: Docker Builder
|
||||
:title: Dockerfile Builder
|
||||
:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image.
|
||||
:keywords: builder, docker, Docker Builder, automation, image creation
|
||||
|
||||
==============
|
||||
Docker Builder
|
||||
==============
|
||||
==================
|
||||
Dockerfile Builder
|
||||
==================
|
||||
|
||||
**Docker can act as a builder** and read instructions from a text
|
||||
Dockerfile to automate the steps you would otherwise make manually to
|
||||
create an image. Executing ``docker build`` will run your steps and
|
||||
commit them along the way, giving you a final image.
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
Docker Builder specifes a simple DSL which allows you to automate the steps you
|
||||
would normally manually take to create an image. Docker Build will run your
|
||||
steps and commit them along the way, giving you a final image.
|
||||
|
||||
1. Usage
|
||||
========
|
||||
|
||||
To build an image from a source repository, create a description file called `Dockerfile`
|
||||
at the root of your repository. This file will describe the steps to assemble
|
||||
the image.
|
||||
To build an image from a source repository, create a description file
|
||||
called ``Dockerfile`` at the root of your repository. This file will
|
||||
describe the steps to assemble the image.
|
||||
|
||||
Then call `docker build` with the path of your source repository as argument:
|
||||
Then call ``docker build`` with the path of your source repository as
|
||||
argument:
|
||||
|
||||
``docker build .``
|
||||
|
||||
|
@ -36,138 +38,164 @@ before finally outputting the ID of your new image.
|
|||
|
||||
The Dockerfile format is quite simple:
|
||||
|
||||
``instruction arguments``
|
||||
::
|
||||
|
||||
# Comment
|
||||
INSTRUCTION arguments
|
||||
|
||||
The Instruction is not case-sensitive, however convention is for them to be
|
||||
UPPERCASE in order to distinguish them from arguments more easily.
|
||||
|
||||
Dockerfiles are evaluated in order, therefore the first instruction must be
|
||||
`FROM` in order to specify the base image from which you are building.
|
||||
Docker evaluates the instructions in a Dockerfile in order. **The first
|
||||
instruction must be `FROM`** in order to specify the base image from
|
||||
which you are building.
|
||||
|
||||
Docker will ignore lines in Dockerfiles prefixed with "`#`", so you may add
|
||||
comment lines. A comment marker in the rest of the line will be treated as an
|
||||
argument.
|
||||
Docker will ignore **comment lines** *beginning* with ``#``. A comment
|
||||
marker anywhere in the rest of the line will be treated as an argument.
|
||||
|
||||
2. Instructions
|
||||
3. Instructions
|
||||
===============
|
||||
|
||||
Docker builder comes with a set of instructions, described below.
|
||||
Here is the set of instructions you can use in a ``Dockerfile`` for
|
||||
building images.
|
||||
|
||||
2.1 FROM
|
||||
3.1 FROM
|
||||
--------
|
||||
|
||||
``FROM <image>``
|
||||
|
||||
The `FROM` instruction sets the base image for subsequent instructions. As such,
|
||||
a valid Dockerfile must have it as its first instruction.
|
||||
The ``FROM`` instruction sets the :ref:`base_image_def` for subsequent
|
||||
instructions. As such, a valid Dockerfile must have ``FROM`` as its
|
||||
first instruction.
|
||||
|
||||
`FROM` can be included multiple times within a single Dockerfile in order to
|
||||
create multiple images. Simply make a note of the last image id output by the
|
||||
commit before each new `FROM` command.
|
||||
``FROM`` must be the first non-comment instruction in the
|
||||
``Dockerfile``.
|
||||
|
||||
2.2 MAINTAINER
|
||||
``FROM`` can appear multiple times within a single Dockerfile in order
|
||||
to create multiple images. Simply make a note of the last image id
|
||||
output by the commit before each new ``FROM`` command.
|
||||
|
||||
3.2 MAINTAINER
|
||||
--------------
|
||||
|
||||
``MAINTAINER <name>``
|
||||
|
||||
The `MAINTAINER` instruction allows you to set the Author field of the generated
|
||||
images.
|
||||
The ``MAINTAINER`` instruction allows you to set the *Author* field of
|
||||
the generated images.
|
||||
|
||||
2.3 RUN
|
||||
3.3 RUN
|
||||
-------
|
||||
|
||||
``RUN <command>``
|
||||
|
||||
The `RUN` instruction will execute any commands on the current image and commit
|
||||
the results. The resulting committed image will be used for the next step in the
|
||||
Dockerfile.
|
||||
The ``RUN`` instruction will execute any commands on the current image
|
||||
and commit the results. The resulting committed image will be used for
|
||||
the next step in the Dockerfile.
|
||||
|
||||
Layering `RUN` instructions and generating commits conforms to the
|
||||
core concepts of Docker where commits are cheap and containers can be created
|
||||
from any point in an image's history, much like source control.
|
||||
Layering ``RUN`` instructions and generating commits conforms to the
|
||||
core concepts of Docker where commits are cheap and containers can be
|
||||
created from any point in an image's history, much like source
|
||||
control.
|
||||
|
||||
2.4 CMD
|
||||
3.4 CMD
|
||||
-------
|
||||
|
||||
``CMD <command>``
|
||||
|
||||
The `CMD` instruction sets the command to be executed when running the image.
|
||||
This is functionally equivalent to running
|
||||
`docker commit -run '{"Cmd": <command>}'` outside the builder.
|
||||
The ``CMD`` instruction sets the command to be executed when running
|
||||
the image. This is functionally equivalent to running ``docker commit
|
||||
-run '{"Cmd": <command>}'`` outside the builder.
|
||||
|
||||
.. note::
|
||||
Don't confuse `RUN` with `CMD`. `RUN` actually runs a command and commits
|
||||
the result; `CMD` does not execute anything at build time, but specifies the
|
||||
intended command for the image.
|
||||
.. note::
|
||||
Don't confuse `RUN` with `CMD`. `RUN` actually runs a
|
||||
command and commits the result; `CMD` does not execute anything at
|
||||
build time, but specifies the intended command for the image.
|
||||
|
||||
2.5 EXPOSE
|
||||
3.5 EXPOSE
|
||||
----------
|
||||
|
||||
``EXPOSE <port> [<port>...]``
|
||||
|
||||
The `EXPOSE` instruction sets ports to be publicly exposed when running the
|
||||
image. This is functionally equivalent to running
|
||||
`docker commit -run '{"PortSpecs": ["<port>", "<port2>"]}'` outside the builder.
|
||||
The ``EXPOSE`` instruction sets ports to be publicly exposed when
|
||||
running the image. This is functionally equivalent to running ``docker
|
||||
commit -run '{"PortSpecs": ["<port>", "<port2>"]}'`` outside the
|
||||
builder.
|
||||
|
||||
2.6 ENV
|
||||
3.6 ENV
|
||||
-------
|
||||
|
||||
``ENV <key> <value>``
|
||||
|
||||
The `ENV` instruction sets the environment variable `<key>` to the value
|
||||
`<value>`. This value will be passed to all future ``RUN`` instructions. This is
|
||||
functionally equivalent to prefixing the command with `<key>=<value>`
|
||||
The ``ENV`` instruction sets the environment variable ``<key>`` to the
|
||||
value ``<value>``. This value will be passed to all future ``RUN``
|
||||
instructions. This is functionally equivalent to prefixing the command
|
||||
with ``<key>=<value>``
|
||||
|
||||
.. note::
|
||||
The environment variables will persist when a container is run from the resulting image.
|
||||
.. note::
|
||||
The environment variables will persist when a container is run
|
||||
from the resulting image.
|
||||
|
||||
2.7 ADD
|
||||
3.7 ADD
|
||||
-------
|
||||
|
||||
``ADD <src> <dest>``
|
||||
|
||||
The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
|
||||
The ``ADD`` instruction will copy new files from <src> and add them to
|
||||
the container's filesystem at path ``<dest>``.
|
||||
|
||||
`<src>` must be the path to a file or directory relative to the source directory being built (also called the
|
||||
context of the build) or a remote file URL.
|
||||
``<src>`` must be the path to a file or directory relative to the
|
||||
source directory being built (also called the *context* of the build) or
|
||||
a remote file URL.
|
||||
|
||||
`<dest>` is the path at which the source will be copied in the destination container.
|
||||
``<dest>`` is the path at which the source will be copied in the
|
||||
destination container.
|
||||
|
||||
The copy obeys the following rules:
|
||||
|
||||
If `<src>` is a directory, the entire directory is copied, including filesystem metadata.
|
||||
* If ``<src>`` is a directory, the entire directory is copied,
|
||||
including filesystem metadata.
|
||||
* If ``<src>``` is a tar archive in a recognized compression format
|
||||
(identity, gzip, bzip2 or xz), it is unpacked as a directory.
|
||||
|
||||
If `<src>` is a tar archive in a recognized compression format (identity, gzip, bzip2 or xz), it
|
||||
is unpacked as a directory.
|
||||
When a directory is copied or unpacked, it has the same behavior as
|
||||
``tar -x``: the result is the union of
|
||||
|
||||
When a directory is copied or unpacked, it has the same behavior as 'tar -x': the result is the union of
|
||||
a) whatever existed at the destination path and b) the contents of the source tree, with conflicts resolved
|
||||
in favor of b on a file-by-file basis.
|
||||
1. whatever existed at the destination path and
|
||||
2. the contents of the source tree,
|
||||
|
||||
If `<src>` is any other kind of file, it is copied individually along with its metadata. In this case,
|
||||
if `<dst>` ends with a trailing slash '/', it will be considered a directory and the contents of `<src>`
|
||||
will be written at `<dst>/base(<src>)`.
|
||||
If `<dst>` does not end with a trailing slash, it will be considered a regular file and the contents
|
||||
of `<src>` will be written at `<dst>`.
|
||||
with conflicts resolved in favor of 2) on a file-by-file basis.
|
||||
|
||||
If `<dest>` doesn't exist, it is created along with all missing directories in its path. All new
|
||||
files and directories are created with mode 0700, uid and gid 0.
|
||||
* If ``<src>`` is any other kind of file, it is copied individually
|
||||
along with its metadata. In this case, if ``<dst>`` ends with a
|
||||
trailing slash ``/``, it will be considered a directory and the
|
||||
contents of ``<src>`` will be written at ``<dst>/base(<src>)``.
|
||||
* If ``<dst>`` does not end with a trailing slash, it will be
|
||||
considered a regular file and the contents of ``<src>`` will be
|
||||
written at ``<dst>``.
|
||||
* If ``<dest>`` doesn't exist, it is created along with all missing
|
||||
directories in its path. All new files and directories are created
|
||||
with mode 0700, uid and gid 0.
|
||||
|
||||
2.8 ENTRYPOINT
|
||||
3.8 ENTRYPOINT
|
||||
-------------
|
||||
|
||||
``ENTRYPOINT /bin/echo``
|
||||
|
||||
The `ENTRYPOINT` instruction adds an entry command that will not be overwritten when arguments are passed to docker run, unlike the behavior of `CMD`. This allows arguments to be passed to the entrypoint. i.e. `docker run <image> -d` will pass the "-d" argument to the entrypoint.
|
||||
The ``ENTRYPOINT`` instruction adds an entry command that will not be
|
||||
overwritten when arguments are passed to docker run, unlike the
|
||||
behavior of ``CMD``. This allows arguments to be passed to the
|
||||
entrypoint. i.e. ``docker run <image> -d`` will pass the "-d" argument
|
||||
to the entrypoint.
|
||||
|
||||
2.9 VOLUME
|
||||
3.9 VOLUME
|
||||
----------
|
||||
|
||||
``VOLUME ["/data"]``
|
||||
|
||||
The `VOLUME` instruction will add one or more new volumes to any container created from the image.
|
||||
The ``VOLUME`` instruction will add one or more new volumes to any
|
||||
container created from the image.
|
||||
|
||||
3. Dockerfile Examples
|
||||
4. Dockerfile Examples
|
||||
======================
|
||||
|
||||
.. code-block:: bash
|
||||
|
|
|
@ -7,21 +7,69 @@
|
|||
Working with Repositories
|
||||
=========================
|
||||
|
||||
A *repository* is a hosted collection of tagged :ref:`images
|
||||
<image_def>` that together create the file system for a container. The
|
||||
repository's name is a tag that indicates the provenance of the
|
||||
repository, i.e. who created it and where the original copy is
|
||||
located.
|
||||
|
||||
Top-level repositories and user repositories
|
||||
--------------------------------------------
|
||||
You can find one or more repositories hosted on a *registry*. There
|
||||
can be an implicit or explicit host name as part of the repository
|
||||
tag. The implicit registry is located at ``index.docker.io``, the home
|
||||
of "top-level" repositories and the Central Index. This registry may
|
||||
also include public "user" repositories.
|
||||
|
||||
Generally, there are two types of repositories: Top-level repositories
|
||||
which are controlled by the people behind Docker, and user
|
||||
repositories.
|
||||
So Docker is not only a tool for creating and managing your own
|
||||
:ref:`containers <container_def>` -- **Docker is also a tool for
|
||||
sharing**. The Docker project provides a Central Registry to host
|
||||
public repositories, namespaced by user, and a Central Index which
|
||||
provides user authentication and search over all the public
|
||||
repositories. You can host your own Registry too! Docker acts as a
|
||||
client for these services via ``docker search, pull, login`` and
|
||||
``push``.
|
||||
|
||||
* 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 images are not checked, it is therefore up to you whether or not you trust the creator of this image.
|
||||
Top-level, User, and Your Own Repositories
|
||||
------------------------------------------
|
||||
|
||||
There are two types of public repositories: *top-level* repositories
|
||||
which are controlled by the Docker team, and *user* repositories
|
||||
created by individual contributors.
|
||||
|
||||
Find public images available on the index
|
||||
-----------------------------------------
|
||||
* 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 if you push to the public Central Registry.
|
||||
* Only the authenticated user can push to their *username* namespace
|
||||
on the Central Registry.
|
||||
* User images are not checked, it is therefore up to you whether or
|
||||
not you trust the creator of this image.
|
||||
|
||||
Right now (version 0.5), private repositories are only possible by
|
||||
hosting `your own registry
|
||||
<https://github.com/dotcloud/docker-registry>`_. To push or pull to a
|
||||
repository on your own registry, you must prefix the tag with the
|
||||
address of the registry's host, like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Tag to create a repository with the full registry location.
|
||||
# The location (e.g. localhost.localdomain:5000) becomes
|
||||
# a permanent part of the repository name
|
||||
docker tag 0u812deadbeef localhost.localdomain:5000/repo_name
|
||||
|
||||
# Push the new repository to its home location on localhost
|
||||
docker push localhost.localdomain:5000/repo_name
|
||||
|
||||
Once a repository has your registry's host name as part of the tag,
|
||||
you can push and pull it like any other repository, but it will
|
||||
**not** be searchable (or indexed at all) in the Central Index, and
|
||||
there will be no user name checking performed. Your registry will
|
||||
function completely independently from the Central Index.
|
||||
|
||||
Find public images available on the Central Index
|
||||
-------------------------------------------------
|
||||
|
||||
Seach by name, namespace or description
|
||||
|
||||
|
@ -37,68 +85,48 @@ Download them simply by their name
|
|||
docker pull <value>
|
||||
|
||||
|
||||
Very similarly you can search for and browse the index online on https://index.docker.io
|
||||
Very similarly you can search for and browse the index online on
|
||||
https://index.docker.io
|
||||
|
||||
|
||||
Connecting to the repository
|
||||
----------------------------
|
||||
Connecting to the Central Registry
|
||||
----------------------------------
|
||||
|
||||
You can create a user on the central docker repository online, or by running
|
||||
You can create a user on the central Docker Index online, or by running
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
docker login
|
||||
|
||||
This will prompt you for a username, which will become a public
|
||||
namespace for your public repositories.
|
||||
|
||||
If your username does not exist it will prompt you to also enter a password and your e-mail address. It will then
|
||||
automatically log you in.
|
||||
If your username does not exist it will prompt you to also enter a
|
||||
password and your e-mail address. It will then automatically log you
|
||||
in.
|
||||
|
||||
|
||||
Committing a container to a named image
|
||||
---------------------------------------
|
||||
|
||||
In order to commit to the repository it is required to have committed your container to an image with your namespace.
|
||||
In order to commit to the repository it is required to have committed
|
||||
your container to an image within your username namespace.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker commit $CONTAINER_ID dhrp/kickassapp
|
||||
docker commit <container_id> <your username>/<some_name>
|
||||
docker commit <container_id> <username>/<repo_name>
|
||||
|
||||
|
||||
Pushing a container to the repository
|
||||
-----------------------------------------
|
||||
Pushing a container to its repository
|
||||
------------------------------------
|
||||
|
||||
In order to push an image to the repository you need to have committed your container to a named image (see above)
|
||||
In order to push an image to its repository you need to have committed
|
||||
your container to a named image (see above)
|
||||
|
||||
Now you can commit this image to the repository
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# for example docker push dhrp/kickassapp
|
||||
docker push <image-name>
|
||||
|
||||
|
||||
Changing the server to connect to
|
||||
----------------------------------
|
||||
|
||||
When you are running your own index and/or registry, You can change the server the docker client will connect to.
|
||||
|
||||
Variable
|
||||
^^^^^^^^
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
DOCKER_INDEX_URL
|
||||
|
||||
Setting this environment variable on the docker server will change the URL docker index.
|
||||
This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
|
||||
The docker daemon doesn't need to be restarted for this parameter to take effect.
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
docker -d &
|
||||
export DOCKER_INDEX_URL="https://index.docker.io"
|
||||
|
||||
docker push <username>/<repo_name>
|
||||
|
|
|
@ -30,11 +30,12 @@ up-to-date.
|
|||
|
||||
* CATEGORY should describe which part of the project is affected.
|
||||
Valid categories are:
|
||||
* Runtime
|
||||
* Remote API
|
||||
* Builder
|
||||
* Documentation
|
||||
* Hack
|
||||
* Packaging
|
||||
* Remote API
|
||||
* Runtime
|
||||
|
||||
* DESCRIPTION: a concise description of the change that is relevant to the end-user,
|
||||
using the present tense.
|
||||
|
@ -53,6 +54,10 @@ up-to-date.
|
|||
|
||||
### 4. Run all tests
|
||||
|
||||
```bash
|
||||
$ make test
|
||||
```
|
||||
|
||||
### 5. Commit and create a pull request
|
||||
|
||||
```bash
|
||||
|
@ -109,11 +114,20 @@ up-to-date.
|
|||
|
||||
### 9. Publish Ubuntu packages
|
||||
|
||||
If everything went well in the previous step, you can finalize the release by submitting the Ubuntu packages.
|
||||
If everything went well in the previous step, you can finalize the release by submitting the Ubuntu
|
||||
packages.
|
||||
|
||||
```bash
|
||||
$ RELEASE_IMAGE=image_provided_by_infrastructure_maintainers
|
||||
$ docker run -e RELEASE_PPA=1 $RELEASE_IMAGE
|
||||
```
|
||||
|
||||
If that goes well, congratulations! You're done.
|
||||
If that goes well, Ubuntu Precise package is in its way. It will take anywhere from 0.5 to 30 hours
|
||||
for the builders to complete their job depending on builder demand at this time. At this point, Quantal
|
||||
and Raring packages need to be created using the Launchpad interface:
|
||||
https://launchpad.net/~dotcloud/+archive/lxc-docker/+packages
|
||||
|
||||
Notify [the packager maintainers](https://github.com/dotcloud/docker/blob/master/packaging/MAINTAINERS)
|
||||
who will ensure PPA is ready.
|
||||
|
||||
Congratulations! You're done
|
||||
|
|
|
@ -68,6 +68,7 @@ func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
|
|||
from.CloseWrite()
|
||||
}
|
||||
}
|
||||
to.CloseRead()
|
||||
event <- written
|
||||
}
|
||||
utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr())
|
||||
|
|
|
@ -13,8 +13,8 @@ PKG_NAME=lxc-docker
|
|||
ROOT_PATH=$(shell git rev-parse --show-toplevel)
|
||||
GITHUB_PATH=github.com/dotcloud/docker
|
||||
BUILD_SRC=build_src
|
||||
VERSION_TAG?=v$(shell sed -E 's/.+\((.+)-.+\).+/\1/;q' changelog)
|
||||
VERSION=$(shell echo ${VERSION_TAG} | cut -c2-)
|
||||
VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
|
||||
VERSION_TAG?=v${VERSION}
|
||||
DOCKER_VERSION=${PKG_NAME}_${VERSION}
|
||||
|
||||
all:
|
||||
|
@ -28,7 +28,6 @@ install:
|
|||
mkdir -p $(DESTDIR)/usr/share/doc/lxc-docker
|
||||
install -m 0755 src/${GITHUB_PATH}/docker/docker $(DESTDIR)/usr/bin/lxc-docker
|
||||
cp debian/lxc-docker.1 $(DESTDIR)/usr/share/man/man1
|
||||
cp debian/CHANGELOG.md $(DESTDIR)/usr/share/doc/lxc-docker/changelog
|
||||
|
||||
debian:
|
||||
# Prepare docker source from revision ${VERSION_TAG}
|
||||
|
@ -41,6 +40,7 @@ debian:
|
|||
cp -r `ls | grep -v ${BUILD_SRC}` ${BUILD_SRC}/debian
|
||||
cp ${ROOT_PATH}/README.md ${BUILD_SRC}
|
||||
cp ${ROOT_PATH}/CHANGELOG.md ${BUILD_SRC}/debian
|
||||
./parse_changelog.py < ../../CHANGELOG.md > ${BUILD_SRC}/debian/changelog
|
||||
# Cleanup
|
||||
rm -rf `find . -name '.git*'`
|
||||
rm -f ${DOCKER_VERSION}*
|
||||
|
|
|
@ -13,6 +13,9 @@ Vagrant::Config.run do |config|
|
|||
|
||||
# Install debian packaging dependencies and create debian packages
|
||||
pkg_cmd = "apt-get -qq update; DEBIAN_FRONTEND=noninteractive apt-get install -qq -y #{PKG_DEP}; " \
|
||||
"curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz; " \
|
||||
"tar -C /usr/local -xzf /go.tar.gz; rm /usr/bin/go; " \
|
||||
"ln -s /usr/local/go/bin/go /usr/bin; "\
|
||||
"export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/debian; make debian"
|
||||
config.vm.provision :shell, :inline => pkg_cmd
|
||||
end
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'Parse main CHANGELOG.md from stdin outputing on stdout the debian changelog'
|
||||
|
||||
import sys,re, datetime
|
||||
|
||||
on_block=False
|
||||
for line in sys.stdin.readlines():
|
||||
line = line.strip()
|
||||
if line.startswith('# ') or len(line) == 0:
|
||||
continue
|
||||
if line.startswith('## '):
|
||||
if on_block:
|
||||
print '\n -- dotCloud <ops@dotcloud.com> {0}\n'.format(date)
|
||||
version, date = line[3:].split()
|
||||
date = datetime.datetime.strptime(date, '(%Y-%m-%d)').strftime(
|
||||
'%a, %d %b %Y 00:00:00 -0700')
|
||||
on_block = True
|
||||
print 'lxc-docker ({0}-1) precise; urgency=low'.format(version)
|
||||
continue
|
||||
if on_block:
|
||||
print ' ' + line
|
||||
print '\n -- dotCloud <ops@dotcloud.com> {0}'.format(date)
|
|
@ -141,7 +141,6 @@ func (runtime *Runtime) Register(container *Container) error {
|
|||
utils.Debugf("Restarting")
|
||||
container.State.Ghost = false
|
||||
container.State.setStopped(0)
|
||||
// assume empty host config
|
||||
hostConfig := &HostConfig{}
|
||||
if err := container.Start(hostConfig); err != nil {
|
||||
return err
|
||||
|
|
|
@ -114,26 +114,6 @@ func init() {
|
|||
|
||||
// FIXME: test that ImagePull(json=true) send correct json output
|
||||
|
||||
func newTestRuntime() (*Runtime, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Remove(root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime, err := NewRuntimeFromDirectory(root, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.UpdateCapabilities(true)
|
||||
return runtime, nil
|
||||
}
|
||||
|
||||
func GetTestImage(runtime *Runtime) *Image {
|
||||
imgs, err := runtime.graph.All()
|
||||
if err != nil {
|
||||
|
@ -148,10 +128,7 @@ func GetTestImage(runtime *Runtime) *Image {
|
|||
}
|
||||
|
||||
func TestRuntimeCreate(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
// Make sure we start we 0 containers
|
||||
|
@ -223,10 +200,7 @@ func TestRuntimeCreate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDestroy(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, err := NewBuilder(runtime).Create(&Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
|
@ -270,10 +244,7 @@ func TestDestroy(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
|
@ -323,11 +294,8 @@ func TestGet(t *testing.T) {
|
|||
}
|
||||
|
||||
func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, string) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var err error
|
||||
runtime := mkRuntime(t)
|
||||
port := 5554
|
||||
var container *Container
|
||||
var strPort string
|
||||
|
|
61
server.go
61
server.go
|
@ -1,6 +1,7 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
@ -98,7 +100,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
|
|||
return "", err
|
||||
}
|
||||
|
||||
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
|
||||
if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// FIXME: Handle custom repo, tag comment, author
|
||||
|
@ -247,6 +249,40 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
|
|||
|
||||
}
|
||||
|
||||
func (srv *Server) ContainerTop(name string) ([]APITop, error) {
|
||||
if container := srv.runtime.Get(name); container != nil {
|
||||
output, err := exec.Command("lxc-ps", "--name", container.ID).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error trying to use lxc-ps: %s (%s)", err, output)
|
||||
}
|
||||
var procs []APITop
|
||||
for i, line := range strings.Split(string(output), "\n") {
|
||||
if i == 0 || len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
proc := APITop{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(line))
|
||||
scanner.Split(bufio.ScanWords)
|
||||
if !scanner.Scan() {
|
||||
return nil, fmt.Errorf("Error trying to use lxc-ps")
|
||||
}
|
||||
// no scanner.Text because we skip container id
|
||||
scanner.Scan()
|
||||
proc.PID = scanner.Text()
|
||||
scanner.Scan()
|
||||
proc.Tty = scanner.Text()
|
||||
scanner.Scan()
|
||||
proc.Time = scanner.Text()
|
||||
scanner.Scan()
|
||||
proc.Cmd = scanner.Text()
|
||||
procs = append(procs, proc)
|
||||
}
|
||||
return procs, nil
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
func (srv *Server) ContainerChanges(name string) ([]Change, error) {
|
||||
if container := srv.runtime.Get(name); container != nil {
|
||||
return container.Changes()
|
||||
|
@ -343,7 +379,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
|||
return err
|
||||
}
|
||||
defer layer.Close()
|
||||
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
|
||||
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%8v/%v (%v)"), sf), false, img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -666,7 +702,7 @@ 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, sf.FormatProgress("Pushing", "%v/%v (%v)"), sf), ep, token); err != nil {
|
||||
if err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("Pushing", "%8v/%v (%v)"), sf), ep, token); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -736,7 +772,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
|
||||
archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%8v/%v (%v)"), sf)
|
||||
}
|
||||
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
|
||||
if err != nil {
|
||||
|
@ -834,7 +870,6 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
|
|||
if len(srv.runtime.repositories.ByID()[id]) != 0 {
|
||||
return ErrImageReferenced
|
||||
}
|
||||
|
||||
// If the image is not referenced but has children, go recursive
|
||||
referenced := false
|
||||
byParents, err := srv.runtime.graph.ByParent()
|
||||
|
@ -888,8 +923,22 @@ func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error {
|
|||
}
|
||||
|
||||
func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, error) {
|
||||
//Untag the current image
|
||||
imgs := []APIRmi{}
|
||||
|
||||
//If delete by id, see if the id belong only to one repository
|
||||
if strings.Contains(img.ID, repoName) && tag == "" {
|
||||
for _, repoAndTag := range srv.runtime.repositories.ByID()[img.ID] {
|
||||
parsedRepo := strings.Split(repoAndTag, ":")[0]
|
||||
if strings.Contains(img.ID, repoName) {
|
||||
repoName = parsedRepo
|
||||
} else if repoName != parsedRepo {
|
||||
// the id belongs to multiple repos, like base:latest and user:test,
|
||||
// in that case return conflict
|
||||
return imgs, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
//Untag the current image
|
||||
tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -5,10 +5,7 @@ import (
|
|||
)
|
||||
|
||||
func TestContainerTagImageDelete(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -62,10 +59,7 @@ func TestContainerTagImageDelete(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateRm(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -95,10 +89,7 @@ func TestCreateRm(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
@ -154,11 +145,9 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRunWithTooLowMemoryLimit(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
var err error
|
||||
runtime := mkRuntime(t)
|
||||
srv := &Server{runtime: runtime}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime)
|
||||
// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
|
||||
_, err = srv.ContainerCreate(
|
||||
|
|
|
@ -3,10 +3,10 @@ package docker
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
@ -27,10 +27,7 @@ func changeUser(u string) {
|
|||
if u == "" {
|
||||
return
|
||||
}
|
||||
userent, err := user.LookupId(u)
|
||||
if err != nil {
|
||||
userent, err = user.Lookup(u)
|
||||
}
|
||||
userent, err := utils.UserLookup(u)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to find user %v: %v", u, err)
|
||||
}
|
||||
|
|
8
tags.go
8
tags.go
|
@ -204,15 +204,15 @@ func (store *TagStore) GetImage(repoName, tagOrID string) (*Image, error) {
|
|||
} else if repo == nil {
|
||||
return nil, nil
|
||||
}
|
||||
//go through all the tags, to see if tag is in fact an ID
|
||||
if revision, exists := repo[tagOrID]; exists {
|
||||
return store.graph.Get(revision)
|
||||
}
|
||||
// If no matching tag is found, search through images for a matching image id
|
||||
for _, revision := range repo {
|
||||
if strings.HasPrefix(revision, tagOrID) {
|
||||
return store.graph.Get(revision)
|
||||
}
|
||||
}
|
||||
if revision, exists := repo[tagOrID]; exists {
|
||||
return store.graph.Get(revision)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,7 @@ import (
|
|||
)
|
||||
|
||||
func TestLookupImage(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil {
|
||||
|
|
39
utils.go
39
utils.go
|
@ -1,5 +1,9 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
|
||||
// If OpenStdin is set, then it differs
|
||||
func CompareConfig(a, b *Config) bool {
|
||||
|
@ -68,6 +72,20 @@ func MergeConfig(userConf, imageConf *Config) {
|
|||
}
|
||||
if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
|
||||
userConf.PortSpecs = imageConf.PortSpecs
|
||||
} else {
|
||||
for _, imagePortSpec := range imageConf.PortSpecs {
|
||||
found := false
|
||||
imageNat, _ := parseNat(imagePortSpec)
|
||||
for _, userPortSpec := range userConf.PortSpecs {
|
||||
userNat, _ := parseNat(userPortSpec)
|
||||
if imageNat.Proto == userNat.Proto && imageNat.Frontend == userNat.Frontend {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !userConf.Tty {
|
||||
userConf.Tty = imageConf.Tty
|
||||
|
@ -80,17 +98,38 @@ func MergeConfig(userConf, imageConf *Config) {
|
|||
}
|
||||
if userConf.Env == nil || len(userConf.Env) == 0 {
|
||||
userConf.Env = imageConf.Env
|
||||
} else {
|
||||
for _, imageEnv := range imageConf.Env {
|
||||
found := false
|
||||
imageEnvKey := strings.Split(imageEnv, "=")[0]
|
||||
for _, userEnv := range userConf.Env {
|
||||
userEnvKey := strings.Split(userEnv, "=")[0]
|
||||
if imageEnvKey == userEnvKey {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
userConf.Env = append(userConf.Env, imageEnv)
|
||||
}
|
||||
}
|
||||
}
|
||||
if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
|
||||
userConf.Cmd = imageConf.Cmd
|
||||
}
|
||||
if userConf.Dns == nil || len(userConf.Dns) == 0 {
|
||||
userConf.Dns = imageConf.Dns
|
||||
} else {
|
||||
//duplicates aren't an issue here
|
||||
userConf.Dns = append(userConf.Dns, imageConf.Dns...)
|
||||
}
|
||||
if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
|
||||
userConf.Entrypoint = imageConf.Entrypoint
|
||||
}
|
||||
if userConf.Volumes == nil || len(userConf.Volumes) == 0 {
|
||||
userConf.Volumes = imageConf.Volumes
|
||||
} else {
|
||||
for k, v := range imageConf.Volumes {
|
||||
userConf.Volumes[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
@ -87,7 +88,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
|
|||
}
|
||||
if r.readProgress-r.lastUpdate > updateEvery || err != nil {
|
||||
if r.readTotal > 0 {
|
||||
fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%2.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
|
||||
fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
|
||||
} else {
|
||||
fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
|
||||
}
|
||||
|
@ -106,7 +107,7 @@ func (r *progressReader) Close() error {
|
|||
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)"))
|
||||
tpl = string(sf.FormatProgress("", "%8v/%v (%v)"))
|
||||
}
|
||||
return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
|
||||
}
|
||||
|
@ -147,7 +148,7 @@ func HumanSize(size int64) string {
|
|||
sizef = sizef / 1000.0
|
||||
i++
|
||||
}
|
||||
return fmt.Sprintf("%5.4g %s", sizef, units[i])
|
||||
return fmt.Sprintf("%.4g %s", sizef, units[i])
|
||||
}
|
||||
|
||||
func Trunc(s string, maxlen int) string {
|
||||
|
@ -713,3 +714,26 @@ func ParseRepositoryTag(repos string) (string, string) {
|
|||
}
|
||||
return repos, ""
|
||||
}
|
||||
|
||||
// UserLookup check if the given username or uid is present in /etc/passwd
|
||||
// and returns the user struct.
|
||||
// If the username is not found, an error is returned.
|
||||
func UserLookup(uid string) (*user.User, error) {
|
||||
file, err := ioutil.ReadFile("/etc/passwd")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, line := range strings.Split(string(file), "\n") {
|
||||
data := strings.Split(line, ":")
|
||||
if len(data) > 5 && (data[0] == uid || data[2] == uid) {
|
||||
return &user.User{
|
||||
Uid: data[2],
|
||||
Gid: data[3],
|
||||
Username: data[0],
|
||||
Name: data[4],
|
||||
HomeDir: data[5],
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("User not found in /etc/passwd")
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -264,14 +265,16 @@ func TestCompareKernelVersion(t *testing.T) {
|
|||
|
||||
func TestHumanSize(t *testing.T) {
|
||||
|
||||
size1000 := HumanSize(1000)
|
||||
if size1000 != " 1 kB" {
|
||||
t.Errorf("1000 -> expected 1 kB, got %s", size1000)
|
||||
size := strings.Trim(HumanSize(1000), " \t")
|
||||
expect := "1 kB"
|
||||
if size != expect {
|
||||
t.Errorf("1000 -> expected '%s', got '%s'", expect, size)
|
||||
}
|
||||
|
||||
size1024 := HumanSize(1024)
|
||||
if size1024 != "1.024 kB" {
|
||||
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
|
||||
size = strings.Trim(HumanSize(1024), " \t")
|
||||
expect = "1.024 kB"
|
||||
if size != expect {
|
||||
t.Errorf("1024 -> expected '%s', got '%s'", expect, size)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
101
utils_test.go
101
utils_test.go
|
@ -1,6 +1,7 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -15,14 +16,40 @@ import (
|
|||
|
||||
// Create a temporary runtime suitable for unit testing.
|
||||
// Call t.Fatal() at the first error.
|
||||
func mkRuntime(t *testing.T) *Runtime {
|
||||
func mkRuntime(f Fataler) *Runtime {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
f.Fatal(err)
|
||||
}
|
||||
return runtime
|
||||
}
|
||||
|
||||
// A common interface to access the Fatal method of
|
||||
// both testing.B and testing.T.
|
||||
type Fataler interface {
|
||||
Fatal(args ...interface{})
|
||||
}
|
||||
|
||||
func newTestRuntime() (*Runtime, error) {
|
||||
root, err := ioutil.TempDir("", "docker-test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Remove(root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime, err := NewRuntimeFromDirectory(root, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.UpdateCapabilities(true)
|
||||
return runtime, nil
|
||||
}
|
||||
|
||||
// Write `content` to the file at path `dst`, creating it if necessary,
|
||||
// as well as any missing directories.
|
||||
// The file is truncated if it already exists.
|
||||
|
@ -60,17 +87,18 @@ func readFile(src string, t *testing.T) (content string) {
|
|||
// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image.
|
||||
// The caller is responsible for destroying the container.
|
||||
// Call t.Fatal() at the first error.
|
||||
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig) {
|
||||
func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) {
|
||||
config, hostConfig, _, err := ParseRun(args, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
config.Image = GetTestImage(r).ID
|
||||
c, err := NewBuilder(r).Create(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return c, hostConfig
|
||||
return c, hostConfig, nil
|
||||
}
|
||||
|
||||
// Create a test container, start it, wait for it to complete, destroy it,
|
||||
|
@ -83,7 +111,10 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e
|
|||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
container, hostConfig := mkContainer(r, args, t)
|
||||
container, hostConfig, err := mkContainer(r, args, t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer r.Destroy(container)
|
||||
stdout, err := container.StdoutPipe()
|
||||
if err != nil {
|
||||
|
@ -101,3 +132,61 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e
|
|||
output = string(data)
|
||||
return
|
||||
}
|
||||
|
||||
func TestMergeConfig(t *testing.T) {
|
||||
volumesImage := make(map[string]struct{})
|
||||
volumesImage["/test1"] = struct{}{}
|
||||
volumesImage["/test2"] = struct{}{}
|
||||
configImage := &Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111:1111", "2222:2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
Volumes: volumesImage,
|
||||
}
|
||||
|
||||
volumesUser := make(map[string]struct{})
|
||||
volumesUser["/test3"] = struct{}{}
|
||||
configUser := &Config{
|
||||
Dns: []string{"3.3.3.3"},
|
||||
PortSpecs: []string{"2222:3333", "3333:3333"},
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: volumesUser,
|
||||
}
|
||||
|
||||
MergeConfig(configUser, configImage)
|
||||
|
||||
if len(configUser.Dns) != 3 {
|
||||
t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns))
|
||||
}
|
||||
for _, dns := range configUser.Dns {
|
||||
if dns != "1.1.1.1" && dns != "2.2.2.2" && dns != "3.3.3.3" {
|
||||
t.Fatalf("Expected 1.1.1.1 or 2.2.2.2 or 3.3.3.3, found %s", dns)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.PortSpecs) != 3 {
|
||||
t.Fatalf("Expected 3 portSpecs, 1111:1111, 2222:3333 and 3333:3333, found %d", len(configUser.PortSpecs))
|
||||
}
|
||||
for _, portSpecs := range configUser.PortSpecs {
|
||||
if portSpecs != "1111:1111" && portSpecs != "2222:3333" && portSpecs != "3333:3333" {
|
||||
t.Fatalf("Expected 1111:1111 or 2222:3333 or 3333:3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
if len(configUser.Env) != 3 {
|
||||
t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
|
||||
}
|
||||
for _, env := range configUser.Env {
|
||||
if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
|
||||
t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configUser.Volumes) != 3 {
|
||||
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
|
||||
}
|
||||
for v, _ := range configUser.Volumes {
|
||||
if v != "/test1" && v != "/test2" && v != "/test3" {
|
||||
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue