Skip to content

Commit

Permalink
Merge pull request #13 from PRODYNA/10-external-collaborateuers
Browse files Browse the repository at this point in the history
10 external collaborateuers
  • Loading branch information
dkrizic authored Mar 13, 2024
2 parents 1c3b014 + eedfb22 commit 235ebf4
Show file tree
Hide file tree
Showing 10 changed files with 478 additions and 177 deletions.
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,35 @@ jobs:

# Run the deployment overview action
- name: Github users
uses: prodyna/github-users@v0.7
uses: prodyna/github-users@v1.0
with:
# The action to run
action: userlist
action: members
# The GitHub Enterprise to query for repositories
enterprise: octocat
# The GitHub Token to use for authentication
github-token: ${{ secrets.GITHUB_TOKEN }}
# The template file to use for rendering the result
template-file: template/userlist.tpl
template-file: template/members.tpl
# The markdown file to write the result to
markdown-file: USERS.md
markdown-file: MEMBERS.md
# Verbosity level, 0=info, 1=debug
verbose: 1

# Run the deployment overview action
- name: Github users
uses: prodyna/[email protected]
with:
# The action to run
action: collaborators
# The GitHub Enterprise to query for repositories
enterprise: octocat
# The GitHub Token to use for authentication
github-token: ${{ secrets.GITHUB_TOKEN }}
# The template file to use for rendering the result
template-file: template/collaborators.tpl
# The markdown file to write the result to
markdown-file: COLLABORATORS.md
# Verbosity level, 0=info, 1=debug
verbose: 1

