diff --git a/api.go b/api.go index 738d8d5633..23a63a4b33 100644 --- a/api.go +++ b/api.go @@ -4,6 +4,7 @@ import ( _ "bytes" "encoding/json" "fmt" + "github.com/dotcloud/docker/auth" "github.com/gorilla/mux" "log" "net" @@ -42,6 +43,51 @@ func ListenAndServe(addr string, srv *Server) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) + r.Path("/auth").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + var out auth.AuthConfig + out.Username = srv.runtime.authConfig.Username + out.Email = srv.runtime.authConfig.Email + b, err := json.Marshal(out) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + }) + + r.Path("/auth").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + var config auth.AuthConfig + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + 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 { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + srv.runtime.authConfig = newAuthConfig + } + if status != "" { + b, err := json.Marshal(ApiAuth{status}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + } else { + w.WriteHeader(http.StatusOK) + } + }) + r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) m := srv.DockerVersion() @@ -272,6 +318,28 @@ func ListenAndServe(addr string, srv *Server) error { } }) + r.Path("/images/{name:*.}/push").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return + } + fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImagePush(name, file); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + } + }) + r.Path("/containers").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) var config Config diff --git a/api_params.go b/api_params.go index 0b46f8c727..b86265b178 100644 --- a/api_params.go +++ b/api_params.go @@ -54,3 +54,7 @@ type ApiVersion struct { type ApiWait struct { StatusCode int } + +type ApiAuth struct { + Status string +} diff --git a/commands.go b/commands.go index 0305c115af..168c04bc2f 100644 --- a/commands.go +++ b/commands.go @@ -5,6 +5,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/term" "io" "io/ioutil" @@ -15,8 +16,10 @@ import ( "os" "path/filepath" "strconv" + "strings" "text/tabwriter" "time" + "unicode" ) const VERSION = "0.2.2" @@ -59,10 +62,12 @@ func ParseCommands(args ...string) error { "import": CmdImport, "history": CmdHistory, "kill": CmdKill, + "login": CmdLogin, "logs": CmdLogs, "port": CmdPort, "ps": CmdPs, "pull": CmdPull, + "push": CmdPush, "restart": CmdRestart, "rm": CmdRm, "rmi": CmdRmi, @@ -98,12 +103,12 @@ func cmdHelp(args ...string) error { {"info", "Display system-wide information"}, {"inspect", "Return low-level information on a container/image"}, {"kill", "Kill a running container"}, - // {"login", "Register or Login to the docker registry server"}, + {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from the docker registry server"}, - // {"push", "Push an image or a repository to the docker registry server"}, + {"push", "Push an image or a repository to the docker registry server"}, {"restart", "Restart a running container"}, {"rm", "Remove a container"}, {"rmi", "Remove an image"}, @@ -120,17 +125,8 @@ func cmdHelp(args ...string) error { return nil } -/* // 'docker login': login / register a user to registry service. -func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - // Read a line on raw terminal with support for simple backspace - // sequences and echo. - // - // This function is necessary because the login command must be done in a - // raw terminal for two reasons: - // - we have to read a password (without echoing it); - // - the rcli "protocol" only supports cannonical and raw modes and you - // can't tune it once the command as been started. +func CmdLogin(args ...string) error { var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { char := make([]byte, 1) buffer := make([]byte, 64) @@ -173,52 +169,74 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. return readStringOnRawTerminal(stdin, stdout, false) } - stdout.SetOptionRawTerminal() + oldState, err := SetRawTerminal() + if err != nil { + return err + } else { + defer RestoreTerminal(oldState) + } - cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") + cmd := Subcmd("login", "", "Register or Login to the docker registry server") if err := cmd.Parse(args); err != nil { return nil } + body, _, err := call("GET", "/auth", nil) + if err != nil { + return err + } + + var out auth.AuthConfig + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + var username string var password string var email string - fmt.Fprint(stdout, "Username (", srv.runtime.authConfig.Username, "): ") - username = readAndEchoString(stdin, stdout) + fmt.Print("Username (", out.Username, "): ") + username = readAndEchoString(os.Stdin, os.Stdout) if username == "" { - username = srv.runtime.authConfig.Username + username = out.Username } - if username != srv.runtime.authConfig.Username { - fmt.Fprint(stdout, "Password: ") - password = readString(stdin, stdout) + if username != out.Username { + fmt.Print("Password: ") + password = readString(os.Stdin, os.Stdout) if password == "" { return fmt.Errorf("Error : Password Required") } - fmt.Fprint(stdout, "Email (", srv.runtime.authConfig.Email, "): ") - email = readAndEchoString(stdin, stdout) + fmt.Print("Email (", out.Email, "): ") + email = readAndEchoString(os.Stdin, os.Stdout) if email == "" { - email = srv.runtime.authConfig.Email + email = out.Email } } else { - password = srv.runtime.authConfig.Password - email = srv.runtime.authConfig.Email + email = out.Email } - newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root) - status, err := auth.Login(newAuthConfig) + + out.Username = username + out.Password = password + out.Email = email + + body, _, err = call("POST", "/auth", out) if err != nil { - fmt.Fprintf(stdout, "Error: %s\r\n", err) - } else { - srv.runtime.authConfig = newAuthConfig + return err } - if status != "" { - fmt.Fprint(stdout, status) + + var out2 ApiAuth + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + if out2.Status != "" { + fmt.Print(out2.Status) } return nil } -*/ // 'docker wait': block until a container stops func CmdWait(args ...string) error { @@ -547,66 +565,60 @@ func CmdImport(args ...string) error { return nil } -/* -func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") +func CmdPush(args ...string) error { + cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry") if err := cmd.Parse(args); err != nil { return nil } - local := cmd.Arg(0) + name := cmd.Arg(0) - if local == "" { + if name == "" { cmd.Usage() return nil } + body, _, err := call("GET", "/auth", nil) + if err != nil { + return err + } + + var out auth.AuthConfig + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + // If the login failed, abort - if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" { - if err := srv.CmdLogin(stdin, stdout, args...); err != nil { + if out.Username == "" { + if err := CmdLogin(args...); err != nil { return err } - if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" { + + body, _, err = call("GET", "/auth", nil) + if err != nil { + return err + } + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + + if out.Username == "" { return fmt.Errorf("Please login prior to push. ('docker login')") } } - var remote string - - tmp := strings.SplitN(local, "/", 2) - if len(tmp) == 1 { + if len(strings.SplitN(name, "/", 2)) == 1 { return fmt.Errorf( "Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", - srv.runtime.authConfig.Username, local) - } else { - remote = local + out.Username, name) } - Debugf("Pushing [%s] to [%s]\n", local, remote) - - // Try to get the image - // FIXME: Handle lookup - // FIXME: Also push the tags in case of ./docker push myrepo:mytag - // img, err := srv.runtime.LookupImage(cmd.Arg(0)) - img, err := srv.runtime.graph.Get(local) - if err != nil { - Debugf("The push refers to a repository [%s] (len: %d)\n", local, len(srv.runtime.repositories.Repositories[local])) - // If it fails, try to get the repository - if localRepo, exists := srv.runtime.repositories.Repositories[local]; exists { - if err := srv.runtime.graph.PushRepository(stdout, remote, localRepo, srv.runtime.authConfig); err != nil { - return err - } - return nil - } - - return err - } - err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig) - if err != nil { + if err := hijack("POST", "/images"+name+"/pull", false); err != nil { return err } return nil } -*/ func CmdPull(args ...string) error { cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") diff --git a/server.go b/server.go index 4d6feb3f71..7ea71d1beb 100644 --- a/server.go +++ b/server.go @@ -214,6 +214,27 @@ func (srv *Server) ImagePull(name string, file *os.File) error { return nil } +func (srv *Server) ImagePush(name string, file *os.File) 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(file, name, localRepo, srv.runtime.authConfig); err != nil { + return err + } + return nil + } + + return err + } + err = srv.runtime.graph.PushImage(file, img, srv.runtime.authConfig) + if err != nil { + return err + } + return nil +} + func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { var archive io.Reader var resp *http.Response diff --git a/utils.go b/utils.go index 40f67f7193..8f269df40c 100644 --- a/utils.go +++ b/utils.go @@ -477,3 +477,37 @@ func FindCgroupMountpoint(cgroupType string) (string, error) { return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) } + +/* +func ReadStringOnTerminal(prompt string) (string, error) { + fmt.Print(prompt) + in := bufio.NewReader(os.Stdin); + return in.ReadString('\n'); +} + +func ReadPasswdOnTerminal(prompt string) (string, error) { + fmt.Print(prompt); + const stty_arg0 = "/bin/stty"; + stty_argv_e_off := []string{"stty","-echo"}; + stty_argv_e_on := []string{"stty","echo"}; + const exec_cwdir = ""; + fd := []*os.File{os.Stdin,os.Stdout,os.Stderr}; + pid, err := os.StartProcess(stty_arg0,stty_argv_e_off,nil,exec_cwdir,fd); + if err != nil { + return "", err + } + rd := bufio.NewReader(os.Stdin); + os.Wait(pid,0); + line, err := rd.ReadString('\n'); + if err != nil { + return "", err + } + passwd := strings.TrimSpace(line) + pid, e := os.StartProcess(stty_arg0,stty_argv_e_on,nil,exec_cwdir,fd); + if e =! nil { + return "", err + } + os.Wait(pid,0) + return passwd, err +} +*/