Skip to content

Commit 6215716

Browse files
authored
Merge pull request #13 from ernoaapa/add-support-for-usernames
Added --user flag to support fetching users public keys
2 parents 230e330 + a46e9ab commit 6215716

File tree

6 files changed

+145
-39
lines changed

6 files changed

+145
-39
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ fetch-ssh-keys <source name> <parameters> <output file>
1414

1515
For example fetch users public SSH keys from GitHub `my-lovely-team` team in `my-awesome-company` organization and output in SSH authorized_keys format
1616
```shell
17+
# Fetch 'my-lovely-team' keys in 'my-awesome-company' organisation
1718
fetch-ssh-keys github --organization my-awesome-company --team my-lovely-team --token YOUR-TOKEN-HERE ./the-keys
19+
20+
# Fetch 'ernoaapa' and 'arnested' public keys
21+
fetch-ssh-keys github --user ernoaapa --user arnested --token YOUR-TOKEN-HERE ./the-keys
1822
```
1923

2024
Tool can be used for example to automatically update `.ssh/authorized_keys` file by giving path to `.ssh/authorized_keys` as last argument and adding the script to cron job.
@@ -30,12 +34,15 @@ Tool can be used for example to automatically update `.ssh/authorized_keys` file
3034
| --file-mode | No (default 0600) | File permissions when writing to a file |
3135

3236
#### GitHub
33-
| Parameter | Required | Description |
34-
|----------------|----------|-----------------------------------------------------------------------------------------------------------|
35-
| --organization | Yes | Name of the organization which members keys to pick |
36-
| --team | No | Name of the team which members keys to pick |
37-
| --token | No | GitHub API token to use for communication. Without token you get only public members of the organization. |
38-
| --public-only | No | Return only members what are publicly members of the given organization |
37+
| Parameter | Description |
38+
|----------------|-----------------------------------------------------------------------------------------------------------|
39+
| --organization | Name of the organization which members keys to pick |
40+
| --team | Name of the team which members keys to pick |
41+
| --user | Name of the user which keys to pick |
42+
| --token | GitHub API token to use for communication. Without token you get only public members of the organization. |
43+
| --public-only | Return only members what are publicly members of the given organization |
44+
45+
You can give `--organisation` (optionally combined with `--team` flag) and/or one or more `--user` flags.
3946

4047
## Development
4148
### Get dependencies

fetch/github.go

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,33 @@ type GithubFetchParams struct {
1717
PublicMembersOnly bool
1818
}
1919