Expand All @@ -61,6 +78,6 @@ jobs:
run: |
git config --local user.email "[email protected]"
git config --local user.name "Deployment Overview"
git add profile
git add MEMBERS.md COLLABORATORS.md
git commit -m "Add/update deployment overview"
```
4 changes: 2 additions & 2 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ inputs:
template-file:
description: 'The template file to use for rendering the result'
required: false
default: '/template/userlist.tpl'
default: '/template/members.tpl'
markdown-file:
description: 'The markdown file to write the result to'
required: false
Expand All @@ -25,7 +25,7 @@ inputs:
default: 1
runs:
using: 'docker'
image: 'docker://ghcr.io/prodyna/github-users:v0.7'
image: 'docker://ghcr.io/prodyna/github-users:v1.0'
env:
ACTION: ${{ inputs.action }}
ENTERPRISE: ${{ inputs.enterprise }}
Expand Down
15 changes: 7 additions & 8 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,17 @@ func New() (*Config, error) {
flag.StringVar(&c.Action, keyAction, lookupEnvOrString("ACTION", ""), "The action to perform.")
flag.StringVar(&c.Enterprise, keyEnterprise, lookupEnvOrString("ENTERPRISE", ""), "The GitHub Enterprise to query for repositories.")
flag.StringVar(&c.GithubToken, keyGithubToken, lookupEnvOrString("GITHUB_TOKEN", ""), "The GitHub Token to use for authentication.")
flag.StringVar(&c.TemplateFile, keyTemplateFile, lookupEnvOrString("TEMPLATE_FILE", "template/userlist.tpl"), "The template file to use for rendering the result.")
flag.StringVar(&c.TemplateFile, keyTemplateFile, lookupEnvOrString("TEMPLATE_FILE", "template/members.tpl"), "The template file to use for rendering the result.")
flag.StringVar(&c.MarkdownFile, keyMarkdownFile, lookupEnvOrString("MARKDOWN_FILE", "USERS.md"), "The markdown file to write the result to.")
verbose := flag.Int("verbose", lookupEnvOrInt(keyVerbose, 0), "Verbosity level, 0=info, 1=debug. Overrides the environment variable VERBOSE.")

logLevel := &slog.LevelVar{}
if verbose != nil {
if *verbose == 0 {
logLevel.Set(slog.LevelInfo)
} else {
logLevel.Set(slog.LevelDebug)
}
level := slog.LevelInfo
if *verbose > 0 {
level = slog.LevelDebug
}
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: level,
})))
flag.Parse()
return &c, nil
}
Expand Down
60 changes: 28 additions & 32 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
config2 "github.com/prodyna/github-users/config"
config "github.com/prodyna/github-users/config"
"github.com/prodyna/github-users/userlist"
"log/slog"
"os"
Expand All @@ -22,42 +22,38 @@ type Config struct {
}

func main() {
config, err := config2.New()
c, err := config.New()
if err != nil {
slog.Error("Unable to create config", "error", err)
os.Exit(1)
}

switch config.Action {
case "userlist":
ulc := userlist.New(
userlist.WithEnterprise(config.Enterprise),
userlist.WithGithubToken(config.GithubToken),
userlist.WithTemplateFile(config.TemplateFile),
userlist.WithMarkdownFile(config.MarkdownFile),
)
err := ulc.Validate()
if err != nil {
slog.Error("Invalid config", "error", err)
os.Exit(1)
}
err = ulc.Load()
if err != nil {
slog.Error("Unable to load userlist", "error", err)
os.Exit(1)
}
err = ulc.Print()
if err != nil {
slog.Error("Unable to print userlist", "error", err)
os.Exit(1)
}
err = ulc.Render()
if err != nil {
slog.Error("Unable to render userlist", "error", err)
os.Exit(1)
}
default:
slog.Error("Unknown action", "action", config.Action)
ulc := userlist.New(
userlist.WithAction(c.Action),
userlist.WithEnterprise(c.Enterprise),
userlist.WithGithubToken(c.GithubToken),
userlist.WithTemplateFile(c.TemplateFile),
userlist.WithMarkdownFile(c.MarkdownFile),
)

err = ulc.Validate()
if err != nil {
slog.Error("Invalid config", "error", err)
os.Exit(1)
}
err = ulc.Load()
if err != nil {
slog.Error("Unable to load userlist", "error", err)
os.Exit(1)
}
err = ulc.Print()
if err != nil {
slog.Error("Unable to print userlist", "error", err)
os.Exit(1)
}
err = ulc.Render()
if err != nil {
slog.Error("Unable to render userlist", "error", err)
os.Exit(1)
}
}
11 changes: 11 additions & 0 deletions template/collaborators.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# GitHub Enterprise collaborators for {{ .Enterprise.Name }}

Last updated: {{ .Updated }}

| Number | User | Contributions | Organization | Repository |
| ------ | ---- | ------------- | ------------ | ---------- |
{{ range $user := .Users }}{{ range $org := $user.Organizations }}{{ range $repo := $org.Repositories }}| {{ $user.Number }} | {{ $user.Login }} | {{ $user.Contributions }} | {{ $org.Name }} | {{ $repo.Name }} |
{{ end }}{{ end }}{{ end }}

---
Generated with :heart: by [github-users](https://github.com/prodyna/github-users)
4 changes: 2 additions & 2 deletions template/userlist.tpl → template/members.tpl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# GitHub Enterprise Users for {{ .Enterprise.Name }}
# GitHub Enterprise members for {{ .Enterprise.Name }}

Last updated: {{ .Updated }}

| # | GitHub Login | GitHub name | E-Mail | Contributions |
| --- | --- | --- | --- | --- |
{{ range .Users }} | {{ .Number }} | [{{ .Login }}](https://github.com/enterprises/{{ $.Enterprise.Slug }}/people/{{ .Login }}/sso) | {{ .Name }} | {{ .Email }} | {{if .Contributions}}:green_square:{{else}}:red_square:{{end}} {{.Contributions }} |
{{ 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 }}) |
{{ end }}

{{ if .Users }}_{{ len .Users }} users_{{ else }}No users found.{{ end }}
Expand Down
185 changes: 185 additions & 0 deletions userlist/collaborators.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package userlist

import (
"context"
"encoding/json"
"fmt"
"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
"log/slog"
"time"
)

func (c *UserListConfig) loadCollaborators() error {
slog.Info("Loading collaborators", "enterprise", c.enterprise)
c.userList = UserList{
// updated as RFC3339 string
Updated: time.Now().Format(time.RFC3339),
}
ctx := context.Background()
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: c.githubToken},
)
httpClient := oauth2.NewClient(ctx, src)
client := githubv4.NewClient(httpClient)

/*
{
enterprise(slug: "prodyna") {
slug
name
organizations (first:100) {
nodes {
login
name
}
}
}
}
*/
var organizations struct {
Enterprise struct {
Slug string
Name string
Organizations struct {
Nodes []struct {
Login string
Name string
}
} `graphql:"organizations(first:100)"`
} `graphql:"enterprise(slug: $slug)"`
}

variables := map[string]interface{}{
"slug": githubv4.String(c.enterprise),
}

slog.Info("Loading organizations", "enterprise", c.enterprise)
err := client.Query(ctx, &organizations, variables)
if err != nil {
slog.ErrorContext(ctx, "Unable to query", "error", err)
return err
}
slog.Info("Loaded organizations", "organization.count", len(organizations.Enterprise.Organizations.Nodes))

/*
{
organization(login:"prodyna") {
repositories(first:100) {
pageInfo {
hasNextPage
startCursor
}
nodes {
name
collaborators(first:100,affiliation:OUTSIDE) {
pageInfo {
hasNextPage
startCursor
}
nodes {
login
name
}
}
}
}
}
}
*/
c.userList.Enterprise.Slug = organizations.Enterprise.Slug
c.userList.Enterprise.Name = organizations.Enterprise.Name

userNumber := 0
slog.Info("Iterating organizatons", "organization.count", len(organizations.Enterprise.Organizations.Nodes))
for _, org := range organizations.Enterprise.Organizations.Nodes {
if org.Login != "PRODYNA" {
continue
}
slog.Info("Loading repositories and external collaborators", "organization", org.Login)
var query struct {
Organization struct {
Repositories struct {
Nodes []struct {
Name string
Collaborators struct {
Nodes []struct {
Login string
Name string
ContributionsCollection struct {
ContributionCalendar struct {
TotalContributions int
}
}
}
} `graphql:"collaborators(first:100,affiliation:OUTSIDE)"`
}
} `graphql:"repositories(first:100)"`
} `graphql:"organization(login: $organization)"`
}

variables := map[string]interface{}{
"organization": githubv4.String(org.Login),
}

err := client.Query(ctx, &query, variables)
if err != nil {
slog.WarnContext(ctx, "Unable to query - will skip this organization", "error", err, "organization", org.Login)
continue
}

// count the collaborators
collaboratorCount := 0
for _, repo := range query.Organization.Repositories.Nodes {
collaboratorCount += len(repo.Collaborators.Nodes)
}
if collaboratorCount == 0 {
slog.DebugContext(ctx, "No collaborators found", "organization", org.Login)
continue
}

for _, repo := range query.Organization.Repositories.Nodes {
slog.DebugContext(ctx, "Processing repository", "repository", repo.Name, "collaborator.count", len(repo.Collaborators.Nodes))
for _, collaborator := range repo.Collaborators.Nodes {
slog.DebugContext(ctx, "Processing collaborator", "login", collaborator.Login, "name", collaborator.Name, "contributions", collaborator.ContributionsCollection.ContributionCalendar.TotalContributions)
user := c.userList.findUser(collaborator.Login)
if user == nil {
user = c.userList.createUser(userNumber+1, collaborator.Login, collaborator.Name, "", collaborator.ContributionsCollection.ContributionCalendar.TotalContributions)
userNumber++
} else {
slog.Info("Found existing user", "login", user.Login)
}
organization := Organization{
Name: org.Name,
Repositories: new([]Repository),
}
user.upsertOrganization(organization)
repository := Repository{
Name: repo.Name,
}
organization.upsertRepository(repository)
}
}

slog.InfoContext(ctx, "Loaded repositories",
"repository.count", len(query.Organization.Repositories.Nodes),
"organization", org.Login,
"collaborator.count", collaboratorCount)

if collaboratorCount == 0 {
continue
}

output, err := json.MarshalIndent(c.userList, "", " ")
if err != nil {
slog.ErrorContext(ctx, "Unable to marshal json", "error", err)
return err
}
fmt.Printf("%s\n", output)

slog.InfoContext(ctx, "Adding collaborators", "organization", org.Login)
}

c.loaded = true
return nil
}
Loading

0 comments on commit 235ebf4

Please sign in to comment.