Merge pull request #432 from dotcloud/remote-api

+ Remote API: Implement the remote API.
This commit is contained in:
Guillaume J. Charmes 2013-05-09 22:43:47 -07:00
commit 26bfeb1d67
27 changed files with 4640 additions and 1185 deletions

618
api.go Normal file
View file

@ -0,0 +1,618 @@
package docker
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/gorilla/mux"
"github.com/shin-/cookiejar"
"io"
"log"
"net/http"
"strconv"
"strings"
)
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
return nil, nil, err
}
// Flush the options to make sure the client sets the raw mode
conn.Write([]byte{})
return conn, conn, nil
}
//If we don't do this, POST method without Content-type (even with empty body) will fail
func parseForm(r *http.Request) error {
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
return err
}
return nil
}
func httpError(w http.ResponseWriter, err error) {
if strings.HasPrefix(err.Error(), "No such") {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
config := &auth.AuthConfig{
Username: srv.runtime.authConfig.Username,
Email: srv.runtime.authConfig.Email,
}
b, err := json.Marshal(config)
if err != nil {
return nil, err
}
return b, nil
}
func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
config := &auth.AuthConfig{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return nil, err
}
if config.Username == srv.runtime.authConfig.Username {
config.Password = srv.runtime.authConfig.Password
}
newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root)
status, err := auth.Login(newAuthConfig)
if err != nil {
return nil, err
} else {
srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
srv.runtime.authConfig = newAuthConfig
}
if status != "" {
b, err := json.Marshal(&ApiAuth{Status: status})
if err != nil {
return nil, err
}
return b, nil
}
w.WriteHeader(http.StatusNoContent)
return nil, nil
}
func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
m := srv.DockerVersion()
b, err := json.Marshal(m)
if err != nil {
return nil, err
}
return b, nil
}
func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerKill(name); err != nil {
return nil, err
}
w.WriteHeader(http.StatusNoContent)
return nil, nil
}
func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerExport(name, w); err != nil {
Debugf("%s", err.Error())
//return nil, err
}
return nil, nil
}
func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
all := r.Form.Get("all") == "1"
filter := r.Form.Get("filter")
only_ids := r.Form.Get("only_ids") == "1"
outs, err := srv.Images(all, only_ids, filter)
if err != nil {
return nil, err
}
b, err := json.Marshal(outs)
if err != nil {
return nil, err
}
return b, nil
}
func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := srv.ImagesViz(w); err != nil {
return nil, err
}
return nil, nil
}
func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
out := srv.DockerInfo()
b, err := json.Marshal(out)
if err != nil {
return nil, err
}
return b, nil
}
func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
outs, err := srv.ImageHistory(name)
if err != nil {
return nil, err
}
b, err := json.Marshal(outs)
if err != nil {
return nil, err
}
return b, nil
}
func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
changesStr, err := srv.ContainerChanges(name)
if err != nil {
return nil, err
}
b, err := json.Marshal(changesStr)
if err != nil {
return nil, err
}
return b, nil
}
func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
all := r.Form.Get("all") == "1"
trunc_cmd := r.Form.Get("trunc_cmd") != "0"
only_ids := r.Form.Get("only_ids") == "1"
since := r.Form.Get("since")
before := r.Form.Get("before")
n, err := strconv.Atoi(r.Form.Get("limit"))
if err != nil {
n = -1
}
outs := srv.Containers(all, trunc_cmd, only_ids, n, since, before)
b, err := json.Marshal(outs)
if err != nil {
return nil, err
}
return b, nil
}
func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
repo := r.Form.Get("repo")
tag := r.Form.Get("tag")
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
force := r.Form.Get("force") == "1"
if err := srv.ContainerTag(name, repo, tag, force); err != nil {
return nil, err
}
w.WriteHeader(http.StatusCreated)
return nil, nil
}
func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
var config Config
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
Debugf("%s", err.Error())
}
repo := r.Form.Get("repo")
tag := r.Form.Get("tag")
container := r.Form.Get("container")
author := r.Form.Get("author")
comment := r.Form.Get("comment")
id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config)
if err != nil {
return nil, err
}
b, err := json.Marshal(ApiId{id})
if err != nil {
return nil, err
}
w.WriteHeader(http.StatusCreated)
return b, nil
}
// Creates an image from Pull or from Import
func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
src := r.Form.Get("fromSrc")
image := r.Form.Get("fromImage")
repo := r.Form.Get("repo")
tag := r.Form.Get("tag")
in, out, err := hijackServer(w)
if err != nil {
return nil, err
}
defer in.Close()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if image != "" { //pull
registry := r.Form.Get("registry")
if err := srv.ImagePull(image, tag, registry, out); err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
}
} else { //import
if err := srv.ImageImport(src, repo, tag, in, out); err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
}
}
return nil, nil
}
func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
term := r.Form.Get("term")
outs, err := srv.ImagesSearch(term)
if err != nil {
return nil, err
}
b, err := json.Marshal(outs)
if err != nil {
return nil, err
}
return b, nil
}
func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
url := r.Form.Get("url")
path := r.Form.Get("path")
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
in, out, err := hijackServer(w)
if err != nil {
return nil, err
}
defer in.Close()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if err := srv.ImageInsert(name, url, path, out); err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
}
return nil, nil
}
func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
registry := r.Form.Get("registry")
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
in, out, err := hijackServer(w)
if err != nil {
return nil, err
}
defer in.Close()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if err := srv.ImagePush(name, registry, out); err != nil {
fmt.Fprintln(out, "Error: %s\n", err)
}
return nil, nil
}
func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
in, out, err := hijackServer(w)
if err != nil {
return nil, err
}
defer in.Close()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if err := srv.ImageCreateFromFile(in, out); err != nil {
fmt.Fprintln(out, "Error: %s\n", err)
}
return nil, nil
}
func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
config := &Config{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return nil, err
}
id, err := srv.ContainerCreate(config)
if err != nil {
return nil, err
}
out := &ApiRun{
Id: id,
}
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
}
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
}
b, err := json.Marshal(out)
if err != nil {
return nil, err
}
w.WriteHeader(http.StatusCreated)
return b, nil
}
func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
t, err := strconv.Atoi(r.Form.Get("t"))
if err != nil || t < 0 {
t = 10
}
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerRestart(name, t); err != nil {
return nil, err
}
w.WriteHeader(http.StatusNoContent)
return nil, nil
}
func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
removeVolume := r.Form.Get("v") == "1"
if err := srv.ContainerDestroy(name, removeVolume); err != nil {
return nil, err
}
w.WriteHeader(http.StatusNoContent)
return nil, nil
}
func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ImageDelete(name); err != nil {
return nil, err
}
w.WriteHeader(http.StatusNoContent)
return nil, nil
}
func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerStart(name); err != nil {
return nil, err
}
w.WriteHeader(http.StatusNoContent)
return nil, nil
}
func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
t, err := strconv.Atoi(r.Form.Get("t"))
if err != nil || t < 0 {
t = 10
}
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerStop(name, t); err != nil {
return nil, err
}
w.WriteHeader(http.StatusNoContent)
return nil, nil
}
func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
status, err := srv.ContainerWait(name)
if err != nil {
return nil, err
}
b, err := json.Marshal(&ApiWait{StatusCode: status})
if err != nil {
return nil, err
}
return b, nil
}
func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if err := parseForm(r); err != nil {
return nil, err
}
logs := r.Form.Get("logs") == "1"
stream := r.Form.Get("stream") == "1"
stdin := r.Form.Get("stdin") == "1"
stdout := r.Form.Get("stdout") == "1"
stderr := r.Form.Get("stderr") == "1"
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
in, out, err := hijackServer(w)
if err != nil {
return nil, err
}
defer in.Close()
fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
}
return nil, nil
}
func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
container, err := srv.ContainerInspect(name)
if err != nil {
return nil, err
}
b, err := json.Marshal(container)
if err != nil {
return nil, err
}
return b, nil
}
func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
if vars == nil {
return nil, fmt.Errorf("Missing parameter")
}
name := vars["name"]
image, err := srv.ImageInspect(name)
if err != nil {
return nil, err
}
b, err := json.Marshal(image)
if err != nil {
return nil, err
}
return b, nil
}
func ListenAndServe(addr string, srv *Server, logging bool) error {
r := mux.NewRouter()
log.Printf("Listening for HTTP on %s\n", addr)
m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request, map[string]string) ([]byte, error){
"GET": {
"/auth": getAuth,
"/version": getVersion,
"/info": getInfo,
"/images/json": getImagesJson,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersPs,
"/containers/{name:.*}/export": getContainersExport,
"/containers/{name:.*}/changes": getContainersChanges,
"/containers/{name:.*}/json": getContainersByName,
},
"POST": {
"/auth": postAuth,
"/commit": postCommit,
"/build": postBuild,
"/images/create": postImagesCreate,
"/images/{name:.*}/insert": postImagesInsert,
"/images/{name:.*}/push": postImagesPush,
"/images/{name:.*}/tag": postImagesTag,
"/containers/create": postContainersCreate,
"/containers/{name:.*}/kill": postContainersKill,
"/containers/{name:.*}/restart": postContainersRestart,
"/containers/{name:.*}/start": postContainersStart,
"/containers/{name:.*}/stop": postContainersStop,
"/containers/{name:.*}/wait": postContainersWait,
"/containers/{name:.*}/attach": postContainersAttach,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
"/images/{name:.*}": deleteImages,
},
}
for method, routes := range m {
for route, fct := range routes {
Debugf("Registering %s, %s", method, route)
// NOTE: scope issue, make sure the variables are local and won't be changed
localRoute := route
localMethod := method
localFct := fct
r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Debugf("Calling %s %s", localMethod, localRoute)
if logging {
log.Println(r.Method, r.RequestURI)
}
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
if len(userAgent) == 2 && userAgent[1] != VERSION {
Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
}
}
json, err := localFct(srv, w, r, mux.Vars(r))
if err != nil {
httpError(w, err)
}
if json != nil {
w.Header().Set("Content-Type", "application/json")
w.Write(json)
}
})
}
}
return http.ListenAndServe(addr, r)
}

