Signed-off-by: Jake Sanders <jsand@google.com>
| ... | ... |
@@ -58,17 +58,29 @@ func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
| 58 | 58 |
|
| 59 | 59 |
// GetAll retrieves all the credentials from the native store. |
| 60 | 60 |
func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
|
| 61 |
- auths, _ := c.fileStore.GetAll() |
|
| 61 |
+ auths, err := c.listCredentialsInStore() |
|
| 62 |
+ if err != nil {
|
|
| 63 |
+ return nil, err |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ // Emails are only stored in the file store. |
|
| 67 |
+ // This call can be safely eliminated when emails are removed. |
|
| 68 |
+ fileConfigs, _ := c.fileStore.GetAll() |
|
| 62 | 69 |
|
| 63 |
- for s, ac := range auths {
|
|
| 64 |
- creds, _ := c.getCredentialsFromStore(s) |
|
| 70 |
+ authConfigs := make(map[string]types.AuthConfig) |
|
| 71 |
+ for registry := range auths {
|
|
| 72 |
+ creds, err := c.getCredentialsFromStore(registry) |
|
| 73 |
+ if err != nil {
|
|
| 74 |
+ return nil, err |
|
| 75 |
+ } |
|
| 76 |
+ ac, _ := fileConfigs[registry] // might contain Email |
|
| 65 | 77 |
ac.Username = creds.Username |
| 66 | 78 |
ac.Password = creds.Password |
| 67 | 79 |
ac.IdentityToken = creds.IdentityToken |
| 68 |
- auths[s] = ac |
|
| 80 |
+ authConfigs[registry] = ac |
|
| 69 | 81 |
} |
| 70 | 82 |
|
| 71 |
- return auths, nil |
|
| 83 |
+ return authConfigs, nil |
|
| 72 | 84 |
} |
| 73 | 85 |
|
| 74 | 86 |
// Store saves the given credentials in the file store. |
| ... | ... |
@@ -124,3 +136,9 @@ func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthC |
| 124 | 124 |
ret.ServerAddress = serverAddress |
| 125 | 125 |
return ret, nil |
| 126 | 126 |
} |
| 127 |
+ |
|
| 128 |
+// listCredentialsInStore returns a listing of stored credentials as a map of |
|
| 129 |
+// URL -> username. |
|
| 130 |
+func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
|
|
| 131 |
+ return client.List(c.programFunc) |
|
| 132 |
+} |
| ... | ... |
@@ -70,6 +70,8 @@ func (m *mockCommand) Output() ([]byte, error) {
|
| 70 | 70 |
default: |
| 71 | 71 |
return []byte("program failed"), errCommandExited
|
| 72 | 72 |
} |
| 73 |
+ case "list": |
|
| 74 |
+ return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
|
|
| 73 | 75 |
} |
| 74 | 76 |
|
| 75 | 77 |
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
|
| ... | ... |
@@ -225,9 +227,6 @@ func TestNativeStoreGetAll(t *testing.T) {
|
| 225 | 225 |
validServerAddress: {
|
| 226 | 226 |
Email: "foo@example.com", |
| 227 | 227 |
}, |
| 228 |
- validServerAddress2: {
|
|
| 229 |
- Email: "foo@example2.com", |
|
| 230 |
- }, |
|
| 231 | 228 |
}) |
| 232 | 229 |
f.CredentialsStore = "mock" |
| 233 | 230 |
|
| ... | ... |
@@ -265,8 +264,8 @@ func TestNativeStoreGetAll(t *testing.T) {
|
| 265 | 265 |
if as[validServerAddress2].IdentityToken != "abcd1234" {
|
| 266 | 266 |
t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
|
| 267 | 267 |
} |
| 268 |
- if as[validServerAddress2].Email != "foo@example2.com" {
|
|
| 269 |
- t.Fatalf("expected email `foo@example2.com` for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
|
|
| 268 |
+ if as[validServerAddress2].Email != "" {
|
|
| 269 |
+ t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
|
|
| 270 | 270 |
} |
| 271 | 271 |
} |
| 272 | 272 |
|
| ... | ... |
@@ -137,7 +137,7 @@ clone git google.golang.org/api dc6d2353af16e2a2b0ff6986af051d473a4ed468 https:/ |
| 137 | 137 |
clone git google.golang.org/cloud dae7e3d993bc3812a2185af60552bb6b847e52a0 https://code.googlesource.com/gocloud |
| 138 | 138 |
|
| 139 | 139 |
# native credentials |
| 140 |
-clone git github.com/docker/docker-credential-helpers v0.3.0 |
|
| 140 |
+clone git github.com/docker/docker-credential-helpers f72c04f1d8e71959a6d103f808c50ccbad79b9fd |
|
| 141 | 141 |
|
| 142 | 142 |
# containerd |
| 143 | 143 |
clone git github.com/docker/containerd 2545227b0357eb55e369fa0072baef9ad91cdb69 |
| ... | ... |
@@ -2,29 +2,51 @@ |
| 2 | 2 |
|
| 3 | 3 |
set -e |
| 4 | 4 |
|
| 5 |
+listFile=shell_test_list.json |
|
| 6 |
+ |
|
| 5 | 7 |
case $1 in |
| 6 | 8 |
"store") |
| 7 | 9 |
in=$(</dev/stdin) |
| 8 |
- server=$(echo "$in" | jq --raw-output ".ServerURL" | sha1sum - | awk '{print $1}')
|
|
| 10 |
+ server=$(echo "$in" | jq --raw-output ".ServerURL") |
|
| 11 |
+ serverHash=$(echo "$server" | sha1sum - | awk '{print $1}')
|
|
| 9 | 12 |
|
| 10 | 13 |
username=$(echo "$in" | jq --raw-output ".Username") |
| 11 | 14 |
password=$(echo "$in" | jq --raw-output ".Secret") |
| 12 |
- echo "{ \"Username\": \"${username}\", \"Secret\": \"${password}\" }" > $TEMP/$server
|
|
| 15 |
+ echo "{ \"Username\": \"${username}\", \"Secret\": \"${password}\" }" > $TEMP/$serverHash
|
|
| 16 |
+ # add the server to the list file |
|
| 17 |
+ if [[ ! -f $TEMP/$listFile ]]; then |
|
| 18 |
+ echo "{ \"${server}\": \"${username}\" }" > $TEMP/$listFile
|
|
| 19 |
+ else |
|
| 20 |
+ list=$(<$TEMP/$listFile) |
|
| 21 |
+ echo "$list" | jq ". + {\"${server}\": \"${username}\"}" > $TEMP/$listFile
|
|
| 22 |
+ fi |
|
| 13 | 23 |
;; |
| 14 | 24 |
"get") |
| 15 | 25 |
in=$(</dev/stdin) |
| 16 |
- server=$(echo "$in" | sha1sum - | awk '{print $1}')
|
|
| 17 |
- if [[ ! -f $TEMP/$server ]]; then |
|
| 26 |
+ serverHash=$(echo "$in" | sha1sum - | awk '{print $1}')
|
|
| 27 |
+ if [[ ! -f $TEMP/$serverHash ]]; then |
|
| 18 | 28 |
echo "credentials not found in native keychain" |
| 19 | 29 |
exit 1 |
| 20 | 30 |
fi |
| 21 |
- payload=$(<$TEMP/$server) |
|
| 31 |
+ payload=$(<$TEMP/$serverHash) |
|
| 22 | 32 |
echo "$payload" |
| 23 | 33 |
;; |
| 24 | 34 |
"erase") |
| 25 | 35 |
in=$(</dev/stdin) |
| 26 |
- server=$(echo "$in" | sha1sum - | awk '{print $1}')
|
|
| 27 |
- rm -f $TEMP/$server |
|
| 36 |
+ serverHash=$(echo "$in" | sha1sum - | awk '{print $1}')
|
|
| 37 |
+ rm -f $TEMP/$serverHash |
|
| 38 |
+ |
|
| 39 |
+ # Remove the server from the list |
|
| 40 |
+ list=$(<$TEMP/$listFile) |
|
| 41 |
+ echo "$list" | jq "del(.\"${in}\")" > $TEMP/$listFile
|
|
| 42 |
+ ;; |
|
| 43 |
+ "list") |
|
| 44 |
+ if [[ ! -f $TEMP/$listFile ]]; then |
|
| 45 |
+ echo "{}"
|
|
| 46 |
+ else |
|
| 47 |
+ payload=$(<$TEMP/$listFile) |
|
| 48 |
+ echo "$payload" |
|
| 49 |
+ fi |
|
| 28 | 50 |
;; |
| 29 | 51 |
*) |
| 30 | 52 |
echo "unknown credential option" |
| ... | ... |
@@ -55,11 +55,10 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error |
| 55 | 55 |
return resp, nil |
| 56 | 56 |
} |
| 57 | 57 |
|
| 58 |
-// Erase executes a program to remove the server credentails from the native store. |
|
| 58 |
+// Erase executes a program to remove the server credentials from the native store. |
|
| 59 | 59 |
func Erase(program ProgramFunc, serverURL string) error {
|
| 60 | 60 |
cmd := program("erase")
|
| 61 | 61 |
cmd.Input(strings.NewReader(serverURL)) |
| 62 |
- |
|
| 63 | 62 |
out, err := cmd.Output() |
| 64 | 63 |
if err != nil {
|
| 65 | 64 |
t := strings.TrimSpace(string(out)) |
| ... | ... |
@@ -68,3 +67,21 @@ func Erase(program ProgramFunc, serverURL string) error {
|
| 68 | 68 |
|
| 69 | 69 |
return nil |
| 70 | 70 |
} |
| 71 |
+ |
|
| 72 |
+// List executes a program to list server credentials in the native store. |
|
| 73 |
+func List(program ProgramFunc) (map[string]string, error) {
|
|
| 74 |
+ cmd := program("list")
|
|
| 75 |
+ cmd.Input(strings.NewReader("unused"))
|
|
| 76 |
+ out, err := cmd.Output() |
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ t := strings.TrimSpace(string(out)) |
|
| 79 |
+ return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t)
|
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ var resp map[string]string |
|
| 83 |
+ if err = json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
|
|
| 84 |
+ return nil, err |
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ return resp, nil |
|
| 88 |
+} |
| ... | ... |
@@ -25,7 +25,7 @@ type Credentials struct {
|
| 25 | 25 |
func Serve(helper Helper) {
|
| 26 | 26 |
var err error |
| 27 | 27 |
if len(os.Args) != 2 {
|
| 28 |
- err = fmt.Errorf("Usage: %s <store|get|erase>", os.Args[0])
|
|
| 28 |
+ err = fmt.Errorf("Usage: %s <store|get|erase|list>", os.Args[0])
|
|
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 | 31 |
if err == nil {
|
| ... | ... |
@@ -47,6 +47,8 @@ func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error |
| 47 | 47 |
return Get(helper, in, out) |
| 48 | 48 |
case "erase": |
| 49 | 49 |
return Erase(helper, in) |
| 50 |
+ case "list": |
|
| 51 |
+ return List(helper, out) |
|
| 50 | 52 |
} |
| 51 | 53 |
return fmt.Errorf("Unknown credential action `%s`", key)
|
| 52 | 54 |
} |
| ... | ... |
@@ -127,3 +129,13 @@ func Erase(helper Helper, reader io.Reader) error {
|
| 127 | 127 |
|
| 128 | 128 |
return helper.Delete(serverURL) |
| 129 | 129 |
} |
| 130 |
+ |
|
| 131 |
+//List returns all the serverURLs of keys in |
|
| 132 |
+//the OS store as a list of strings |
|
| 133 |
+func List(helper Helper, writer io.Writer) error {
|
|
| 134 |
+ accts, err := helper.List() |
|
| 135 |
+ if err != nil {
|
|
| 136 |
+ return err |
|
| 137 |
+ } |
|
| 138 |
+ return json.NewEncoder(writer).Encode(accts) |
|
| 139 |
+} |
| ... | ... |
@@ -9,4 +9,6 @@ type Helper interface {
|
| 9 | 9 |
// Get retrieves credentials from the store. |
| 10 | 10 |
// It returns username and secret as strings. |
| 11 | 11 |
Get(serverURL string) (string, string, error) |
| 12 |
+ // List returns the stored serverURLs and their associated usernames. |
|
| 13 |
+ List() (map[string]string, error) |
|
| 12 | 14 |
} |