20-
// GitHubKeys fetches organization users public SSH key from GitHub
21-
func GitHubKeys(organizationName string, params GithubFetchParams) (map[string][]string, error) {
22-
ctx := context.Background()
23-
24-
client := getClient(params)
20+
// GitHubOrganisationKeys fetches organization users public SSH key from GitHub
21+
func GitHubOrganisationKeys(organizationName string, params GithubFetchParams) (map[string][]string, error) {
22+
client := getClient(params.Token)
2523
users, err := fetchUsers(client, organizationName, params)
2624
if err != nil {
2725
return map[string][]string{}, err
2826
}
2927
log.Debugf("Users found: %d", len(users))
3028

31-
result := map[string][]string{}
29+
usernames := []string{}
3230
for _, user := range users {
33-
username := *user.Login
34-
keys, _, err := client.Users.ListKeys(ctx, username, &github.ListOptions{})
35-
if err != nil {
36-
return map[string][]string{}, err
37-
}
38-
39-
result[username] = make([]string, len(keys))
40-
41-
for index, key := range keys {
42-
result[username][index] = *key.Key
43-
}
31+
usernames = append(usernames, *user.Login)
4432
}
4533

46-
return result, nil
34+
return fetchUserKeys(client, usernames, params.Token)
35+
}
36+
37+
// GitHubUsers fetches users public SSH keys from GitHub
38+
func GitHubUsers(usernames []string, token string) (map[string][]string, error) {
39+
client := getClient(token)
40+
return fetchUserKeys(client, usernames, token)
4741
}
4842

49-
func getClient(params GithubFetchParams) *github.Client {
50-
if len(params.Token) > 0 {
43+
func getClient(token string) *github.Client {
44+
if len(token) > 0 {
5145
ts := oauth2.StaticTokenSource(
52-
&oauth2.Token{AccessToken: params.Token},
46+
&oauth2.Token{AccessToken: token},
5347
)
5448
return github.NewClient(oauth2.NewClient(oauth2.NoContext, ts))
5549
}
@@ -100,3 +94,23 @@ func resolveTeamID(client *github.Client, organizationName, teamName string) (in
10094

10195
return -1, fmt.Errorf("Unable to find team [%s] from organization [%s]", teamName, organizationName)
10296
}
97+
98+
func fetchUserKeys(client *github.Client, usernames []string, token string) (map[string][]string, error) {
99+
ctx := context.Background()
100+
101+
result := map[string][]string{}
102+
for _, username := range usernames {
103+
keys, _, err := client.Users.ListKeys(ctx, username, &github.ListOptions{})
104+
if err != nil {
105+
return map[string][]string{}, err
106+
}
107+
108+
result[username] = make([]string, len(keys))
109+
110+
for index, key := range keys {
111+
result[username][index] = *key.Key
112+
}
113+
}
114+
115+
return result, nil
116+
}

fetch/github_test.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"github.com/stretchr/testify/assert"
1010
)
1111

12-
func TestFetchPublicKeys(t *testing.T) {
12+
func TestFetchOrganisationKeys(t *testing.T) {
1313
log.SetLevel(log.DebugLevel)
1414

15-
keys, err := GitHubKeys("devopsfinland", GithubFetchParams{
15+
keys, err := GitHubOrganisationKeys("devopsfinland", GithubFetchParams{
1616
// Use token if it's available to avoid hitting API rate limits with the tests...
1717
Token: os.Getenv("GITHUB_TOKEN"),
1818
PublicMembersOnly: true,
@@ -23,3 +23,14 @@ func TestFetchPublicKeys(t *testing.T) {
2323
assert.True(t, len(keys["ernoaapa"]) > 0, "should return ernoaapa public SSH key")
2424
assert.True(t, len(keys["ernoaapa"][0]) > 0, "should not return empty key for ernoaapa")
2525
}
26+
27+
func TestFetchUserKeys(t *testing.T) {
28+
log.SetLevel(log.DebugLevel)
29+
30+
keys, err := GitHubUsers([]string{"ernoaapa", "arnested"}, os.Getenv("GITHUB_TOKEN"))
31+
32+
assert.NoError(t, err, "Fetch GitHub keys returned error")
33+
assert.Equal(t, 2, len(keys), "should return SSH keys for both users")
34+
assert.True(t, len(keys["ernoaapa"]) > 0, "should return ernoaapa public SSH key")
35+
assert.True(t, len(keys["ernoaapa"][0]) > 0, "should not return empty key for ernoaapa")
36+
}

main.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package main
22

33
import (
4+
"fmt"
45
"os"
56

67
log "github.com/Sirupsen/logrus"
8+
"github.com/pkg/errors"
79

810
"github.com/ernoaapa/fetch-ssh-keys/fetch"
911
"github.com/ernoaapa/fetch-ssh-keys/output"
12+
"github.com/ernoaapa/fetch-ssh-keys/utils"
1013
"github.com/urfave/cli"
1114
)
1215

@@ -55,22 +58,52 @@ func main() {
5558
Name: "team",
5659
Usage: "Return only members of `TEAM` (this option can be used multiple times for multiple teams)",
5760
},
61+
cli.StringSliceFlag{
62+
Name: "user",
63+
Usage: "Return given user public ssh keys (this option can be used multiple times for multiple users)",
64+
},
5865
},
5966
Action: func(c *cli.Context) error {
60-
if c.String("organization") == "" {
61-
log.Fatalln("You must give --organization value")
67+
var (
68+
token = c.String("token")
69+
organisation = c.String("organization")
70+
teams = c.StringSlice("team")
71+
users = c.StringSlice("user")
72+
publicOnly = c.Bool("public-only")
73+
74+
orgKeys map[string][]string
75+
userKeys map[string][]string
76+
77+
target = c.Args().Get(0)
78+
fileMode = os.FileMode(c.GlobalInt("file-mode"))
79+
format = c.GlobalString("format")
80+
81+
err error
82+
)
83+
84+
if organisation == "" && len(users) == 0 {
85+
return fmt.Errorf("You must give either --organisation or --user parameter")
86+
}
87+
88+
if c.IsSet("organization") {
89+
orgKeys, err = fetch.GitHubOrganisationKeys(organisation, fetch.GithubFetchParams{
90+
Token: token,
91+
TeamNames: teams,
92+
PublicMembersOnly: publicOnly,
93+
})
94+
if err != nil {
95+
return errors.Wrapf(err, "Failed to fetch keys from organisation %s", organisation)
96+
}
6297
}
6398

64-
keys, err := fetch.GitHubKeys(c.String("organization"), fetch.GithubFetchParams{
65-
Token: c.String("token"),
66-
TeamNames: c.StringSlice("team"),
67-
PublicMembersOnly: c.Bool("public-only"),
68-
})
69-
if err != nil {
70-
log.Fatalln("Failed to fetch keys", err)
99+
if c.IsSet("user") {
100+
userKeys, err = fetch.GitHubUsers(users, token)
101+
if err != nil {
102+
return errors.Wrap(err, "Failed to fetch GitHub user(s) keys")
103+
}
71104
}
72105

73-
return output.Write(c.GlobalString("format"), c.Args().Get(0), os.FileMode(c.GlobalInt("file-mode")), keys)
106+
return output.Write(format, target, fileMode, utils.MergeKeys(orgKeys, userKeys))
74107
},
75108
},
76109
}

utils/merge.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package utils
2+
3+
// MergeKeys merges key maps together to single map
4+
func MergeKeys(keySets ...map[string][]string) map[string][]string {
5+
result := make(map[string][]string)
6+
7+
for _, userKeys := range keySets {
8+
for username, keys := range userKeys {
9+
if _, ok := result[username]; !ok {
10+
result[username] = []string{}
11+
}
12+
13+
result[username] = append(result[username], keys...)
14+
}
15+
}
16+
17+
return result
18+
}

utils/merge_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package utils
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestMergeKeys(t *testing.T) {
10+
result := MergeKeys(map[string][]string{
11+
"user-1": []string{"key1"},
12+
"user-2": []string{"key1"},
13+
}, map[string][]string{
14+
"user-2": []string{"key2"},
15+
"user-3": []string{"key1"},
16+
})
17+
18+
assert.Equal(t, map[string][]string{
19+
"user-1": []string{"key1"},
20+
"user-2": []string{"key1", "key2"},
21+
"user-3": []string{"key1"},
22+
}, result)
23+
}

0 commit comments

Comments
 (0)