66
api_params.go Normal file
View file

@ -0,0 +1,66 @@
package docker
type ApiHistory struct {
Id string
Created int64
CreatedBy string
}
type ApiImages struct {
Repository string `json:",omitempty"`
Tag string `json:",omitempty"`
Id string
Created int64 `json:",omitempty"`
}
type ApiInfo struct {
Containers int
Version string
Images int
Debug bool
GoVersion string
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
}
type ApiContainers struct {
Id string
Image string `json:",omitempty"`
Command string `json:",omitempty"`
Created int64 `json:",omitempty"`
Status string `json:",omitempty"`
Ports string `json:",omitempty"`
}
type ApiSearch struct {
Name string
Description string
}
type ApiId struct {
Id string
}
type ApiRun struct {
Id string
Warnings []string
}
type ApiPort struct {
Port string
}
type ApiVersion struct {
Version string
GitCommit string
MemoryLimit bool
SwapLimit bool
}
type ApiWait struct {
StatusCode int
}
type ApiAuth struct {
Status string
}

877
api_test.go Normal file
View file

@ -0,0 +1,877 @@
package docker
import (
"bufio"
"bytes"
"encoding/json"
"github.com/dotcloud/docker/auth"
"io"
"net"
"net/http"
"net/http/httptest"
"os"
"path"
"testing"
"time"
)
func TestGetAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
authConfig := &auth.AuthConfig{
Username: "utest",
Password: "utest",
Email: "utest@yopmail.com",
}
authConfigJson, err := json.Marshal(authConfig)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJson))
if err != nil {
t.Fatal(err)
}
body, err := postAuth(srv, r, req, nil)
if err != nil {
t.Fatal(err)
}
if body == nil {
t.Fatalf("No body received\n")
}
if r.Code != http.StatusOK {
t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
}
if runtime.authConfig.Username != authConfig.Username ||
runtime.authConfig.Password != authConfig.Password ||
runtime.authConfig.Email != authConfig.Email {
t.Fatalf("The auth configuration hasn't been set correctly")
}
}
func TestGetVersion(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
body, err := getVersion(srv, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
v := &ApiVersion{}
err = json.Unmarshal(body, v)
if err != nil {
t.Fatal(err)
}
if v.Version != VERSION {
t.Errorf("Excepted version %s, %s found", VERSION, v.Version)
}
}
func TestGetInfo(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
body, err := getInfo(srv, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
infos := &ApiInfo{}
err = json.Unmarshal(body, infos)
if err != nil {
t.Fatal(err)
}
if infos.Version != VERSION {
t.Errorf("Excepted version %s, %s found", VERSION, infos.Version)
}
}
func TestGetImagesJson(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
// FIXME: Do more tests with filter
req, err := http.NewRequest("GET", "/images/json?quiet=0&all=0", nil)
if err != nil {
t.Fatal(err)
}
body, err := getImagesJson(srv, nil, req, nil)
if err != nil {
t.Fatal(err)
}
images := []ApiImages{}
err = json.Unmarshal(body, &images)
if err != nil {
t.Fatal(err)
}
if len(images) != 1 {
t.Errorf("Excepted 1 image, %d found", len(images))
}
if images[0].Repository != "docker-ut" {
t.Errorf("Excepted image docker-ut, %s found", images[0].Repository)
}
}
func TestGetImagesViz(t *testing.T) {
//FIXME: Implement this test (or remove this endpoint)
t.Log("Test not implemented")
}
func TestGetImagesSearch(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
req, err := http.NewRequest("GET", "/images/search?term=redis", nil)
if err != nil {
t.Fatal(err)
}
body, err := getImagesSearch(srv, nil, req, nil)
if err != nil {
t.Fatal(err)
}
results := []ApiSearch{}
err = json.Unmarshal(body, &results)
if err != nil {
t.Fatal(err)
}
if len(results) < 2 {
t.Errorf("Excepted at least 2 lines, %d found", len(results))
}
}
func TestGetImagesHistory(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
body, err := getImagesHistory(srv, nil, nil, map[string]string{"name": unitTestImageName})
if err != nil {
t.Fatal(err)
}
history := []ApiHistory{}
err = json.Unmarshal(body, &history)
if err != nil {
t.Fatal(err)
}
if len(history) != 1 {
t.Errorf("Excepted 1 line, %d found", len(history))
}
}
func TestGetImagesByName(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
body, err := getImagesByName(srv, nil, nil, map[string]string{"name": unitTestImageName})
if err != nil {
t.Fatal(err)
}
img := &Image{}
err = json.Unmarshal(body, img)
if err != nil {
t.Fatal(err)
}
if img.Comment != "Imported from http://get.docker.io/images/busybox" {
t.Errorf("Error inspecting image")
}
}
func TestGetContainersPs(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,
Cmd: []string{"echo", "test"},
})
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil)
if err != nil {
t.Fatal(err)
}
body, err := getContainersPs(srv, nil, req, nil)
if err != nil {
t.Fatal(err)
}
containers := []ApiContainers{}
err = json.Unmarshal(body, &containers)
if err != nil {
t.Fatal(err)
}
if len(containers) != 1 {
t.Fatalf("Excepted %d container, %d found", 1, len(containers))
}
if containers[0].Id != container.ShortId() {
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ShortId(), containers[0].Id)
}
}
func TestGetContainersExport(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
func TestGetContainersChanges(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
builder := NewBuilder(runtime)
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Run(); err != nil {
t.Fatal(err)
}
body, err := getContainersChanges(srv, nil, nil, map[string]string{"name": container.Id})
if err != nil {
t.Fatal(err)
}
changes := []Change{}
if err := json.Unmarshal(body, &changes); err != nil {
t.Fatal(err)
}
// Check the changelog
success := false
for _, elem := range changes {
if elem.Path == "/etc/passwd" && elem.Kind == 2 {
success = true
}
}
if !success {
t.Fatalf("/etc/passwd as been removed but is not present in the diff")
}
}
func TestGetContainersByName(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
authConfigOrig := &auth.AuthConfig{
Username: "utest",
Email: "utest@yopmail.com",
}
runtime.authConfig = authConfigOrig
body, err := getAuth(srv, nil, nil, nil)
if err != nil {
t.Fatal(err)
}
authConfig := &auth.AuthConfig{}
err = json.Unmarshal(body, authConfig)
if err != nil {
t.Fatal(err)
}
if authConfig.Username != authConfigOrig.Username || authConfig.Email != authConfigOrig.Email {
t.Errorf("The retrieve auth mismatch with the one set.")
}
}
func TestPostCommit(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
func TestPostBuild(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
func TestPostImagesCreate(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
func TestPostImagesInsert(t *testing.T) {
//FIXME: Implement this test (or remove this endpoint)
t.Log("Test not implemented")
}
func TestPostImagesPush(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
func TestPostImagesTag(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
func TestPostContainersCreate(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
r := httptest.NewRecorder()
configJson, err := json.Marshal(&Config{
Image: GetTestImage(runtime).Id,
Memory: 33554432,
Cmd: []string{"touch", "/test"},
})
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJson))
if err != nil {
t.Fatal(err)
}
body, err := postContainersCreate(srv, r, req, nil)
if err != nil {
t.Fatal(err)
}
if r.Code != http.StatusCreated {
t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
}
apiRun := &ApiRun{}
if err := json.Unmarshal(body, apiRun); err != nil {
t.Fatal(err)
}
container := srv.runtime.Get(apiRun.Id)
if container == nil {
t.Fatalf("Container not created")
}
if err := container.Run(); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil {
if os.IsNotExist(err) {
Debugf("Err: %s", err)
t.Fatalf("The test file has not been created")
}
t.Fatal(err)
}
}
func TestPostContainersKill(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,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Start(); 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()
body, err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id})
if err != nil {
t.Fatal(err)
}
if body != nil {
t.Fatalf("No body expected, received: %s\n", body)
}
if r.Code != http.StatusNoContent {
t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
}
if container.State.Running {
t.Fatalf("The container hasn't been killed")
}
}
func TestPostContainersRestart(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,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Start(); 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()
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/restart?t=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
body, err := postContainersRestart(srv, r, req, map[string]string{"name": container.Id})
if err != nil {
t.Fatal(err)
}
if body != nil {
t.Fatalf("No body expected, received: %s\n", body)
}
if r.Code != http.StatusNoContent {
t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
}
// Give some time to the process to restart
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
t.Fatalf("Container should be running")
}
if err := container.Kill(); err != nil {
t.Fatal(err)
}
}
func TestPostContainersStart(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,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
r := httptest.NewRecorder()
body, err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id})
if err != nil {
t.Fatal(err)
}
if body != nil {
t.Fatalf("No body expected, received: %s\n", body)
}
if r.Code != http.StatusNoContent {
t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
}
// Give some time to the process to start
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
t.Errorf("Container should be running")
}
if _, err = postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err == nil {
t.Fatalf("A running containter should be able to be started")
}
if err := container.Kill(); err != nil {
t.Fatal(err)
}
}
func TestPostContainersStop(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,
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Start(); 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()
// Note: as it is a POST request, it requires a body.
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/stop?t=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
body, err := postContainersStop(srv, r, req, map[string]string{"name": container.Id})
if err != nil {
t.Fatal(err)
}
if body != nil {
t.Fatalf("No body expected, received: %s\n", body)
}
if r.Code != http.StatusNoContent {
t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
}
if container.State.Running {
t.Fatalf("The container hasn't been stopped")
}
}
func TestPostContainersWait(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,
Cmd: []string{"/bin/sleep", "1"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
t.Fatal(err)
}
setTimeout(t, "Wait timed out", 3*time.Second, func() {
body, err := postContainersWait(srv, nil, nil, map[string]string{"name": container.Id})
if err != nil {
t.Fatal(err)
}
apiWait := &ApiWait{}
if err := json.Unmarshal(body, apiWait); err != nil {
t.Fatal(err)
}
if apiWait.StatusCode != 0 {
t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.StatusCode)
}
})
if container.State.Running {
t.Fatalf("The container should be stopped after wait")
}
}
func TestPostContainersAttach(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,
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.
r := &hijackTester{
ResponseRecorder: httptest.NewRecorder(),
in: stdin,
out: stdoutPipe,
}
req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
if err != nil {
t.Fatal(err)
}
body, err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id})
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, "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()
}
// FIXME: Test deleting runnign container
// 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)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).Id,
Cmd: []string{"touch", "/test"},
})
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Run(); err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
req, err := http.NewRequest("DELETE", "/containers/"+container.Id, nil)
if err != nil {
t.Fatal(err)
}
body, err := deleteContainers(srv, r, req, map[string]string{"name": container.Id})
if err != nil {
t.Fatal(err)
}
if body != nil {
t.Fatalf("No body expected, received: %s\n", body)
}
if r.Code != http.StatusNoContent {
t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
}
if c := runtime.Get(container.Id); c != nil {
t.Fatalf("The container as not been deleted")
}
if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil {
t.Fatalf("The test file has not been deleted")
}
}
func TestDeleteImages(t *testing.T) {
//FIXME: Implement this test
t.Log("Test not implemented")
}
// Mocked types for tests
type NopConn struct {
io.ReadCloser
io.Writer
}
func (c *NopConn) LocalAddr() net.Addr { return nil }
func (c *NopConn) RemoteAddr() net.Addr { return nil }
func (c *NopConn) SetDeadline(t time.Time) error { return nil }
func (c *NopConn) SetReadDeadline(t time.Time) error { return nil }
func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil }
type hijackTester struct {
*httptest.ResponseRecorder
in io.ReadCloser
out io.Writer
}
func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) {
bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out))
conn := &NopConn{
ReadCloser: t.in,
Writer: t.out,
}
return conn, bufrw, nil
}

