diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000000..ae0e71b111 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,151 @@ +package auth + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" +) + +// Where we store the config file +const CONFIGFILE = "/var/lib/docker/.dockercfg" + +// the registry server we want to login against +const REGISTRY_SERVER = "http://registry.docker.io" + +type AuthConfig struct { + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` +} + +// create a base64 encoded auth string to store in config +func EncodeAuth(authConfig AuthConfig) string { + authStr := authConfig.Username + ":" + authConfig.Password + msg := []byte(authStr) + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) + base64.StdEncoding.Encode(encoded, msg) + return string(encoded) +} + +// decode the auth string +func DecodeAuth(authStr string) (AuthConfig, error) { + decLen := base64.StdEncoding.DecodedLen(len(authStr)) + decoded := make([]byte, decLen) + authByte := []byte(authStr) + n, err := base64.StdEncoding.Decode(decoded, authByte) + if err != nil { + return AuthConfig{}, err + } + if n > decLen { + return AuthConfig{}, errors.New("something went wrong decoding auth config") + } + arr := strings.Split(string(decoded), ":") + password := strings.Trim(arr[1], "\x00") + return AuthConfig{Username: arr[0], Password: password}, nil + +} + +// load up the auth config information and return values +func LoadConfig() (AuthConfig, error) { + if _, err := os.Stat(CONFIGFILE); err == nil { + b, err := ioutil.ReadFile(CONFIGFILE) + if err != nil { + return AuthConfig{}, err + } + arr := strings.Split(string(b), "\n") + orig_auth := strings.Split(arr[0], " = ") + orig_email := strings.Split(arr[1], " = ") + authConfig, err := DecodeAuth(orig_auth[1]) + if err != nil { + return AuthConfig{}, err + } + authConfig.Email = orig_email[1] + return authConfig, nil + } else { + return AuthConfig{}, nil + } + return AuthConfig{}, nil +} + +// save the auth config +func saveConfig(authStr string, email string) error { + lines := "auth = " + authStr + "\n" + "email = " + email + "\n" + b := []byte(lines) + err := ioutil.WriteFile(CONFIGFILE, b, 0600) + if err != nil { + return err + } + return nil +} + +// try to register/login to the registry server +func Login(authConfig AuthConfig) (string, error) { + storeConfig := false + reqStatusCode := 0 + var status string + var errMsg string + var reqBody []byte + jsonBody, err := json.Marshal(authConfig) + if err != nil { + errMsg = fmt.Sprintf("Config Error: %s", err) + return "", errors.New(errMsg) + } + + b := strings.NewReader(string(jsonBody)) + req1, err := http.Post(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b) + if err != nil { + errMsg = fmt.Sprintf("Server Error: %s", err) + return "", errors.New(errMsg) + } + + reqStatusCode = req1.StatusCode + defer req1.Body.Close() + reqBody, err = ioutil.ReadAll(req1.Body) + if err != nil { + errMsg = fmt.Sprintf("Server Error: [%#v] %s", reqStatusCode, err) + return "", errors.New(errMsg) + } + + if reqStatusCode == 201 { + status = "Account Created\n" + storeConfig = true + } else if reqStatusCode == 400 { + if string(reqBody) == "Username or email already exist" { + client := &http.Client{} + req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil) + req.SetBasicAuth(authConfig.Username, authConfig.Password) + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + if resp.StatusCode == 200 { + status = "Login Succeeded\n" + storeConfig = true + } else { + status = fmt.Sprintf("Login: %s", body) + return "", errors.New(status) + } + } else { + status = fmt.Sprintf("Registration: %s", string(reqBody)) + return "", errors.New(status) + } + } else { + status = fmt.Sprintf("[%s] : %s", reqStatusCode, string(reqBody)) + return "", errors.New(status) + } + if storeConfig { + authStr := EncodeAuth(authConfig) + saveConfig(authStr, authConfig.Email) + } + return status, nil +} diff --git a/auth/auth_test.go b/auth/auth_test.go new file mode 100644 index 0000000000..d1650668e7 --- /dev/null +++ b/auth/auth_test.go @@ -0,0 +1,23 @@ +package auth + +import ( + "testing" +) + +func TestEncodeAuth(t *testing.T) { + newAuthConfig := AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} + authStr := EncodeAuth(newAuthConfig) + decAuthConfig, err := DecodeAuth(authStr) + if err != nil { + t.Fatal(err) + } + if newAuthConfig.Username != decAuthConfig.Username { + t.Fatal("Encode Username doesn't match decoded Username") + } + if newAuthConfig.Password != decAuthConfig.Password { + t.Fatal("Encode Password doesn't match decoded Password") + } + if authStr != "a2VuOnRlc3Q=" { + t.Fatal("AuthString encoding isn't correct.") + } +} diff --git a/commands.go b/commands.go index 4d8e252062..cff79baeed 100644 --- a/commands.go +++ b/commands.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/fs" "github.com/dotcloud/docker/future" "github.com/dotcloud/docker/rcli" @@ -46,6 +47,7 @@ func (srv *Server) Help() string { {"inspect", "Return low-level information on a container"}, {"kill", "Kill a running container"}, {"layers", "(debug only) List filesystem layers"}, + {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, {"ls", "List the contents of a container's directory"}, {"mirror", "(debug only) (No documentation available)"}, @@ -70,6 +72,53 @@ func (srv *Server) Help() string { return help } +// 'docker login': login / register a user to registry service. +func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") + if err := cmd.Parse(args); err != nil { + return nil + } + var username string + var password string + var email string + authConfig, err := auth.LoadConfig() + if err != nil { + fmt.Fprintf(stdout, "Error : %s\n", err) + } + + fmt.Fprint(stdout, "Username (", authConfig.Username, "): ") + fmt.Fscanf(stdin, "%s", &username) + if username == "" { + username = authConfig.Username + } + if username != authConfig.Username { + fmt.Fprint(stdout, "Password: ") + fmt.Fscanf(stdin, "%s", &password) + + if password == "" { + return errors.New("Error : Password Required\n") + } + + fmt.Fprint(stdout, "Email (", authConfig.Email, "): ") + fmt.Fscanf(stdin, "%s", &email) + if email == "" { + email = authConfig.Email + } + } else { + password = authConfig.Password + email = authConfig.Email + } + newAuthConfig := auth.AuthConfig{Username: username, Password: password, Email: email} + status, err := auth.Login(newAuthConfig) + if err != nil { + fmt.Fprintf(stdout, "Error : %s\n", err) + } + if status != "" { + fmt.Fprintf(stdout, status) + } + return nil +} + // 'docker wait': block until a container stops func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.") diff --git a/future/future.go b/future/future.go index d4f841524a..21b0eee385 100644 --- a/future/future.go +++ b/future/future.go @@ -101,23 +101,24 @@ func Download(url string, stderr io.Writer) (*http.Response, error) { // Reader with progress bar type progressReader struct { - reader io.ReadCloser // Stream to read from - output io.Writer // Where to send progress bar to - read_total int // Expected stream length (bytes) - read_progress int // How much has been read so far (bytes) - last_update int // How many bytes read at least update + reader io.ReadCloser // Stream to read from + output io.Writer // Where to send progress bar to + read_total int // Expected stream length (bytes) + read_progress int // How much has been read so far (bytes) + last_update int // How many bytes read at least update } + func (r *progressReader) Read(p []byte) (n int, err error) { read, err := io.ReadCloser(r.reader).Read(p) r.read_progress += read // Only update progress for every 1% read update_every := int(0.01 * float64(r.read_total)) - if r.read_progress - r.last_update > update_every || r.read_progress == r.read_total { - fmt.Fprintf(r.output, "%d/%d (%.0f%%)\r", + if r.read_progress-r.last_update > update_every || r.read_progress == r.read_total { + fmt.Fprintf(r.output, "%d/%d (%.0f%%)\r", r.read_progress, r.read_total, - float64(r.read_progress) / float64(r.read_total) * 100) + float64(r.read_progress)/float64(r.read_total)*100) r.last_update = r.read_progress } // Send newline when complete