Browse code

added ability to login/register to the docker registry, via the docker login command

Ken Cochrane authored on 2013/03/15 09:43:59
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,136 @@
0
+package auth
1
+
2
+import (
3
+	"encoding/base64"
4
+	"encoding/json"
5
+	"errors"
6
+	"fmt"
7
+	"io/ioutil"
8
+	"net/http"
9
+	"os"
10
+	"strings"
11
+)
12
+
13
+// Where we store the config file
14
+const CONFIGFILE = "/var/lib/docker/.dockercfg"
15
+
16
+// the registry server we want to login against
17
+const REGISTRY_SERVER = "http://registry.docker.io"
18
+
19
+type AuthConfig struct {
20
+	Username string `json:"username"`
21
+	Password string `json:"password"`
22
+	Email    string `json:"email"`
23
+}
24
+
25
+// create a base64 encoded auth string to store in config
26
+func EncodeAuth(authConfig AuthConfig) string {
27
+	authStr := authConfig.Username + ":" + authConfig.Password
28
+	msg := []byte(authStr)
29
+	encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
30
+	base64.StdEncoding.Encode(encoded, msg)
31
+	return string(encoded)
32
+}
33
+
34
+// decode the auth string
35
+func DecodeAuth(authStr string) (AuthConfig, error) {
36
+	decLen := base64.StdEncoding.DecodedLen(len(authStr))
37
+	decoded := make([]byte, decLen)
38
+	authByte := []byte(authStr)
39
+	n, err := base64.StdEncoding.Decode(decoded, authByte)
40
+	if err != nil {
41
+		return AuthConfig{}, err
42
+	}
43
+	if n > decLen {
44
+		return AuthConfig{}, errors.New("something went wrong decoding auth config")
45
+	}
46
+	arr := strings.Split(string(decoded), ":")
47
+	password := strings.Trim(arr[1], "\x00")
48
+	return AuthConfig{Username: arr[0], Password: password}, nil
49
+
50
+}
51
+
52
+// load up the auth config information and return values
53
+func LoadConfig() (AuthConfig, error) {
54
+	if _, err := os.Stat(CONFIGFILE); err == nil {
55
+		b, err := ioutil.ReadFile(CONFIGFILE)
56
+		if err != nil {
57
+			return AuthConfig{}, err
58
+		}
59
+		arr := strings.Split(string(b), "\n")
60
+		orig_auth := strings.Split(arr[0], " = ")
61
+		orig_email := strings.Split(arr[1], " = ")
62
+		authConfig, err := DecodeAuth(orig_auth[1])
63
+		if err != nil {
64
+			return AuthConfig{}, err
65
+		}
66
+		authConfig.Email = orig_email[1]
67
+		return authConfig, nil
68
+	} else {
69
+		return AuthConfig{}, nil
70
+	}
71
+	return AuthConfig{}, nil
72
+}
73
+
74
+// save the auth config
75
+func saveConfig(authStr string, email string) error {
76
+	lines := "auth = " + authStr + "\n" + "email = " + email + "\n"
77
+	b := []byte(lines)
78
+	err := ioutil.WriteFile(CONFIGFILE, b, 0600)
79
+	if err != nil {
80
+		return err
81
+	}
82
+	return nil
83
+}
84
+
85
+// try to register/login to the registry server
86
+func Login(authConfig AuthConfig) (string, error) {
87
+	storeConfig := false
88
+	reqStatusCode := 0
89
+	var status string
90
+	var reqBody []byte
91
+	jsonBody, _ := json.Marshal(authConfig)
92
+	b := strings.NewReader(string(jsonBody))
93
+	req1, err := http.Post(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b)
94
+	if err == nil {
95
+		body, _ := ioutil.ReadAll(req1.Body)
96
+		reqStatusCode = req1.StatusCode
97
+		reqBody = body
98
+		req1.Body.Close()
99
+	} else {
100
+		return "", err
101
+	}
102
+	if reqStatusCode == 201 {
103
+		status = "Account Created\n"
104
+		storeConfig = true
105
+	} else if reqStatusCode == 400 {
106
+		if string(reqBody) == "Username or email already exist" {
107
+			client := &http.Client{}
108
+			req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil)
109
+			req.SetBasicAuth(authConfig.Username, authConfig.Password)
110
+			resp, err := client.Do(req)
111
+			if err != nil {
112
+				return "", err
113
+			}
114
+			defer resp.Body.Close()
115
+			body, err := ioutil.ReadAll(resp.Body)
116
+			if err != nil {
117
+				return "", err
118
+			}
119
+			if resp.StatusCode == 200 {
120
+				status = "Login Succeeded\n"
121
+				storeConfig = true
122
+			} else {
123
+				storeConfig = false
124
+				status = fmt.Sprintf("Error: %s\n", body)
125
+			}
126
+		} else {
127
+			status = fmt.Sprintf("Error: %s\n", reqBody)
128
+		}
129
+	}
130
+	if storeConfig {
131
+		authStr := EncodeAuth(authConfig)
132
+		saveConfig(authStr, authConfig.Email)
133
+	}
134
+	return status, nil
135
+}
0 136
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package auth
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestEncodeAuth(t *testing.T) {
7
+	newAuthConfig := AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
8
+	authStr := EncodeAuth(newAuthConfig)
9
+	decAuthConfig, err := DecodeAuth(authStr)
10
+	if err != nil {
11
+		t.Fatal(err)
12
+	}
13
+	if newAuthConfig.Username != decAuthConfig.Username {
14
+		t.Fatal("Encode Username doesn't match decoded Username")
15
+	}
16
+	if newAuthConfig.Password != decAuthConfig.Password {
17
+		t.Fatal("Encode Password doesn't match decoded Password")
18
+	}
19
+	if authStr != "a2VuOnRlc3Q=" {
20
+		t.Fatal("AuthString encoding isn't correct.")
21
+	}
22
+}
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"errors"
8 8
 	"fmt"
9 9
 	"github.com/dotcloud/docker"
10
+	"github.com/dotcloud/docker/auth"
10 11
 	"github.com/dotcloud/docker/fs"
11 12
 	"github.com/dotcloud/docker/future"
12 13
 	"github.com/dotcloud/docker/rcli"
... ...
@@ -47,6 +48,7 @@ func (srv *Server) Help() string {
47 47
 		{"inspect", "Return low-level information on a container"},
48 48
 		{"kill", "Kill a running container"},
49 49
 		{"layers", "(debug only) List filesystem layers"},
50
+		{"login", "Register or Login to the docker registry server"},
50 51
 		{"logs", "Fetch the logs of a container"},
51 52
 		{"ls", "List the contents of a container's directory"},
52 53
 		{"mirror", "(debug only) (No documentation available)"},
... ...
@@ -71,6 +73,43 @@ func (srv *Server) Help() string {
71 71
 	return help
72 72
 }
73 73
 
74
+// 'docker login': login / register a user to registry service.
75
+func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
76
+	var username string
77
+	var password string
78
+	var email string
79
+	authConfig, err := auth.LoadConfig()
80
+	if err != nil {
81
+		fmt.Fprintf(stdout, "Error : %s\n", err)
82
+	}
83
+
84
+	fmt.Fprint(stdout, "Username (", authConfig.Username, "): ")
85
+	fmt.Scanf("%s", &username)
86
+	if username == "" {
87
+		username = authConfig.Username
88
+	}
89
+	if username != authConfig.Username {
90
+		fmt.Fprint(stdout, "Password: ")
91
+		fmt.Scanf("%s", &password)
92
+
93
+		fmt.Fprint(stdout, "Email (", authConfig.Email, "): ")
94
+		fmt.Scanf("%s", &email)
95
+		if email == "" {
96
+			email = authConfig.Email
97
+		}
98
+	} else {
99
+		password = authConfig.Password
100
+		email = authConfig.Email
101
+	}
102
+	newAuthConfig := auth.AuthConfig{Username: username, Password: password, Email: email}
103
+	status, err := auth.Login(newAuthConfig)
104
+	if err != nil {
105
+		fmt.Fprintf(stdout, "Error : %s\n", err)
106
+	}
107
+	fmt.Fprintf(stdout, status)
108
+	return nil
109
+}
110
+
74 111
 // 'docker wait': block until a container stops
75 112
 func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
76 113
 	cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.")