View file

@ -266,7 +266,7 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
if image == nil {
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, nil, builder.runtime.capabilities)
config, _, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, builder.runtime.capabilities)
if err != nil {
return nil, err
}
@ -413,7 +413,7 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
}
defer file.Body.Close()
config, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, nil, builder.runtime.capabilities)
config, _, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, builder.runtime.capabilities)
if err != nil {
return nil, err
}

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,8 @@ package docker
import (
"bufio"
"fmt"
"github.com/dotcloud/docker/rcli"
"io"
"io/ioutil"
_ "io/ioutil"
"strings"
"testing"
"time"
@ -59,6 +58,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
return nil
}
/*TODO
func cmdWait(srv *Server, container *Container) error {
stdout, stdoutPipe := io.Pipe()
@ -467,3 +467,4 @@ func TestAttachDisconnect(t *testing.T) {
cStdin.Close()
container.Wait()
}
*/

View file

@ -2,8 +2,8 @@ package docker
import (
"encoding/json"
"flag"
"fmt"
"github.com/dotcloud/docker/rcli"
"github.com/kr/pty"
"io"
"io/ioutil"
@ -71,8 +71,8 @@ type Config struct {
VolumesFrom string
}
func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) {
cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard)
}
@ -86,8 +86,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
if *flMemory > 0 && !capabilities.MemoryLimit {
fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
*flMemory = 0
}
@ -106,10 +106,10 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
if err := cmd.Parse(args); err != nil {
return nil, err
return nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, fmt.Errorf("Conflicting options: -a and -d")
return nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
@ -148,8 +148,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
VolumesFrom: *flVolumesFrom,
}
if *flMemory > 0 && !capabilities.SwapLimit {
fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config.MemorySwap = -1
}
@ -157,7 +157,7 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, nil
return config, cmd, nil
}
type NetworkSettings struct {

View file

@ -399,6 +399,11 @@ func TestStart(t *testing.T) {
}
defer runtime.Destroy(container)
cStdin, err := container.StdinPipe()
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
t.Fatal(err)
}
@ -414,7 +419,6 @@ func TestStart(t *testing.T) {
}
// Try to avoid the timeoout in destroy. Best effort, don't check error
cStdin, _ := container.StdinPipe()
cStdin.Close()
container.WaitTimeout(2 * time.Second)
}

