| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
_ "bytes" |
| 5 | 5 |
"encoding/json" |
| 6 | 6 |
"fmt" |
| 7 |
+ "github.com/dotcloud/docker/auth" |
|
| 7 | 8 |
"github.com/gorilla/mux" |
| 8 | 9 |
"log" |
| 9 | 10 |
"net" |
| ... | ... |
@@ -42,6 +43,51 @@ func ListenAndServe(addr string, srv *Server) error {
|
| 42 | 42 |
r := mux.NewRouter() |
| 43 | 43 |
log.Printf("Listening for HTTP on %s\n", addr)
|
| 44 | 44 |
|
| 45 |
+ r.Path("/auth").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
| 46 |
+ log.Println(r.Method, r.RequestURI) |
|
| 47 |
+ var out auth.AuthConfig |
|
| 48 |
+ out.Username = srv.runtime.authConfig.Username |
|
| 49 |
+ out.Email = srv.runtime.authConfig.Email |
|
| 50 |
+ b, err := json.Marshal(out) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
| 53 |
+ } else {
|
|
| 54 |
+ w.Write(b) |
|
| 55 |
+ } |
|
| 56 |
+ }) |
|
| 57 |
+ |
|
| 58 |
+ r.Path("/auth").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
| 59 |
+ log.Println(r.Method, r.RequestURI) |
|
| 60 |
+ var config auth.AuthConfig |
|
| 61 |
+ if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
|
| 62 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
| 63 |
+ return |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ if config.Username == srv.runtime.authConfig.Username {
|
|
| 67 |
+ config.Password = srv.runtime.authConfig.Password |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) |
|
| 71 |
+ status, err := auth.Login(newAuthConfig) |
|
| 72 |
+ if err != nil {
|
|
| 73 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
| 74 |
+ return |
|
| 75 |
+ } else {
|
|
| 76 |
+ srv.runtime.authConfig = newAuthConfig |
|
| 77 |
+ } |
|
| 78 |
+ if status != "" {
|
|
| 79 |
+ b, err := json.Marshal(ApiAuth{status})
|
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ http.Error(w, err.Error(), http.StatusInternalServerError) |
|
| 82 |
+ } else {
|
|
| 83 |
+ w.Write(b) |
|
| 84 |
+ } |
|
| 85 |
+ } else {
|
|
| 86 |
+ w.WriteHeader(http.StatusOK) |
|
| 87 |
+ } |
|
| 88 |
+ }) |
|
| 89 |
+ |
|
| 45 | 90 |
r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
| 46 | 91 |
log.Println(r.Method, r.RequestURI) |
| 47 | 92 |
m := srv.DockerVersion() |
| ... | ... |
@@ -272,6 +318,28 @@ func ListenAndServe(addr string, srv *Server) error {
|
| 272 | 272 |
} |
| 273 | 273 |
}) |
| 274 | 274 |
|
| 275 |
+ r.Path("/images/{name:*.}/push").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
| 276 |
+ log.Println(r.Method, r.RequestURI) |
|
| 277 |
+ vars := mux.Vars(r) |
|
| 278 |
+ name := vars["name"] |
|
| 279 |
+ |
|
| 280 |
+ file, rwc, err := hijackServer(w) |
|
| 281 |
+ if file != nil {
|
|
| 282 |
+ defer file.Close() |
|
| 283 |
+ } |
|
| 284 |
+ if rwc != nil {
|
|
| 285 |
+ defer rwc.Close() |
|
| 286 |
+ } |
|
| 287 |
+ if err != nil {
|
|
| 288 |
+ httpError(w, err) |
|
| 289 |
+ return |
|
| 290 |
+ } |
|
| 291 |
+ fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") |
|
| 292 |
+ if err := srv.ImagePush(name, file); err != nil {
|
|
| 293 |
+ fmt.Fprintln(file, "Error: "+err.Error()) |
|
| 294 |
+ } |
|
| 295 |
+ }) |
|
| 296 |
+ |
|
| 275 | 297 |
r.Path("/containers").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
| 276 | 298 |
log.Println(r.Method, r.RequestURI) |
| 277 | 299 |
var config Config |
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"encoding/json" |
| 6 | 6 |
"flag" |
| 7 | 7 |
"fmt" |
| 8 |
+ "github.com/dotcloud/docker/auth" |
|
| 8 | 9 |
"github.com/dotcloud/docker/term" |
| 9 | 10 |
"io" |
| 10 | 11 |
"io/ioutil" |
| ... | ... |
@@ -15,8 +16,10 @@ import ( |
| 15 | 15 |
"os" |
| 16 | 16 |
"path/filepath" |
| 17 | 17 |
"strconv" |
| 18 |
+ "strings" |
|
| 18 | 19 |
"text/tabwriter" |
| 19 | 20 |
"time" |
| 21 |
+ "unicode" |
|
| 20 | 22 |
) |
| 21 | 23 |
|
| 22 | 24 |
const VERSION = "0.2.2" |
| ... | ... |
@@ -59,10 +62,12 @@ func ParseCommands(args ...string) error {
|
| 59 | 59 |
"import": CmdImport, |
| 60 | 60 |
"history": CmdHistory, |
| 61 | 61 |
"kill": CmdKill, |
| 62 |
+ "login": CmdLogin, |
|
| 62 | 63 |
"logs": CmdLogs, |
| 63 | 64 |
"port": CmdPort, |
| 64 | 65 |
"ps": CmdPs, |
| 65 | 66 |
"pull": CmdPull, |
| 67 |
+ "push": CmdPush, |
|
| 66 | 68 |
"restart": CmdRestart, |
| 67 | 69 |
"rm": CmdRm, |
| 68 | 70 |
"rmi": CmdRmi, |
| ... | ... |
@@ -98,12 +103,12 @@ func cmdHelp(args ...string) error {
|
| 98 | 98 |
{"info", "Display system-wide information"},
|
| 99 | 99 |
{"inspect", "Return low-level information on a container/image"},
|
| 100 | 100 |
{"kill", "Kill a running container"},
|
| 101 |
- // {"login", "Register or Login to the docker registry server"},
|
|
| 101 |
+ {"login", "Register or Login to the docker registry server"},
|
|
| 102 | 102 |
{"logs", "Fetch the logs of a container"},
|
| 103 | 103 |
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
| 104 | 104 |
{"ps", "List containers"},
|
| 105 | 105 |
{"pull", "Pull an image or a repository from the docker registry server"},
|
| 106 |
- // {"push", "Push an image or a repository to the docker registry server"},
|
|
| 106 |
+ {"push", "Push an image or a repository to the docker registry server"},
|
|
| 107 | 107 |
{"restart", "Restart a running container"},
|
| 108 | 108 |
{"rm", "Remove a container"},
|
| 109 | 109 |
{"rmi", "Remove an image"},
|
| ... | ... |
@@ -120,17 +125,8 @@ func cmdHelp(args ...string) error {
|
| 120 | 120 |
return nil |
| 121 | 121 |
} |
| 122 | 122 |
|
| 123 |
-/* |
|
| 124 | 123 |
// 'docker login': login / register a user to registry service. |
| 125 |
-func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
|
| 126 |
- // Read a line on raw terminal with support for simple backspace |
|
| 127 |
- // sequences and echo. |
|
| 128 |
- // |
|
| 129 |
- // This function is necessary because the login command must be done in a |
|
| 130 |
- // raw terminal for two reasons: |
|
| 131 |
- // - we have to read a password (without echoing it); |
|
| 132 |
- // - the rcli "protocol" only supports cannonical and raw modes and you |
|
| 133 |
- // can't tune it once the command as been started. |
|
| 124 |
+func CmdLogin(args ...string) error {
|
|
| 134 | 125 |
var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string {
|
| 135 | 126 |
char := make([]byte, 1) |
| 136 | 127 |
buffer := make([]byte, 64) |
| ... | ... |
@@ -173,52 +169,74 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. |
| 173 | 173 |
return readStringOnRawTerminal(stdin, stdout, false) |
| 174 | 174 |
} |
| 175 | 175 |
|
| 176 |
- stdout.SetOptionRawTerminal() |
|
| 176 |
+ oldState, err := SetRawTerminal() |
|
| 177 |
+ if err != nil {
|
|
| 178 |
+ return err |
|
| 179 |
+ } else {
|
|
| 180 |
+ defer RestoreTerminal(oldState) |
|
| 181 |
+ } |
|
| 177 | 182 |
|
| 178 |
- cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") |
|
| 183 |
+ cmd := Subcmd("login", "", "Register or Login to the docker registry server")
|
|
| 179 | 184 |
if err := cmd.Parse(args); err != nil {
|
| 180 | 185 |
return nil |
| 181 | 186 |
} |
| 182 | 187 |
|
| 188 |
+ body, _, err := call("GET", "/auth", nil)
|
|
| 189 |
+ if err != nil {
|
|
| 190 |
+ return err |
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ var out auth.AuthConfig |
|
| 194 |
+ err = json.Unmarshal(body, &out) |
|
| 195 |
+ if err != nil {
|
|
| 196 |
+ return err |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 183 | 199 |
var username string |
| 184 | 200 |
var password string |
| 185 | 201 |
var email string |
| 186 | 202 |
|
| 187 |
- fmt.Fprint(stdout, "Username (", srv.runtime.authConfig.Username, "): ")
|
|
| 188 |
- username = readAndEchoString(stdin, stdout) |
|
| 203 |
+ fmt.Print("Username (", out.Username, "): ")
|
|
| 204 |
+ username = readAndEchoString(os.Stdin, os.Stdout) |
|
| 189 | 205 |
if username == "" {
|
| 190 |
- username = srv.runtime.authConfig.Username |
|
| 206 |
+ username = out.Username |
|
| 191 | 207 |
} |
| 192 |
- if username != srv.runtime.authConfig.Username {
|
|
| 193 |
- fmt.Fprint(stdout, "Password: ") |
|
| 194 |
- password = readString(stdin, stdout) |
|
| 208 |
+ if username != out.Username {
|
|
| 209 |
+ fmt.Print("Password: ")
|
|
| 210 |
+ password = readString(os.Stdin, os.Stdout) |
|
| 195 | 211 |
|
| 196 | 212 |
if password == "" {
|
| 197 | 213 |
return fmt.Errorf("Error : Password Required")
|
| 198 | 214 |
} |
| 199 | 215 |
|
| 200 |
- fmt.Fprint(stdout, "Email (", srv.runtime.authConfig.Email, "): ")
|
|
| 201 |
- email = readAndEchoString(stdin, stdout) |
|
| 216 |
+ fmt.Print("Email (", out.Email, "): ")
|
|
| 217 |
+ email = readAndEchoString(os.Stdin, os.Stdout) |
|
| 202 | 218 |
if email == "" {
|
| 203 |
- email = srv.runtime.authConfig.Email |
|
| 219 |
+ email = out.Email |
|
| 204 | 220 |
} |
| 205 | 221 |
} else {
|
| 206 |
- password = srv.runtime.authConfig.Password |
|
| 207 |
- email = srv.runtime.authConfig.Email |
|
| 222 |
+ email = out.Email |
|
| 208 | 223 |
} |
| 209 |
- newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root) |
|
| 210 |
- status, err := auth.Login(newAuthConfig) |
|
| 224 |
+ |
|
| 225 |
+ out.Username = username |
|
| 226 |
+ out.Password = password |
|
| 227 |
+ out.Email = email |
|
| 228 |
+ |
|
| 229 |
+ body, _, err = call("POST", "/auth", out)
|
|
| 211 | 230 |
if err != nil {
|
| 212 |
- fmt.Fprintf(stdout, "Error: %s\r\n", err) |
|
| 213 |
- } else {
|
|
| 214 |
- srv.runtime.authConfig = newAuthConfig |
|
| 231 |
+ return err |
|
| 232 |
+ } |
|
| 233 |
+ |
|
| 234 |
+ var out2 ApiAuth |
|
| 235 |
+ err = json.Unmarshal(body, &out) |
|
| 236 |
+ if err != nil {
|
|
| 237 |
+ return err |
|
| 215 | 238 |
} |
| 216 |
- if status != "" {
|
|
| 217 |
- fmt.Fprint(stdout, status) |
|
| 239 |
+ if out2.Status != "" {
|
|
| 240 |
+ fmt.Print(out2.Status) |
|
| 218 | 241 |
} |
| 219 | 242 |
return nil |
| 220 | 243 |
} |
| 221 |
-*/ |
|
| 222 | 244 |
|
| 223 | 245 |
// 'docker wait': block until a container stops |
| 224 | 246 |
func CmdWait(args ...string) error {
|
| ... | ... |
@@ -547,66 +565,60 @@ func CmdImport(args ...string) error {
|
| 547 | 547 |
return nil |
| 548 | 548 |
} |
| 549 | 549 |
|
| 550 |
-/* |
|
| 551 |
-func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
|
|
| 552 |
- cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") |
|
| 550 |
+func CmdPush(args ...string) error {
|
|
| 551 |
+ cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
|
| 553 | 552 |
if err := cmd.Parse(args); err != nil {
|
| 554 | 553 |
return nil |
| 555 | 554 |
} |
| 556 |
- local := cmd.Arg(0) |
|
| 555 |
+ name := cmd.Arg(0) |
|
| 557 | 556 |
|
| 558 |
- if local == "" {
|
|
| 557 |
+ if name == "" {
|
|
| 559 | 558 |
cmd.Usage() |
| 560 | 559 |
return nil |
| 561 | 560 |
} |
| 562 | 561 |
|
| 562 |
+ body, _, err := call("GET", "/auth", nil)
|
|
| 563 |
+ if err != nil {
|
|
| 564 |
+ return err |
|
| 565 |
+ } |
|
| 566 |
+ |
|
| 567 |
+ var out auth.AuthConfig |
|
| 568 |
+ err = json.Unmarshal(body, &out) |
|
| 569 |
+ if err != nil {
|
|
| 570 |
+ return err |
|
| 571 |
+ } |
|
| 572 |
+ |
|
| 563 | 573 |
// If the login failed, abort |
| 564 |
- if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" {
|
|
| 565 |
- if err := srv.CmdLogin(stdin, stdout, args...); err != nil {
|
|
| 574 |
+ if out.Username == "" {
|
|
| 575 |
+ if err := CmdLogin(args...); err != nil {
|
|
| 576 |
+ return err |
|
| 577 |
+ } |
|
| 578 |
+ |
|
| 579 |
+ body, _, err = call("GET", "/auth", nil)
|
|
| 580 |
+ if err != nil {
|
|
| 581 |
+ return err |
|
| 582 |
+ } |
|
| 583 |
+ err = json.Unmarshal(body, &out) |
|
| 584 |
+ if err != nil {
|
|
| 566 | 585 |
return err |
| 567 | 586 |
} |
| 568 |
- if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" {
|
|
| 587 |
+ |
|
| 588 |
+ if out.Username == "" {
|
|
| 569 | 589 |
return fmt.Errorf("Please login prior to push. ('docker login')")
|
| 570 | 590 |
} |
| 571 | 591 |
} |
| 572 | 592 |
|
| 573 |
- var remote string |
|
| 574 |
- |
|
| 575 |
- tmp := strings.SplitN(local, "/", 2) |
|
| 576 |
- if len(tmp) == 1 {
|
|
| 593 |
+ if len(strings.SplitN(name, "/", 2)) == 1 {
|
|
| 577 | 594 |
return fmt.Errorf( |
| 578 | 595 |
"Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", |
| 579 |
- srv.runtime.authConfig.Username, local) |
|
| 580 |
- } else {
|
|
| 581 |
- remote = local |
|
| 596 |
+ out.Username, name) |
|
| 582 | 597 |
} |
| 583 | 598 |
|
| 584 |
- Debugf("Pushing [%s] to [%s]\n", local, remote)
|
|
| 585 |
- |
|
| 586 |
- // Try to get the image |
|
| 587 |
- // FIXME: Handle lookup |
|
| 588 |
- // FIXME: Also push the tags in case of ./docker push myrepo:mytag |
|
| 589 |
- // img, err := srv.runtime.LookupImage(cmd.Arg(0)) |
|
| 590 |
- img, err := srv.runtime.graph.Get(local) |
|
| 591 |
- if err != nil {
|
|
| 592 |
- Debugf("The push refers to a repository [%s] (len: %d)\n", local, len(srv.runtime.repositories.Repositories[local]))
|
|
| 593 |
- // If it fails, try to get the repository |
|
| 594 |
- if localRepo, exists := srv.runtime.repositories.Repositories[local]; exists {
|
|
| 595 |
- if err := srv.runtime.graph.PushRepository(stdout, remote, localRepo, srv.runtime.authConfig); err != nil {
|
|
| 596 |
- return err |
|
| 597 |
- } |
|
| 598 |
- return nil |
|
| 599 |
- } |
|
| 600 |
- |
|
| 601 |
- return err |
|
| 602 |
- } |
|
| 603 |
- err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig) |
|
| 604 |
- if err != nil {
|
|
| 599 |
+ if err := hijack("POST", "/images"+name+"/pull", false); err != nil {
|
|
| 605 | 600 |
return err |
| 606 | 601 |
} |
| 607 | 602 |
return nil |
| 608 | 603 |
} |
| 609 |
-*/ |
|
| 610 | 604 |
|
| 611 | 605 |
func CmdPull(args ...string) error {
|
| 612 | 606 |
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
|
| ... | ... |
@@ -214,6 +214,27 @@ func (srv *Server) ImagePull(name string, file *os.File) error {
|
| 214 | 214 |
return nil |
| 215 | 215 |
} |
| 216 | 216 |
|
| 217 |
+func (srv *Server) ImagePush(name string, file *os.File) error {
|
|
| 218 |
+ img, err := srv.runtime.graph.Get(name) |
|
| 219 |
+ if err != nil {
|
|
| 220 |
+ Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
|
|
| 221 |
+ // If it fails, try to get the repository |
|
| 222 |
+ if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
|
|
| 223 |
+ if err := srv.runtime.graph.PushRepository(file, name, localRepo, srv.runtime.authConfig); err != nil {
|
|
| 224 |
+ return err |
|
| 225 |
+ } |
|
| 226 |
+ return nil |
|
| 227 |
+ } |
|
| 228 |
+ |
|
| 229 |
+ return err |
|
| 230 |
+ } |
|
| 231 |
+ err = srv.runtime.graph.PushImage(file, img, srv.runtime.authConfig) |
|
| 232 |
+ if err != nil {
|
|
| 233 |
+ return err |
|
| 234 |
+ } |
|
| 235 |
+ return nil |
|
| 236 |
+} |
|
| 237 |
+ |
|
| 217 | 238 |
func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error {
|
| 218 | 239 |
var archive io.Reader |
| 219 | 240 |
var resp *http.Response |
| ... | ... |
@@ -477,3 +477,37 @@ func FindCgroupMountpoint(cgroupType string) (string, error) {
|
| 477 | 477 |
|
| 478 | 478 |
return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
|
| 479 | 479 |
} |
| 480 |
+ |
|
| 481 |
+/* |
|
| 482 |
+func ReadStringOnTerminal(prompt string) (string, error) {
|
|
| 483 |
+ fmt.Print(prompt) |
|
| 484 |
+ in := bufio.NewReader(os.Stdin); |
|
| 485 |
+ return in.ReadString('\n');
|
|
| 486 |
+} |
|
| 487 |
+ |
|
| 488 |
+func ReadPasswdOnTerminal(prompt string) (string, error) {
|
|
| 489 |
+ fmt.Print(prompt); |
|
| 490 |
+ const stty_arg0 = "/bin/stty"; |
|
| 491 |
+ stty_argv_e_off := []string{"stty","-echo"};
|
|
| 492 |
+ stty_argv_e_on := []string{"stty","echo"};
|
|
| 493 |
+ const exec_cwdir = ""; |
|
| 494 |
+ fd := []*os.File{os.Stdin,os.Stdout,os.Stderr};
|
|
| 495 |
+ pid, err := os.StartProcess(stty_arg0,stty_argv_e_off,nil,exec_cwdir,fd); |
|
| 496 |
+ if err != nil {
|
|
| 497 |
+ return "", err |
|
| 498 |
+ } |
|
| 499 |
+ rd := bufio.NewReader(os.Stdin); |
|
| 500 |
+ os.Wait(pid,0); |
|
| 501 |
+ line, err := rd.ReadString('\n');
|
|
| 502 |
+ if err != nil {
|
|
| 503 |
+ return "", err |
|
| 504 |
+ } |
|
| 505 |
+ passwd := strings.TrimSpace(line) |
|
| 506 |
+ pid, e := os.StartProcess(stty_arg0,stty_argv_e_on,nil,exec_cwdir,fd); |
|
| 507 |
+ if e =! nil {
|
|
| 508 |
+ return "", err |
|
| 509 |
+ } |
|
| 510 |
+ os.Wait(pid,0) |
|
| 511 |
+ return passwd, err |
|
| 512 |
+} |
|
| 513 |
+*/ |