Skip to content

Commit 235ebf4

Browse files
authored
Merge pull request #13 from PRODYNA/10-external-collaborateuers
10 external collaborateuers
2 parents 1c3b014 + eedfb22 commit 235ebf4

File tree

10 files changed

+478
-177
lines changed

10 files changed

+478
-177
lines changed

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,35 @@ jobs:
4141

4242
# Run the deployment overview action
4343
- name: Github users
44-
uses: prodyna/github-users@v0.7
44+
uses: prodyna/github-users@v1.0
4545
with:
4646
# The action to run
47-
action: userlist
47+
action: members
4848
# The GitHub Enterprise to query for repositories
4949
enterprise: octocat
5050
# The GitHub Token to use for authentication
5151
github-token: ${{ secrets.GITHUB_TOKEN }}
5252
# The template file to use for rendering the result
53-
template-file: template/userlist.tpl
53+
template-file: template/members.tpl
5454
# The markdown file to write the result to
55-
markdown-file: USERS.md
55+
markdown-file: MEMBERS.md
56+
# Verbosity level, 0=info, 1=debug
57+
verbose: 1
58+
59+
# Run the deployment overview action
60+
- name: Github users
61+
uses: prodyna/[email protected]
62+
with:
63+
# The action to run
64+
action: collaborators
65+
# The GitHub Enterprise to query for repositories
66+
enterprise: octocat
67+
# The GitHub Token to use for authentication
68+
github-token: ${{ secrets.GITHUB_TOKEN }}
69+
# The template file to use for rendering the result
70+
template-file: template/collaborators.tpl
71+
# The markdown file to write the result to
72+
markdown-file: COLLABORATORS.md
5673
# Verbosity level, 0=info, 1=debug
5774
verbose: 1
5875