View file

@ -4,9 +4,6 @@ import (
"flag"
"fmt"
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/rcli"
"github.com/dotcloud/docker/term"
"io"
"io/ioutil"
"log"
"os"
@ -48,10 +45,12 @@ func main() {
}
if err := daemon(*pidfile, *flAutoRestart); err != nil {
log.Fatal(err)
os.Exit(-1)
}
} else {
if err := runCommand(flag.Args()); err != nil {
if err := docker.ParseCommands(flag.Args()...); err != nil {
log.Fatal(err)
os.Exit(-1)
}
}
}
@ -98,50 +97,10 @@ func daemon(pidfile string, autoRestart bool) error {
os.Exit(0)
}()
service, err := docker.NewServer(autoRestart)
server, err := docker.NewServer(autoRestart)
if err != nil {
return err
}
return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service)
}
func runCommand(args []string) error {
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
// CloseWrite(), which we need to cleanly signal that stdin is closed without
// closing the connection.
// See http://code.google.com/p/go/issues/detail?id=3345
if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
options := conn.GetOptions()
if options.RawTerminal &&
term.IsTerminal(int(os.Stdin.Fd())) &&
os.Getenv("NORAW") == "" {
if oldState, err := rcli.SetRawTerminal(); err != nil {
return err
} else {
defer rcli.RestoreTerminal(oldState)
}
}
receiveStdout := docker.Go(func() error {
_, err := io.Copy(os.Stdout, conn)
return err
})
sendStdin := docker.Go(func() error {
_, err := io.Copy(conn, os.Stdin)
if err := conn.CloseWrite(); err != nil {
log.Printf("Couldn't send EOF: " + err.Error())
}
return err
})
if err := <-receiveStdout; err != nil {
return err
}
if !term.IsTerminal(int(os.Stdin.Fd())) {
if err := <-sendStdin; err != nil {
return err
}
}
} else {
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
}
return nil
return docker.ListenAndServe("0.0.0.0:4243", server, true)
}

0
docs/sources/.nojekyll Normal file
View file

1
docs/sources/CNAME Normal file
View file

@ -0,0 +1 @@
docker.io

View file

@ -13,4 +13,4 @@ Contents:
basics
workingwithrepository
cli
cli

View file

@ -0,0 +1,2 @@
www:
type: static

View file

