Browse Source

added login and push

Victor Vieux 12 years ago
parent
commit
f37399d22b
5 changed files with 209 additions and 70 deletions
  1. 68 0
      api.go
  2. 4 0
      api_params.go
  3. 82 70
      commands.go
  4. 21 0
      server.go
  5. 34 0
      utils.go

+ 68 - 0
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

+ 4 - 0
api_params.go

@@ -54,3 +54,7 @@ type ApiVersion struct {
 type ApiWait struct {
 	StatusCode int
 }
+
+type ApiAuth struct {
+	Status string
+}

+ 82 - 70
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
+	}
+
+	var out2 ApiAuth
+	err = json.Unmarshal(body, &out)
+	if err != nil {
+		return err
 	}
-	if status != "" {
-		fmt.Fprint(stdout, status)
+	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
+		}
+
+		body, _, err = call("GET", "/auth", nil)
+		if err != nil {
+			return err
+		}
+		err = json.Unmarshal(body, &out)
+		if err != nil {
 			return err
 		}
-		if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" {
+
+		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 <user>/<repo> (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")

+ 21 - 0
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

+ 34 - 0
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
+}
+*/