@@ -61,6 +78,6 @@ jobs:
6178
run: |
6279
git config --local user.email "[email protected]"
6380
git config --local user.name "Deployment Overview"
64-
git add profile
81+
git add MEMBERS.md COLLABORATORS.md
6582
git commit -m "Add/update deployment overview"
6683
```

action.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ inputs:
1414
template-file:
1515
description: 'The template file to use for rendering the result'
1616
required: false
17-
default: '/template/userlist.tpl'
17+
default: '/template/members.tpl'
1818
markdown-file:
1919
description: 'The markdown file to write the result to'
2020
required: false
@@ -25,7 +25,7 @@ inputs:
2525
default: 1
2626
runs:
2727
using: 'docker'
28-
image: 'docker://ghcr.io/prodyna/github-users:v0.7'
28+
image: 'docker://ghcr.io/prodyna/github-users:v1.0'
2929
env:
3030
ACTION: ${{ inputs.action }}
3131
ENTERPRISE: ${{ inputs.enterprise }}

config/config.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,17 @@ func New() (*Config, error) {
3030
flag.StringVar(&c.Action, keyAction, lookupEnvOrString("ACTION", ""), "The action to perform.")
3131
flag.StringVar(&c.Enterprise, keyEnterprise, lookupEnvOrString("ENTERPRISE", ""), "The GitHub Enterprise to query for repositories.")
3232
flag.StringVar(&c.GithubToken, keyGithubToken, lookupEnvOrString("GITHUB_TOKEN", ""), "The GitHub Token to use for authentication.")
33-
flag.StringVar(&c.TemplateFile, keyTemplateFile, lookupEnvOrString("TEMPLATE_FILE", "template/userlist.tpl"), "The template file to use for rendering the result.")
33+
flag.StringVar(&c.TemplateFile, keyTemplateFile, lookupEnvOrString("TEMPLATE_FILE", "template/members.tpl"), "The template file to use for rendering the result.")
3434
flag.StringVar(&c.MarkdownFile, keyMarkdownFile, lookupEnvOrString("MARKDOWN_FILE", "USERS.md"), "The markdown file to write the result to.")
3535
verbose := flag.Int("verbose", lookupEnvOrInt(keyVerbose, 0), "Verbosity level, 0=info, 1=debug. Overrides the environment variable VERBOSE.")
3636

37-
logLevel := &slog.LevelVar{}
38-
if verbose != nil {
39-
if *verbose == 0 {
40-
logLevel.Set(slog.LevelInfo)
41-
} else {
42-
logLevel.Set(slog.LevelDebug)
43-
}
37+
level := slog.LevelInfo
38+
if *verbose > 0 {
39+
level = slog.LevelDebug
4440
}
41+
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
42+
Level: level,
43+
})))
4544
flag.Parse()
4645
return &c, nil
4746
}

main.go

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package main
22

33
import (
4-
config2 "github.com/prodyna/github-users/config"
4+
config "github.com/prodyna/github-users/config"
55
"github.com/prodyna/github-users/userlist"
66
"log/slog"
77
"os"
@@ -22,42 +22,38 @@ type Config struct {
2222
}
2323

2424
func main() {
25-
config, err := config2.New()
25+
c, err := config.New()
2626
if err != nil {
2727
slog.Error("Unable to create config", "error", err)
2828
os.Exit(1)
2929
}
3030

31-
switch config.Action {
32-
case "userlist":
33-
ulc := userlist.New(
34-
userlist.WithEnterprise(config.Enterprise),
35-
userlist.WithGithubToken(config.GithubToken),
36-
userlist.WithTemplateFile(config.TemplateFile),
37-
userlist.WithMarkdownFile(config.MarkdownFile),
38-
)
39-
err := ulc.Validate()
40-
if err != nil {
41-
slog.Error("Invalid config", "error", err)
42-
os.Exit(1)
43-
}
44-
err = ulc.Load()
45-
if err != nil {
46-
slog.Error("Unable to load userlist", "error", err)
47-
os.Exit(1)
48-
}
49-
err = ulc.Print()
50-
if err != nil {
51-
slog.Error("Unable to print userlist", "error", err)
52-
os.Exit(1)
53-
}
54-
err = ulc.Render()
55-
if err != nil {
56-
slog.Error("Unable to render userlist", "error", err)
57-
os.Exit(1)
58-
}
59-
default:
60-
slog.Error("Unknown action", "action", config.Action)
31+
ulc := userlist.New(
32+
userlist.WithAction(c.Action),
33+
userlist.WithEnterprise(c.Enterprise),
34+
userlist.WithGithubToken(c.GithubToken),
35+
userlist.WithTemplateFile(c.TemplateFile),
36+
userlist.WithMarkdownFile(c.MarkdownFile),
37+
)
38+
39+
err = ulc.Validate()
40+
if err != nil {
41+
slog.Error("Invalid config", "error", err)
42+
os.Exit(1)
43+
}
44+
err = ulc.Load()
45+
if err != nil {
46+
slog.Error("Unable to load userlist", "error", err)
47+
os.Exit(1)
48+
}
49+
err = ulc.Print()
50+
if err != nil {
51+
slog.Error("Unable to print userlist", "error", err)
52+
os.Exit(1)
53+
}
54+
err = ulc.Render()
55+
if err != nil {
56+
slog.Error("Unable to render userlist", "error", err)
6157
os.Exit(1)
6258
}
6359
}

template/collaborators.tpl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# GitHub Enterprise collaborators for {{ .Enterprise.Name }}
2+
3+
Last updated: {{ .Updated }}
4+
5+
| Number | User | Contributions | Organization | Repository |
6+
| ------ | ---- | ------------- | ------------ | ---------- |
7+
{{ range $user := .Users }}{{ range $org := $user.Organizations }}{{ range $repo := $org.Repositories }}| {{ $user.Number }} | {{ $user.Login }} | {{ $user.Contributions }} | {{ $org.Name }} | {{ $repo.Name }} |
8+
{{ end }}{{ end }}{{ end }}
9+
10+
---
11+
Generated with :heart: by [github-users](https://github.com/prodyna/github-users)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# GitHub Enterprise Users for {{ .Enterprise.Name }}
1+
# GitHub Enterprise members for {{ .Enterprise.Name }}
22

33
Last updated: {{ .Updated }}
44

55
| # | GitHub Login | GitHub name | E-Mail | Contributions |
66
| --- | --- | --- | --- | --- |
7-
{{ range .Users }} | {{ .Number }} | [{{ .Login }}](https://github.com/enterprises/{{ $.Enterprise.Slug }}/people/{{ .Login }}/sso) | {{ .Name }} | {{ .Email }} | {{if .Contributions}}:green_square:{{else}}:red_square:{{end}} {{.Contributions }} |
7+
{{ range .Users }} | {{ .Number }} | [{{ .Login }}](https://github.com/enterprises/{{ $.Enterprise.Slug }}/people/{{ .Login }}/sso) | {{ .Name }} | {{ .Email }} | {{if .Contributions}}:green_square:{{else}}:red_square:{{end}} [{{.Contributions }}](https://github.com/{{ .Name }}) |
88
{{ end }}
99

1010
{{ if .Users }}_{{ len .Users }} users_{{ else }}No users found.{{ end }}

userlist/collaborators.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package userlist
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"github.com/shurcooL/githubv4"
8+
"golang.org/x/oauth2"
9+
"log/slog"
10+
"time"
11+
)
12+
13+
func (c *UserListConfig) loadCollaborators() error {
14+
slog.Info("Loading collaborators", "enterprise", c.enterprise)
15+
c.userList = UserList{
16+
// updated as RFC3339 string
17+
Updated: time.Now().Format(time.RFC3339),
18+
}
19+
ctx := context.Background()
20+
src := oauth2.StaticTokenSource(
21+
&oauth2.Token{AccessToken: c.githubToken},
22+
)
23+
httpClient := oauth2.NewClient(ctx, src)
24+
client := githubv4.NewClient(httpClient)
25+
26+
/*
27+
{
28+
enterprise(slug: "prodyna") {
29+
slug
30+
name
31+
organizations (first:100) {
32+
nodes {
33+
login
34+
name
35+
}
36+
}
37+
}
38+
}
39+
*/
40+
var organizations struct {
41+
Enterprise struct {
42+
Slug string
43+
Name string
44+
Organizations struct {
45+
Nodes []struct {
46+
Login string
47+
Name string
48+
}
49+
} `graphql:"organizations(first:100)"`
50+
} `graphql:"enterprise(slug: $slug)"`
51+
}
52+
53+
variables := map[string]interface{}{
54+
"slug": githubv4.String(c.enterprise),
55+
}
56+
57+
slog.Info("Loading organizations", "enterprise", c.enterprise)
58+
err := client.Query(ctx, &organizations, variables)
59+
if err != nil {
60+
slog.ErrorContext(ctx, "Unable to query", "error", err)
61+
return err
62+
}
63+
slog.Info("Loaded organizations", "organization.count", len(organizations.Enterprise.Organizations.Nodes))
64+
65+
/*
66+
{
67+
organization(login:"prodyna") {
68+
repositories(first:100) {
69+
pageInfo {
70+
hasNextPage
71+
startCursor
72+
}
73+
nodes {
74+
name
75+
collaborators(first:100,affiliation:OUTSIDE) {
76+
pageInfo {
77+
hasNextPage
78+
startCursor
79+
}
80+
nodes {
81+
login
82+
name
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}
89+
*/
90+
c.userList.Enterprise.Slug = organizations.Enterprise.Slug
91+
c.userList.Enterprise.Name = organizations.Enterprise.Name
92+
93+
userNumber := 0
94+
slog.Info("Iterating organizatons", "organization.count", len(organizations.Enterprise.Organizations.Nodes))
95+
for _, org := range organizations.Enterprise.Organizations.Nodes {
96+
if org.Login != "PRODYNA" {
97+
continue
98+
}
99+
slog.Info("Loading repositories and external collaborators", "organization", org.Login)
100+
var query struct {
101+
Organization struct {
102+
Repositories struct {
103+
Nodes []struct {
104+
Name string
105+
Collaborators struct {
106+
Nodes []struct {
107+
Login string
108+
Name string
109+
ContributionsCollection struct {
110+
ContributionCalendar struct {
111+
TotalContributions int
112+
}
113+
}
114+
}
115+
} `graphql:"collaborators(first:100,affiliation:OUTSIDE)"`
116+
}
117+
} `graphql:"repositories(first:100)"`
118+
} `graphql:"organization(login: $organization)"`
119+
}
120+
121+
variables := map[string]interface{}{
122+
"organization": githubv4.String(org.Login),
123+
}
124+
125+
err := client.Query(ctx, &query, variables)
126+
if err != nil {
127+
slog.WarnContext(ctx, "Unable to query - will skip this organization", "error", err, "organization", org.Login)
128+
continue
129+
}
130+
131+
// count the collaborators
132+
collaboratorCount := 0
133+
for _, repo := range query.Organization.Repositories.Nodes {
134+
collaboratorCount += len(repo.Collaborators.Nodes)
135+
}
136+
if collaboratorCount == 0 {
137+
slog.DebugContext(ctx, "No collaborators found", "organization", org.Login)
138+
continue
139+
}
140+
141+
for _, repo := range query.Organization.Repositories.Nodes {
142+
slog.DebugContext(ctx, "Processing repository", "repository", repo.Name, "collaborator.count", len(repo.Collaborators.Nodes))
143+
for _, collaborator := range repo.Collaborators.Nodes {
144+
slog.DebugContext(ctx, "Processing collaborator", "login", collaborator.Login, "name", collaborator.Name, "contributions", collaborator.ContributionsCollection.ContributionCalendar.TotalContributions)
145+
user := c.userList.findUser(collaborator.Login)
146+
if user == nil {
147+
user = c.userList.createUser(userNumber+1, collaborator.Login, collaborator.Name, "", collaborator.ContributionsCollection.ContributionCalendar.TotalContributions)
148+
userNumber++
149+
} else {
150+
slog.Info("Found existing user", "login", user.Login)
151+
}
152+
organization := Organization{
153+
Name: org.Name,
154+
Repositories: new([]Repository),
155+
}
156+
user.upsertOrganization(organization)
157+
repository := Repository{
158+
Name: repo.Name,
159+
}
160+
organization.upsertRepository(repository)
161+
}
162+
}
163+
164+
slog.InfoContext(ctx, "Loaded repositories",
165+
"repository.count", len(query.Organization.Repositories.Nodes),
166+
"organization", org.Login,
167+
"collaborator.count", collaboratorCount)
168+
169+
if collaboratorCount == 0 {
170+
continue
171+
}
172+
173+
output, err := json.MarshalIndent(c.userList, "", " ")
174+
if err != nil {
175+
slog.ErrorContext(ctx, "Unable to marshal json", "error", err)
176+
return err
177+
}
178+
fmt.Printf("%s\n", output)
179+
180+
slog.InfoContext(ctx, "Adding collaborators", "organization", org.Login)
181+
}
182+
183+
c.loaded = true
184+
return nil
185+
}

0 commit comments

Comments
 (0)