@ -0,0 +1,210 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Docker - the Linux container runtime</title>
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
<meta name="viewport" content="width=device-width">
<!-- twitter bootstrap -->
<link rel="stylesheet" href="../_static/css/bootstrap.min.css">
<link rel="stylesheet" href="../_static/css/bootstrap-responsive.min.css">
<!-- main style file -->
<link rel="stylesheet" href="../_static/css/main.css">
<!-- vendor scripts -->
<script src="../_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
<script src="../_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-dotcloud">
<div class="container" style="text-align: center;">
<div style="float: right" class="pull-right">
<ul class="nav">
<li><a href="../">Introduction</a></li>
<li class="active"><a href="./">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
</ul>
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
</div>
<div style="margin-left: -12px; float: left;">
<a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../_static/img/docker-letters-logo.gif"></a>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="span12 titlebar"><h1 class="pageheader">GETTING STARTED</h1>
</div>
</div>
</div>
<div class="container">
<div class="alert alert-info">
<strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
</div>
<div class="row">
<div class="span6">
<section class="contentblock">
<h2>
<a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
</a>Installing on Ubuntu</h2>
<p><strong>Requirements</strong></p>
<ul>
<li>Ubuntu 12.04 (LTS) (64-bit)</li>
<li> or Ubuntu 12.10 (quantal) (64-bit)</li>
</ul>
<ol>
<li>
<p><strong>Install dependencies</strong></p>
The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
<pre>sudo apt-get install linux-image-extra-`uname -r`</pre>
</li>
<li>
<p><strong>Install Docker</strong></p>
<p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
<p>You may see some warnings that the GPG keys cannot be verified.</p>
<div class="highlight">
<pre>sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"</pre>
<pre>sudo apt-get update</pre>
<pre>sudo apt-get install lxc-docker</pre>
</div>
</li>
<li>
<p><strong>Run!</strong></p>
<div class="highlight">
<pre>docker run -i -t ubuntu /bin/bash</pre>
</div>
</li>
Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.
</ol>
</section>
<section class="contentblock">
<h2>Contributing to Docker</h2>
<p>Want to hack on Docker? Awesome! We have some <a href="http://docs.docker.io/en/latest/contributing/contributing/">instructions to get you started</a>. They are probably not perfect, please let us know if anything feels wrong or incomplete.</p>
</section>
</div>
<div class="span6">
<section class="contentblock">
<h2>Quick install on other operating systems</h2>
<p><strong>For other operating systems we recommend and provide a streamlined install with virtualbox,
vagrant and an Ubuntu virtual machine.</strong></p>
<ul>
<li><a href="http://docs.docker.io/en/latest/installation/vagrant/">Mac OS X and other linuxes</a></li>
<li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
</ul>
</section>
<section class="contentblock">
<h2>More resources</h2>
<ul>
<li><a href="irc://chat.freenode.net#docker">IRC: docker on freenode</a></li>
<li><a href="http://www.github.com/dotcloud/docker">Github</a></li>
<li><a href="http://stackoverflow.com/tags/docker/">Ask questions on Stackoverflow</a></li>
<li><a href="http://twitter.com/getdocker/">Join the conversation on Twitter</a></li>
</ul>
</section>
<section class="contentblock">
<div id="wufoo-z7x3p3">
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
</div>
<script type="text/javascript">var z7x3p3;(function(d, t) {
var s = d.createElement(t), options = {
'userName':'dotclouddocker',
'formHash':'z7x3p3',
'autoResize':true,
'height':'577',
'async':true,
'header':'show'};
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
s.onload = s.onreadystatechange = function() {
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
})(document, 'script');</script>
</section>
</div>
</div>
</div>
<div class="container">
<footer id="footer" class="footer">
<div class="row">
<div class="span12 social">
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
</div>
</div>
<div class="row">
<div class="emptyspace" style="height: 40px">
</div>
</div>
</footer>
</div>
<!-- bootstrap javascipts -->
<script src="../_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
<!-- Google analytics -->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-6096819-11']);
_gaq.push(['_setDomainName', 'docker.io']);
_gaq.push(['_setAllowLinker', true]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>

314
docs/sources/index.html Normal file
View file

@ -0,0 +1,314 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
<title>Docker - the Linux container engine</title>
<meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
<meta name="viewport" content="width=device-width">
<!-- twitter bootstrap -->
<link rel="stylesheet" href="_static/css/bootstrap.min.css">
<link rel="stylesheet" href="_static/css/bootstrap-responsive.min.css">
<!-- main style file -->
<link rel="stylesheet" href="_static/css/main.css">
<!-- vendor scripts -->
<script src="_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
<script src="_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
<style>
.indexlabel {
float: left;
width: 150px;
display: block;
padding: 10px 20px 10px;
font-size: 20px;
font-weight: 200;
background-color: #a30000;
color: white;
height: 22px;
}
.searchbutton {
font-size: 20px;
height: 40px;
}
.debug {
border: 1px red dotted;
}
</style>
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-dotcloud">
<div class="container" style="text-align: center;">
<div class="pull-right" >
<ul class="nav">
<li class="active"><a href="./">Introduction</a></li>
<li ><a href="gettingstarted/">Getting started</a></li>
<li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
</ul>
<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
</div>
</div>
</div>
</div>
<div class="container" style="margin-top: 30px;">
<div class="row">
<div class="span12">
<section class="contentblock header">
<div class="span5" style="margin-bottom: 15px;">
<div style="text-align: center;" >
<img src="_static/img/docker_letters_500px.png">
<h2>The Linux container engine</h2>
</div>
<div style="display: block; text-align: center; margin-top: 20px;">
<h5>
Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
</h5>
</div>
<div style="display: block; text-align: center; margin-top: 30px;">
<a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
</div>
</div>
<div class="span6" >
<div class="js-video" >
<iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
</div>
</div>
<br style="clear: both"/>
</section>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="span6">
<section class="contentblock">
<h4>Heterogeneous payloads</h4>
<p>Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.</p>
<h4>Any server</h4>
<p>Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.</p>
<h4>Isolation</h4>
<p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
<h4>Repeatability</h4>
<p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
</section>
</div>
<div class="span6">
<section class="contentblock">
<h1>New! Docker Index</h1>
On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
<br><br>
<a href="https://index.docker.io" target="_blank">
<div class="indexlabel">
DOCKER index
</div>
</a>
&nbsp;
<input type="button" class="searchbutton" type="submit" value="Search images"
onClick="window.open('https://index.docker.io')" />
</section>
<section class="contentblock">
<div id="wufoo-z7x3p3">
Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
</div>
<script type="text/javascript">var z7x3p3;(function(d, t) {
var s = d.createElement(t), options = {
'userName':'dotclouddocker',
'formHash':'z7x3p3',
'autoResize':true,
'height':'577',
'async':true,
'header':'show'};
s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
s.onload = s.onreadystatechange = function() {
var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
})(document, 'script');</script>
</section>
</div>
</div>
</div>
<style>
.twitterblock {
min-height: 75px;
}
.twitterblock img {
float: left;
margin-right: 10px;
}
</style>
<div class="container">
<div class="row">
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://twimg0-a.akamaihd.net/profile_images/2491994496/rbevyyq6ykp6bnoby2je_bigger.jpeg">
<em>John Willis @botchagalupe:</em> IMHO docker is to paas what chef was to Iaas 4 years ago
</section>
</div>
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://twimg0-a.akamaihd.net/profile_images/3348427561/9d7f08f1e103a16c8debd169301b9944_bigger.jpeg">
<em>John Feminella @superninjarobot:</em> So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam!
</section>
</div>
</div>
<div class="row">
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/3408403010/4496ccdd14e9b7285eca04c31a740207_bigger.jpeg">
<em>David Romulan @destructuring:</em> I haven't had this much fun since AWS
</section>
</div>
<div class="span6">
<section class="contentblock twitterblock">
<img src="https://si0.twimg.com/profile_images/780893320/My_Avatar_bigger.jpg">
<em>Ricardo Gladwell @rgladwell:</em> wow @getdocker is either amazing or totally stupid
</section>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="span6">
<section class="contentblock">
<h2>Notable features</h2>
<ul>
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
<li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li>
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
<li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
<li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
</ul>
<h2>Under the hood</h2>
<p>Under the hood, Docker is built on the following components:</p>
<ul>
<li>The <a href="http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c">cgroup</a> and <a href="http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part">namespacing</a> capabilities of the Linux kernel;</li>
<li><a href="http://aufs.sourceforge.net/aufs.html">AUFS</a>, a powerful union filesystem with copy-on-write capabilities;</li>
<li>The <a href="http://golang.org">Go</a> programming language;</li>
<li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
</ul>
<h2>Who started it</h2>
<p>
Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
<p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
of applications and databases.
</p>
</section>
</div>
<div class="span6">
<section class="contentblock">
<h3 id="twitter">Twitter</h3>
<a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
</section>
</div>
</div>
</div> <!-- end container -->
<div class="container">
<footer id="footer" class="footer">
<div class="row">
<div class="span12">
Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
</div>
</div>
<div class="row">
<div class="emptyspace" style="height: 40px">
</div>
</div>
</footer>
</div>
<!-- bootstrap javascipts -->
<script src="_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
<!-- Google analytics -->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-6096819-11']);
_gaq.push(['_setDomainName', 'docker.io']);
_gaq.push(['_setAllowLinker', true]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>

View file

@ -18,6 +18,7 @@ This documentation has the following resources:
registry/index
index/index
builder/index
remote-api/index
faq

6
docs/sources/nginx.conf Normal file
View file

@ -0,0 +1,6 @@
# rule to redirect original links created when hosted on github pages
rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent;
# rewrite the stuff which was on the current page
rewrite ^/gettingstarted.html$ /gettingstarted/ permanent;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
:title: docker Remote API documentation
:description: Documentation for docker Remote API
:keywords: docker, rest, api, http
Remote API
==========
Contents:
.. toctree::
:maxdepth: 2
api

View file

@ -1,169 +0,0 @@
package rcli
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net"
)
// Note: the globals are here to avoid import cycle
// FIXME: Handle debug levels mode?
var DEBUG_FLAG bool = false
var CLIENT_SOCKET io.Writer = nil
type DockerTCPConn struct {
conn *net.TCPConn
options *DockerConnOptions
optionsBuf *[]byte
handshaked bool
client bool
}
func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
return &DockerTCPConn{
conn: conn,
options: &DockerConnOptions{},
client: client,
}
}
func (c *DockerTCPConn) SetOptionRawTerminal() {
c.options.RawTerminal = true
}
func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
if c.client && !c.handshaked {
// Attempt to parse options encoded as a JSON dict and store
// the reminder of what we read from the socket in a buffer.
//
// bufio (and its ReadBytes method) would have been nice here,
// but if json.Unmarshal() fails (which will happen if we speak
// to a version of docker that doesn't send any option), then
// we can't put the data back in it for the next Read().
c.handshaked = true
buf := make([]byte, 4096)
if n, _ := c.conn.Read(buf); n > 0 {
buf = buf[:n]
if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
if err := json.Unmarshal(buf[:nl], c.options); err == nil {
buf = buf[nl+1:]
}
}
c.optionsBuf = &buf
}
}
return c.options
}
func (c *DockerTCPConn) Read(b []byte) (int, error) {
if c.optionsBuf != nil {
// Consume what we buffered in GetOptions() first:
optionsBuf := *c.optionsBuf
optionsBuflen := len(optionsBuf)
copied := copy(b, optionsBuf)
if copied < optionsBuflen {
optionsBuf = optionsBuf[copied:]
c.optionsBuf = &optionsBuf
return copied, nil
}
c.optionsBuf = nil
return copied, nil
}
return c.conn.Read(b)
}
func (c *DockerTCPConn) Write(b []byte) (int, error) {
optionsLen := 0
if !c.client && !c.handshaked {
c.handshaked = true
options, _ := json.Marshal(c.options)
options = append(options, '\n')
if optionsLen, err := c.conn.Write(options); err != nil {
return optionsLen, err
}
}
n, err := c.conn.Write(b)
return n + optionsLen, err
}
func (c *DockerTCPConn) Flush() error {
_, err := c.Write([]byte{})
return err
}
func (c *DockerTCPConn) Close() error { return c.conn.Close() }
func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
// Connect to a remote endpoint using protocol `proto` and address `addr`,
// issue a single call, and return the result.
// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
func Call(proto, addr string, args ...string) (DockerConn, error) {
cmd, err := json.Marshal(args)
if err != nil {
return nil, err
}
conn, err := dialDocker(proto, addr)
if err != nil {
return nil, err
}
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
return nil, err
}
return conn, nil
}
// Listen on `addr`, using protocol `proto`, for incoming rcli calls,
// and pass them to `service`.
func ListenAndServe(proto, addr string, service Service) error {
listener, err := net.Listen(proto, addr)
if err != nil {
return err
}
log.Printf("Listening for RCLI/%s on %s\n", proto, addr)
defer listener.Close()
for {
if conn, err := listener.Accept(); err != nil {
return err
} else {
conn, err := newDockerServerConn(conn)
if err != nil {
return err
}
go func(conn DockerConn) {
defer conn.Close()
if DEBUG_FLAG {
CLIENT_SOCKET = conn
}
if err := Serve(conn, service); err != nil {
log.Println("Error:", err.Error())
fmt.Fprintln(conn, "Error:", err.Error())
}
}(conn)
}
}
return nil
}
// Parse an rcli call on a new connection, and pass it to `service` if it
// is valid.
func Serve(conn DockerConn, service Service) error {
r := bufio.NewReader(conn)
var args []string
if line, err := r.ReadString('\n'); err != nil {
return err
} else if err := json.Unmarshal([]byte(line), &args); err != nil {
return err
} else {
return call(service, ioutil.NopCloser(r), conn, args...)
}
return nil
}

