Merge pull request #432 from dotcloud/remote-api
+ Remote API: Implement the remote API.
This commit is contained in:
commit
26bfeb1d67
27 changed files with 4640 additions and 1185 deletions
618
api.go
Normal file
618
api.go
Normal 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
66
api_params.go
Normal 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
877
api_test.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
1527
commands.go
1527
commands.go
File diff suppressed because it is too large
Load diff
|
@ -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()
|
||||
}
|
||||
*/
|
||||
|
|
20
container.go
20
container.go
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
0
docs/sources/.nojekyll
Normal file
1
docs/sources/CNAME
Normal file
1
docs/sources/CNAME
Normal file
|
@ -0,0 +1 @@
|
|||
docker.io
|
|
@ -13,4 +13,4 @@ Contents:
|
|||
|
||||
basics
|
||||
workingwithrepository
|
||||
cli
|
||||
cli
|
||||
|
|
2
docs/sources/dotcloud.yml
Normal file
2
docs/sources/dotcloud.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
www:
|
||||
type: static
|
210
docs/sources/gettingstarted/index.html
Normal file
210
docs/sources/gettingstarted/index.html
Normal 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
314
docs/sources/index.html
Normal 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>
|
||||
|
||||
<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>
|
|
@ -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
6
docs/sources/nginx.conf
Normal 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;
|
1005
docs/sources/remote-api/api.rst
Normal file
1005
docs/sources/remote-api/api.rst
Normal file
File diff suppressed because it is too large
Load diff
15
docs/sources/remote-api/index.rst
Normal file
15
docs/sources/remote-api/index.rst
Normal 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
|
169
rcli/tcp.go
169
rcli/tcp.go
|
@ -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
|
||||
}
|
181
rcli/types.go
181
rcli/types.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
591
server.go
Normal 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
96
server_test.go
Normal 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()))
|
||||
}
|
||||
|
||||
}
|
25
utils.go
25
utils.go
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue