Browse code

Use docker-credential-helpers client to talk with native creds stores.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2016/06/05 02:08:22
Showing 3 changed files
... ...
@@ -1,14 +1,8 @@
1 1
 package credentials
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"errors"
7
-	"fmt"
8
-	"io"
9
-	"strings"
10
-
11
-	"github.com/Sirupsen/logrus"
4
+	"github.com/docker/docker-credential-helpers/client"
5
+	"github.com/docker/docker-credential-helpers/credentials"
12 6
 	"github.com/docker/docker/cliconfig/configfile"
13 7
 	"github.com/docker/engine-api/types"
14 8
 )
... ...
@@ -18,50 +12,27 @@ const (
18 18
 	tokenUsername           = "<token>"
19 19
 )
20 20
 
21
-// Standarize the not found error, so every helper returns
22
-// the same message and docker can handle it properly.
23
-var errCredentialsNotFound = errors.New("credentials not found in native keychain")
24
-
25
-// command is an interface that remote executed commands implement.
26
-type command interface {
27
-	Output() ([]byte, error)
28
-	Input(in io.Reader)
29
-}
30
-
31
-// credentialsRequest holds information shared between docker and a remote credential store.
32
-type credentialsRequest struct {
33
-	ServerURL string
34
-	Username  string
35
-	Secret    string
36
-}
37
-
38
-// credentialsGetResponse is the information serialized from a remote store
39
-// when the plugin sends requests to get the user credentials.
40
-type credentialsGetResponse struct {
41
-	Username string
42
-	Secret   string
43
-}
44
-
45 21
 // nativeStore implements a credentials store
46 22
 // using native keychain to keep credentials secure.
47 23
 // It piggybacks into a file store to keep users' emails.
48 24
 type nativeStore struct {
49
-	commandFn func(args ...string) command
50
-	fileStore Store
25
+	programFunc client.ProgramFunc
26
+	fileStore   Store
51 27
 }
52 28
 
53 29
 // NewNativeStore creates a new native store that
54 30
 // uses a remote helper program to manage credentials.
55 31
 func NewNativeStore(file *configfile.ConfigFile) Store {
32
+	name := remoteCredentialsPrefix + file.CredentialsStore
56 33
 	return &nativeStore{
57
-		commandFn: shellCommandFn(file.CredentialsStore),
58
-		fileStore: NewFileStore(file),
34
+		programFunc: client.NewShellProgramFunc(name),
35
+		fileStore:   NewFileStore(file),
59 36
 	}
60 37
 }
61 38
 
62 39
 // Erase removes the given credentials from the native store.
63 40
 func (c *nativeStore) Erase(serverAddress string) error {
64
-	if err := c.eraseCredentialsFromStore(serverAddress); err != nil {
41
+	if err := client.Erase(c.programFunc, serverAddress); err != nil {
65 42
 		return err
66 43
 	}
67 44
 
... ...
@@ -115,8 +86,7 @@ func (c *nativeStore) Store(authConfig types.AuthConfig) error {
115 115
 
116 116
 // storeCredentialsInStore executes the command to store the credentials in the native store.
117 117
 func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
118
-	cmd := c.commandFn("store")
119
-	creds := &credentialsRequest{
118
+	creds := &credentials.Credentials{
120 119
 		ServerURL: config.ServerAddress,
121 120
 		Username:  config.Username,
122 121
 		Secret:    config.Password,
... ...
@@ -127,70 +97,30 @@ func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
127 127
 		creds.Secret = config.IdentityToken
128 128
 	}
129 129
 
130
-	buffer := new(bytes.Buffer)
131
-	if err := json.NewEncoder(buffer).Encode(creds); err != nil {
132
-		return err
133
-	}
134
-	cmd.Input(buffer)
135
-
136
-	out, err := cmd.Output()
137
-	if err != nil {
138
-		t := strings.TrimSpace(string(out))
139
-		logrus.Debugf("error adding credentials - err: %v, out: `%s`", err, t)
140
-		return fmt.Errorf(t)
141
-	}
142
-
143
-	return nil
130
+	return client.Store(c.programFunc, creds)
144 131
 }
145 132
 
146 133
 // getCredentialsFromStore executes the command to get the credentials from the native store.
147 134
 func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
148 135
 	var ret types.AuthConfig
149 136
 
150
-	cmd := c.commandFn("get")
151
-	cmd.Input(strings.NewReader(serverAddress))
152
-
153
-	out, err := cmd.Output()
137
+	creds, err := client.Get(c.programFunc, serverAddress)
154 138
 	if err != nil {
155
-		t := strings.TrimSpace(string(out))
156
-
157
-		// do not return an error if the credentials are not
158
-		// in the keyckain. Let docker ask for new credentials.
159
-		if t == errCredentialsNotFound.Error() {
139
+		if credentials.IsErrCredentialsNotFound(err) {
140
+			// do not return an error if the credentials are not
141
+			// in the keyckain. Let docker ask for new credentials.
160 142
 			return ret, nil
161 143
 		}
162
-
163
-		logrus.Debugf("error getting credentials - err: %v, out: `%s`", err, t)
164
-		return ret, fmt.Errorf(t)
165
-	}
166
-
167
-	var resp credentialsGetResponse
168
-	if err := json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
169 144
 		return ret, err
170 145
 	}
171 146
 
172
-	if resp.Username == tokenUsername {
173
-		ret.IdentityToken = resp.Secret
147
+	if creds.Username == tokenUsername {
148
+		ret.IdentityToken = creds.Secret
174 149
 	} else {
175
-		ret.Password = resp.Secret
176
-		ret.Username = resp.Username
150
+		ret.Password = creds.Secret
151
+		ret.Username = creds.Username
177 152
 	}
178 153
 
179 154
 	ret.ServerAddress = serverAddress
180 155
 	return ret, nil
181 156
 }
182
-
183
-// eraseCredentialsFromStore executes the command to remove the server credentails from the native store.
184
-func (c *nativeStore) eraseCredentialsFromStore(serverURL string) error {
185
-	cmd := c.commandFn("erase")
186
-	cmd.Input(strings.NewReader(serverURL))
187
-
188
-	out, err := cmd.Output()
189
-	if err != nil {
190
-		t := strings.TrimSpace(string(out))
191
-		logrus.Debugf("error erasing credentials - err: %v, out: `%s`", err, t)
192
-		return fmt.Errorf(t)
193
-	}
194
-
195
-	return nil
196
-}
... ...
@@ -8,6 +8,8 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
+	"github.com/docker/docker-credential-helpers/client"
12
+	"github.com/docker/docker-credential-helpers/credentials"
11 13
 	"github.com/docker/engine-api/types"
12 14
 )
13 15
 
... ...
@@ -43,7 +45,7 @@ func (m *mockCommand) Output() ([]byte, error) {
43 43
 		case validServerAddress:
44 44
 			return nil, nil
45 45
 		default:
46
-			return []byte("error erasing credentials"), errCommandExited
46
+			return []byte("program failed"), errCommandExited
47 47
 		}
48 48
 	case "get":
49 49
 		switch inS {
... ...
@@ -52,21 +54,21 @@ func (m *mockCommand) Output() ([]byte, error) {
52 52
 		case validServerAddress2:
53 53
 			return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
54 54
 		case missingCredsAddress:
55
-			return []byte(errCredentialsNotFound.Error()), errCommandExited
55
+			return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited
56 56
 		case invalidServerAddress:
57
-			return []byte("error getting credentials"), errCommandExited
57
+			return []byte("program failed"), errCommandExited
58 58
 		}
59 59
 	case "store":
60
-		var c credentialsRequest
60
+		var c credentials.Credentials
61 61
 		err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
62 62
 		if err != nil {
63
-			return []byte("error storing credentials"), errCommandExited
63
+			return []byte("program failed"), errCommandExited
64 64
 		}
65 65
 		switch c.ServerURL {
66 66
 		case validServerAddress:
67 67
 			return nil, nil
68 68
 		default:
69
-			return []byte("error storing credentials"), errCommandExited
69
+			return []byte("program failed"), errCommandExited
70 70
 		}
71 71
 	}
72 72
 
... ...
@@ -78,7 +80,7 @@ func (m *mockCommand) Input(in io.Reader) {
78 78
 	m.input = in
79 79
 }
80 80
 
81
-func mockCommandFn(args ...string) command {
81
+func mockCommandFn(args ...string) client.Program {
82 82
 	return &mockCommand{
83 83
 		arg: args[0],
84 84
 	}
... ...
@@ -89,8 +91,8 @@ func TestNativeStoreAddCredentials(t *testing.T) {
89 89
 	f.CredentialsStore = "mock"
90 90
 
91 91
 	s := &nativeStore{
92
-		commandFn: mockCommandFn,
93
-		fileStore: NewFileStore(f),
92
+		programFunc: mockCommandFn,
93
+		fileStore:   NewFileStore(f),
94 94
 	}
95 95
 	err := s.Store(types.AuthConfig{
96 96
 		Username:      "foo",
... ...
@@ -133,8 +135,8 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) {
133 133
 	f.CredentialsStore = "mock"
134 134
 
135 135
 	s := &nativeStore{
136
-		commandFn: mockCommandFn,
137
-		fileStore: NewFileStore(f),
136
+		programFunc: mockCommandFn,
137
+		fileStore:   NewFileStore(f),
138 138
 	}
139 139
 	err := s.Store(types.AuthConfig{
140 140
 		Username:      "foo",
... ...
@@ -147,8 +149,8 @@ func TestNativeStoreAddInvalidCredentials(t *testing.T) {
147 147
 		t.Fatal("expected error, got nil")
148 148
 	}
149 149
 
150
-	if err.Error() != "error storing credentials" {
151
-		t.Fatalf("expected `error storing credentials`, got %v", err)
150
+	if !strings.Contains(err.Error(), "program failed") {
151
+		t.Fatalf("expected `program failed`, got %v", err)
152 152
 	}
153 153
 
154 154
 	if len(f.AuthConfigs) != 0 {
... ...
@@ -165,8 +167,8 @@ func TestNativeStoreGet(t *testing.T) {
165 165
 	f.CredentialsStore = "mock"
166 166
 
167 167
 	s := &nativeStore{
168
-		commandFn: mockCommandFn,
169
-		fileStore: NewFileStore(f),
168
+		programFunc: mockCommandFn,
169
+		fileStore:   NewFileStore(f),
170 170
 	}
171 171
 	a, err := s.Get(validServerAddress)
172 172
 	if err != nil {
... ...
@@ -196,8 +198,8 @@ func TestNativeStoreGetIdentityToken(t *testing.T) {
196 196
 	f.CredentialsStore = "mock"
197 197
 
198 198
 	s := &nativeStore{
199
-		commandFn: mockCommandFn,
200
-		fileStore: NewFileStore(f),
199
+		programFunc: mockCommandFn,
200
+		fileStore:   NewFileStore(f),
201 201
 	}
202 202
 	a, err := s.Get(validServerAddress2)
203 203
 	if err != nil {
... ...
@@ -230,8 +232,8 @@ func TestNativeStoreGetAll(t *testing.T) {
230 230
 	f.CredentialsStore = "mock"
231 231
 
232 232
 	s := &nativeStore{
233
-		commandFn: mockCommandFn,
234
-		fileStore: NewFileStore(f),
233
+		programFunc: mockCommandFn,
234
+		fileStore:   NewFileStore(f),
235 235
 	}
236 236
 	as, err := s.GetAll()
237 237
 	if err != nil {
... ...
@@ -277,8 +279,8 @@ func TestNativeStoreGetMissingCredentials(t *testing.T) {
277 277
 	f.CredentialsStore = "mock"
278 278
 
279 279
 	s := &nativeStore{
280
-		commandFn: mockCommandFn,
281
-		fileStore: NewFileStore(f),
280
+		programFunc: mockCommandFn,
281
+		fileStore:   NewFileStore(f),
282 282
 	}
283 283
 	_, err := s.Get(missingCredsAddress)
284 284
 	if err != nil {
... ...
@@ -296,16 +298,16 @@ func TestNativeStoreGetInvalidAddress(t *testing.T) {
296 296
 	f.CredentialsStore = "mock"
297 297
 
298 298
 	s := &nativeStore{
299
-		commandFn: mockCommandFn,
300
-		fileStore: NewFileStore(f),
299
+		programFunc: mockCommandFn,
300
+		fileStore:   NewFileStore(f),
301 301
 	}
302 302
 	_, err := s.Get(invalidServerAddress)
303 303
 	if err == nil {
304 304
 		t.Fatal("expected error, got nil")
305 305
 	}
306 306
 
307
-	if err.Error() != "error getting credentials" {
308
-		t.Fatalf("expected `error getting credentials`, got %v", err)
307
+	if !strings.Contains(err.Error(), "program failed") {
308
+		t.Fatalf("expected `program failed`, got %v", err)
309 309
 	}
310 310
 }
311 311
 
... ...
@@ -318,8 +320,8 @@ func TestNativeStoreErase(t *testing.T) {
318 318
 	f.CredentialsStore = "mock"
319 319
 
320 320
 	s := &nativeStore{
321
-		commandFn: mockCommandFn,
322
-		fileStore: NewFileStore(f),
321
+		programFunc: mockCommandFn,
322
+		fileStore:   NewFileStore(f),
323 323
 	}
324 324
 	err := s.Erase(validServerAddress)
325 325
 	if err != nil {
... ...
@@ -340,15 +342,15 @@ func TestNativeStoreEraseInvalidAddress(t *testing.T) {
340 340
 	f.CredentialsStore = "mock"
341 341
 
342 342
 	s := &nativeStore{
343
-		commandFn: mockCommandFn,
344
-		fileStore: NewFileStore(f),
343
+		programFunc: mockCommandFn,
344
+		fileStore:   NewFileStore(f),
345 345
 	}
346 346
 	err := s.Erase(invalidServerAddress)
347 347
 	if err == nil {
348 348
 		t.Fatal("expected error, got nil")
349 349
 	}
350 350
 
351
-	if err.Error() != "error erasing credentials" {
352
-		t.Fatalf("expected `error erasing credentials`, got %v", err)
351
+	if !strings.Contains(err.Error(), "program failed") {
352
+		t.Fatalf("expected `program failed`, got %v", err)
353 353
 	}
354 354
 }
355 355
deleted file mode 100644
... ...
@@ -1,28 +0,0 @@
1
-package credentials
2
-
3
-import (
4
-	"io"
5
-	"os/exec"
6
-)
7
-
8
-func shellCommandFn(storeName string) func(args ...string) command {
9
-	name := remoteCredentialsPrefix + storeName
10
-	return func(args ...string) command {
11
-		return &shell{cmd: exec.Command(name, args...)}
12
-	}
13
-}
14
-
15
-// shell invokes shell commands to talk with a remote credentials helper.
16
-type shell struct {
17
-	cmd *exec.Cmd
18
-}
19
-
20
-// Output returns responses from the remote credentials helper.
21
-func (s *shell) Output() ([]byte, error) {
22
-	return s.cmd.Output()
23
-}
24
-
25
-// Input sets the input to send to a remote credentials helper.
26
-func (s *shell) Input(in io.Reader) {
27
-	s.cmd.Stdin = in
28
-}