View file

@ -1,181 +0,0 @@
package rcli
// rcli (Remote Command-Line Interface) is a simple protocol for...
// serving command-line interfaces remotely.
//
// rcli can be used over any transport capable of a) sending binary streams in
// both directions, and b) capable of half-closing a connection. TCP and Unix sockets
// are the usual suspects.
import (
"flag"
"fmt"
"github.com/dotcloud/docker/term"
"io"
"log"
"net"
"os"
"reflect"
"strings"
)
type DockerConnOptions struct {
RawTerminal bool
}
type DockerConn interface {
io.ReadWriteCloser
CloseWrite() error
CloseRead() error
GetOptions() *DockerConnOptions
SetOptionRawTerminal()
Flush() error
}
type DockerLocalConn struct {
writer io.WriteCloser
savedState *term.State
}
func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
return &DockerLocalConn{
writer: w,
}
}
func (c *DockerLocalConn) Read(b []byte) (int, error) {
return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
}
func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
func (c *DockerLocalConn) Close() error {
if c.savedState != nil {
RestoreTerminal(c.savedState)
c.savedState = nil
}
return c.writer.Close()
}
func (c *DockerLocalConn) Flush() error { return nil }
func (c *DockerLocalConn) CloseWrite() error { return nil }
func (c *DockerLocalConn) CloseRead() error { return nil }
func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
func (c *DockerLocalConn) SetOptionRawTerminal() {
if state, err := SetRawTerminal(); err != nil {
if os.Getenv("DEBUG") != "" {
log.Printf("Can't set the terminal in raw mode: %s", err)
}
} else {
c.savedState = state
}
}
var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
func dialDocker(proto string, addr string) (DockerConn, error) {
conn, err := net.Dial(proto, addr)
if err != nil {
return nil, err
}
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, true), nil
}
return nil, UnknownDockerProto
}
func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, client), nil
}
return nil, UnknownDockerProto
}
func newDockerServerConn(conn net.Conn) (DockerConn, error) {
return newDockerFromConn(conn, false)
}
type Service interface {
Name() string
Help() string
}
type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
// FIXME: For reverse compatibility
func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
return LocalCall(service, stdin, stdout, args...)
}
func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
if len(args) == 0 {
args = []string{"help"}
}
flags := flag.NewFlagSet("main", flag.ContinueOnError)
flags.SetOutput(stdout)
flags.Usage = func() { stdout.Write([]byte(service.Help())) }
if err := flags.Parse(args); err != nil {
return err
}
cmd := flags.Arg(0)
log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
if cmd == "" {
cmd = "help"
}
method := getMethod(service, cmd)
if method != nil {
return method(stdin, stdout, flags.Args()[1:]...)
}
return fmt.Errorf("No such command: %s", cmd)
}
func getMethod(service Service, name string) Cmd {
if name == "help" {
return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
if len(args) == 0 {
stdout.Write([]byte(service.Help()))
} else {
if method := getMethod(service, args[0]); method == nil {
return fmt.Errorf("No such command: %s", args[0])
} else {
method(stdin, stdout, "--help")
}
}
return nil
}
}
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
method, exists := reflect.TypeOf(service).MethodByName(methodName)
if !exists {
return nil
}
return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
ret := method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(service),
reflect.ValueOf(stdin),
reflect.ValueOf(stdout),
reflect.ValueOf(args),
})[0].Interface()
if ret == nil {
return nil
}
return ret.(error)
}
}
func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet {
flags := flag.NewFlagSet(name, flag.ContinueOnError)
flags.SetOutput(output)
flags.Usage = func() {
fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
flags.PrintDefaults()
}
return flags
}

View file

@ -1,27 +0,0 @@
package rcli
import (
"github.com/dotcloud/docker/term"
"os"
"os/signal"
)
//FIXME: move these function to utils.go (in rcli to avoid import loop)
func SetRawTerminal() (*term.State, error) {
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return nil, err
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
term.Restore(int(os.Stdin.Fd()), oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *term.State) {
term.Restore(int(os.Stdin.Fd()), state)
}

View file

@ -187,7 +187,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
}
if err := container.Stop(10); err != nil {
if err := container.Stop(3); err != nil {
return err
}
if mounted, err := container.Mounted(); err != nil {

View file

@ -2,7 +2,6 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/rcli"
"io"
"io/ioutil"
"net"
@ -67,12 +66,13 @@ func init() {
if err != nil {
panic(err)
}
// Create the "Server"
srv := &Server{
runtime: runtime,
}
// Retrieve the Image
if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil {
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout); err != nil {
panic(err)
}
}

591
server.go Normal file
View file

@ -0,0 +1,591 @@
package docker
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"runtime"
"strings"
)
func (srv *Server) DockerVersion() ApiVersion {
return ApiVersion{VERSION, GIT_COMMIT, srv.runtime.capabilities.MemoryLimit, srv.runtime.capabilities.SwapLimit}
}
func (srv *Server) ContainerKill(name string) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Kill(); err != nil {
return fmt.Errorf("Error restarting container %s: %s", name, err.Error())
}
} else {
return fmt.Errorf("No such container: %s", name)
}
return nil
}
func (srv *Server) ContainerExport(name string, out io.Writer) error {
if container := srv.runtime.Get(name); container != nil {
data, err := container.Export()
if err != nil {
return err
}
// Stream the entire contents of the container (basically a volatile snapshot)
if _, err := io.Copy(out, data); err != nil {
return err
}
return nil
}
return fmt.Errorf("No such container: %s", name)
}
func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
results, err := srv.runtime.graph.SearchRepositories(nil, term)
if err != nil {
return nil, err
}
var outs []ApiSearch
for _, repo := range results.Results {
var out ApiSearch
out.Description = repo["description"]
if len(out.Description) > 45 {
out.Description = Trunc(out.Description, 42) + "..."
}
out.Name = repo["name"]
outs = append(outs, out)
}
return outs, nil
}
func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error {
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return err
}
file, err := Download(url, out)
if err != nil {
return err
}
defer file.Body.Close()
config, _, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, srv.runtime.capabilities)
if err != nil {
return err
}
b := NewBuilder(srv.runtime)
c, err := b.Create(config)
if err != nil {
return err
}
if err := c.Inject(ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil {
return err
}
// FIXME: Handle custom repo, tag comment, author
img, err = b.Commit(c, "", "", img.Comment, img.Author, nil)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", img.Id)
return nil
}
func (srv *Server) ImagesViz(out io.Writer) error {
images, _ := srv.runtime.graph.All()
if images == nil {
return nil
}
out.Write([]byte("digraph docker {\n"))
var (
parentImage *Image
err error
)
for _, image := range images {
parentImage, err = image.GetParent()
if err != nil {
return fmt.Errorf("Error while getting parent image: %v", err)
}
if parentImage != nil {
out.Write([]byte(" \"" + parentImage.ShortId() + "\" -> \"" + image.ShortId() + "\"\n"))
} else {
out.Write([]byte(" base -> \"" + image.ShortId() + "\" [style=invis]\n"))
}
}
reporefs := make(map[string][]string)
for name, repository := range srv.runtime.repositories.Repositories {
for tag, id := range repository {
reporefs[TruncateId(id)] = append(reporefs[TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
}
}
for id, repos := range reporefs {
out.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n"))
}
out.Write([]byte(" base [style=invisible]\n}\n"))
return nil
}
func (srv *Server) Images(all, only_ids bool, filter string) ([]ApiImages, error) {
var allImages map[string]*Image
var err error
if all {
allImages, err = srv.runtime.graph.Map()
} else {
allImages, err = srv.runtime.graph.Heads()
}
if err != nil {
return nil, err
}
var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null'
for name, repository := range srv.runtime.repositories.Repositories {
if filter != "" && name != filter {
continue
}
for tag, id := range repository {
var out ApiImages
image, err := srv.runtime.graph.Get(id)
if err != nil {
log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
continue
}
delete(allImages, id)
if !only_ids {
out.Repository = name
out.Tag = tag
out.Id = TruncateId(id)
out.Created = image.Created.Unix()
} else {
out.Id = image.ShortId()
}
outs = append(outs, out)
}
}
// Display images which aren't part of a
if filter == "" {
for id, image := range allImages {
var out ApiImages
if !only_ids {
out.Repository = "<none>"
out.Tag = "<none>"
out.Id = TruncateId(id)
out.Created = image.Created.Unix()
} else {
out.Id = image.ShortId()
}
outs = append(outs, out)
}
}
return outs, nil
}
func (srv *Server) DockerInfo() ApiInfo {
images, _ := srv.runtime.graph.All()
var imgcount int
if images == nil {
imgcount = 0
} else {
imgcount = len(images)
}
var out ApiInfo
out.Containers = len(srv.runtime.List())
out.Version = VERSION
out.Images = imgcount
out.GoVersion = runtime.Version()
if os.Getenv("DEBUG") != "" {
out.Debug = true
out.NFd = getTotalUsedFds()
out.NGoroutines = runtime.NumGoroutine()
}
return out
}
func (srv *Server) ImageHistory(name string) ([]ApiHistory, error) {
image, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return nil, err
}
var outs []ApiHistory = []ApiHistory{} //produce [] when empty instead of 'null'
err = image.WalkHistory(func(img *Image) error {
var out ApiHistory
out.Id = srv.runtime.repositories.ImageName(img.ShortId())
out.Created = img.Created.Unix()
out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
outs = append(outs, out)
return nil
})
return outs, nil
}
func (srv *Server) ContainerChanges(name string) ([]Change, error) {
if container := srv.runtime.Get(name); container != nil {
return container.Changes()
}
return nil, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int, since, before string) []ApiContainers {
var foundBefore bool
var displayed int
retContainers := []ApiContainers{}
for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
continue
}
if before != "" {
if container.ShortId() == before {
foundBefore = true
continue
}
if !foundBefore {
continue
}
}
if displayed == n {
break
}
if container.ShortId() == since {
break
}
displayed++
c := ApiContainers{
Id: container.ShortId(),
}
if !only_ids {
command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
if trunc_cmd {
command = Trunc(command, 20)
}
c.Image = srv.runtime.repositories.ImageName(container.Image)
c.Command = command
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingHuman()
}
retContainers = append(retContainers, c)
}
return retContainers
}
func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) {
container := srv.runtime.Get(name)
if container == nil {
return "", fmt.Errorf("No such container: %s", name)
}
img, err := NewBuilder(srv.runtime).Commit(container, repo, tag, comment, author, config)
if err != nil {
return "", err
}
return img.ShortId(), err
}
func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
if err := srv.runtime.repositories.Set(repo, tag, name, force); err != nil {
return err
}
return nil
}
func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error {
if registry != "" {
if err := srv.runtime.graph.PullImage(out, name, registry, nil); err != nil {
return err
}
return nil
}
if err := srv.runtime.graph.PullRepository(out, name, tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil {
return err
}
return nil
}
func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
img, err := srv.runtime.graph.Get(name)
if err != nil {
Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
// If it fails, try to get the repository
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
if err := srv.runtime.graph.PushRepository(out, name, localRepo, srv.runtime.authConfig); err != nil {
return err
}
return nil
}
return err
}
err = srv.runtime.graph.PushImage(out, img, registry, nil)
if err != nil {
return err
}
return nil
}
func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
var archive io.Reader
var resp *http.Response
if src == "-" {
archive = in
} else {
u, err := url.Parse(src)
if err != nil {
fmt.Fprintf(out, "Error: %s\n", err)
}
if u.Scheme == "" {
u.Scheme = "http"
u.Host = src
u.Path = ""
}
fmt.Fprintln(out, "Downloading from", u)
// Download with curl (pretty progress bar)
// If curl is not available, fallback to http.Get()
resp, err = Download(u.String(), out)
if err != nil {
return err
}
archive = ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)")
}
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
if err != nil {
return err
}
// Optionally register the image at REPO/TAG
if repo != "" {
if err := srv.runtime.repositories.Set(repo, tag, img.Id, true); err != nil {
return err
}
}
fmt.Fprintln(out, img.ShortId())
return nil
}
func (srv *Server) ContainerCreate(config *Config) (string, error) {
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
config.Memory = 0
}
if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
config.MemorySwap = -1
}
b := NewBuilder(srv.runtime)
container, err := b.Create(config)
if err != nil {
if srv.runtime.graph.IsNotExist(err) {
return "", fmt.Errorf("No such image: %s", config.Image)
}
return "", err
}
return container.ShortId(), nil
}
func (srv *Server) ImageCreateFromFile(dockerfile io.Reader, out io.Writer) error {
img, err := NewBuilder(srv.runtime).Build(dockerfile, out)
if err != nil {
return err
}
fmt.Fprintf(out, "%s\n", img.ShortId())
return nil
}
func (srv *Server) ContainerRestart(name string, t int) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Restart(t); err != nil {
return fmt.Errorf("Error restarting container %s: %s", name, err.Error())
}
} else {
return fmt.Errorf("No such container: %s", name)
}
return nil
}
func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
if container := srv.runtime.Get(name); container != nil {
volumes := make(map[string]struct{})
// Store all the deleted containers volumes
for _, volumeId := range container.Volumes {
volumes[volumeId] = struct{}{}
}
if err := srv.runtime.Destroy(container); err != nil {
return fmt.Errorf("Error destroying container %s: %s", name, err.Error())
}
if removeVolume {
// Retrieve all volumes from all remaining containers
usedVolumes := make(map[string]*Container)
for _, container := range srv.runtime.List() {
for _, containerVolumeId := range container.Volumes {
usedVolumes[containerVolumeId] = container
}
}
for volumeId := range volumes {
// If the requested volu
if c, exists := usedVolumes[volumeId]; exists {
log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
continue
}
if err := srv.runtime.volumes.Delete(volumeId); err != nil {
return err
}
}
}
} else {
return fmt.Errorf("No such container: %s", name)
}
return nil
}
func (srv *Server) ImageDelete(name string) error {
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return fmt.Errorf("No such image: %s", name)
} else {
if err := srv.runtime.graph.Delete(img.Id); err != nil {
return fmt.Errorf("Error deleting image %s: %s", name, err.Error())
}
}
return nil
}
func (srv *Server) ContainerStart(name string) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Start(); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err.Error())
}
} else {
return fmt.Errorf("No such container: %s", name)
}
return nil
}
func (srv *Server) ContainerStop(name string, t int) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Stop(t); err != nil {
return fmt.Errorf("Error stopping container %s: %s", name, err.Error())
}
} else {
return fmt.Errorf("No such container: %s", name)
}
return nil
}
func (srv *Server) ContainerWait(name string) (int, error) {
if container := srv.runtime.Get(name); container != nil {
return container.Wait(), nil
}
return 0, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error {
container := srv.runtime.Get(name)
if container == nil {
return fmt.Errorf("No such container: %s", name)
}
//logs
if logs {
if stdout {
cLog, err := container.ReadLog("stdout")
if err != nil {
Debugf(err.Error())
} else if _, err := io.Copy(out, cLog); err != nil {
Debugf(err.Error())
}
}
if stderr {
cLog, err := container.ReadLog("stderr")
if err != nil {
Debugf(err.Error())
} else if _, err := io.Copy(out, cLog); err != nil {
Debugf(err.Error())
}
}
}
//stream
if stream {
if container.State.Ghost {
return fmt.Errorf("Impossible to attach to a ghost container")
}
var (
cStdin io.ReadCloser
cStdout, cStderr io.Writer
cStdinCloser io.Closer
)
if stdin {
r, w := io.Pipe()
go func() {
defer w.Close()
defer Debugf("Closing buffered stdin pipe")
io.Copy(w, in)
}()
cStdin = r
cStdinCloser = in
}
if stdout {
cStdout = out
}
if stderr {
cStderr = out
}
<-container.Attach(cStdin, cStdinCloser, cStdout, cStderr)
// If we are in stdinonce mode, wait for the process to end
// otherwise, simply return
if container.Config.StdinOnce && !container.Config.Tty {
container.Wait()
}
}
return nil
}
func (srv *Server) ContainerInspect(name string) (*Container, error) {
if container := srv.runtime.Get(name); container != nil {
return container, nil
}
return nil, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) ImageInspect(name string) (*Image, error) {
if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil {
return image, nil
}
return nil, fmt.Errorf("No such image: %s", name)
}
func NewServer(autoRestart bool) (*Server, error) {
if runtime.GOARCH != "amd64" {
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
}
runtime, err := NewRuntime(autoRestart)
if err != nil {
return nil, err
}
srv := &Server{
runtime: runtime,
}
return srv, nil
}
type Server struct {
runtime *Runtime
}

96
server_test.go Normal file
View file

@ -0,0 +1,96 @@
package docker
import (
"testing"
)
func TestCreateRm(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
id, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
if len(runtime.List()) != 1 {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
if err = srv.ContainerDestroy(id, true); err != nil {
t.Fatal(err)
}
if len(runtime.List()) != 0 {
t.Errorf("Expected 0 container, %v found", len(runtime.List()))
}
}
func TestCreateStartRestartStopStartKillRm(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}
id, err := srv.ContainerCreate(config)
if err != nil {
t.Fatal(err)
}
if len(runtime.List()) != 1 {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
err = srv.ContainerStart(id)
if err != nil {
t.Fatal(err)
}
err = srv.ContainerRestart(id, 1)
if err != nil {
t.Fatal(err)
}
err = srv.ContainerStop(id, 1)
if err != nil {
t.Fatal(err)
}
err = srv.ContainerStart(id)
if err != nil {
t.Fatal(err)
}
err = srv.ContainerKill(id)
if err != nil {
t.Fatal(err)
}
if err = srv.ContainerDestroy(id, true); err != nil {
t.Fatal(err)
}
if len(runtime.List()) != 0 {
t.Errorf("Expected 0 container, %v found", len(runtime.List()))
}
}

View file

@ -6,13 +6,14 @@ import (
"encoding/hex"
"errors"
"fmt"
"github.com/dotcloud/docker/rcli"
"github.com/dotcloud/docker/term"
"index/suffixarray"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
@ -58,9 +59,6 @@ func Debugf(format string, a ...interface{}) {
}
fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
if rcli.CLIENT_SOCKET != nil {
fmt.Fprintf(rcli.CLIENT_SOCKET, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
}
}
}
@ -404,6 +402,25 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
return written, err
}
func SetRawTerminal() (*term.State, error) {
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return nil, err
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
term.Restore(int(os.Stdin.Fd()), oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *term.State) {
term.Restore(int(os.Stdin.Fd()), state)
}
func HashData(src io.Reader) (string, error) {
h := sha256.New()
if _, err := io.Copy(h, src); err != nil {