-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Label PR scaffolding (essentially dry run, without labeling for now) (#3
) * Scaffold command * Check PRs to label * Add an example config * Add option to skip users * Swap to separate embedded config struct for the label options, so they can be passed to the handler
- Loading branch information
1 parent
6f47ce3
commit d34bd9e
Showing
9 changed files
with
252 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ main | |
bin/ | ||
.idea/* | ||
*.log | ||
config.yml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package cmd | ||
|
||
import ( | ||
"log" | ||
|
||
"github.com/google/go-github/v60/github" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
|
||
"github.com/chia-network/github-bot/internal/config" | ||
"github.com/chia-network/github-bot/internal/label" | ||
) | ||
|
||
// labelPRsCmd represents the labelPRs command | ||
var labelPRsCmd = &cobra.Command{ | ||
Use: "label-prs", | ||
Short: "Adds community and internal labels to pull requests in designated repos", | ||
Run: func(cmd *cobra.Command, args []string) { | ||
cfg, err := config.LoadConfig(viper.GetString("config")) | ||
if err != nil { | ||
log.Fatalf("error loading config: %s\n", err.Error()) | ||
} | ||
client := github.NewClient(nil).WithAuthToken(cfg.GithubToken) | ||
err = label.PullRequests(client, cfg.InternalTeam, cfg.LabelConfig) | ||
if err != nil { | ||
log.Fatalln(err.Error()) | ||
} | ||
}, | ||
} | ||
|
||
func init() { | ||
rootCmd.AddCommand(labelPRsCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
github_token: ghp_abc123 | ||
|
||
# Shared Settings | ||
# Team that contains all "internal" members | ||
internal_team: "my-org/my-team" | ||
|
||
# If empty, internal label will not be added | ||
label_internal: "internal-pr" | ||
# If empty, external label will not be added | ||
label_external: "community-pr" | ||
# Repos to check for labeling | ||
label_check_repos: | ||
- name: "my-org/repo1" | ||
# Only PRs with a number higher than this value will be labeled | ||
minimum_number: 0 | ||
- name: "my-org/repo2" | ||
minimum_number: 1000 | ||
# PRs opened by these users will not be labeled | ||
label_skip_users: | ||
- "dependabot[bot]" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package config | ||
|
||
// Config defines the config for all aspects of the bot | ||
type Config struct { | ||
GithubToken string `yaml:"github_token"` | ||
InternalTeam string `yaml:"internal_team"` | ||
LabelConfig `yaml:",inline"` | ||
} | ||
|
||
// LabelConfig is the configuration options specific to labeling PRs | ||
type LabelConfig struct { | ||
LabelInternal string `yaml:"label_internal"` | ||
LabelExternal string `yaml:"label_external"` | ||
LabelCheckRepos []CheckRepo `yaml:"label_check_repos"` | ||
LabelSkipUsers []string `yaml:"label_skip_users"` | ||
LabelSkipMap map[string]bool | ||
} | ||
|
||
// CheckRepo is config settings when checking a repo | ||
type CheckRepo struct { | ||
Name string `yaml:"name"` | ||
MinimumNumber int `yaml:"minimum_number"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package config | ||
|
||
import ( | ||
"os" | ||
|
||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
// LoadConfig loads config from the given path | ||
func LoadConfig(path string) (*Config, error) { | ||
configBytes, err := os.ReadFile(path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
config := &Config{} | ||
|
||
err = yaml.Unmarshal(configBytes, config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
config.LabelSkipMap = map[string]bool{} | ||
for _, user := range config.LabelSkipUsers { | ||
config.LabelSkipMap[user] = true | ||
} | ||
|
||
return config, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package label | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"strings" | ||
|
||
"github.com/google/go-github/v60/github" | ||
|
||
"github.com/chia-network/github-bot/internal/config" | ||
) | ||
|
||
// PullRequests applies internal or community labels to pull requests | ||
// Internal is determined by checking if the PR author is a member of the specified internalTeam | ||
func PullRequests(githubClient *github.Client, internalTeam string, cfg config.LabelConfig) error { | ||
teamMembers := map[string]bool{} | ||
|
||
teamParts := strings.Split(internalTeam, "/") | ||
if len(teamParts) != 2 { | ||
return fmt.Errorf("invalid team name - must contain org and team : %s", internalTeam) | ||
} | ||
|
||
teamOpts := &github.TeamListTeamMembersOptions{ | ||
Role: "all", | ||
ListOptions: github.ListOptions{ | ||
Page: 0, | ||
PerPage: 100, | ||
}, | ||
} | ||
|
||
for { | ||
teamOpts.ListOptions.Page++ | ||
members, resp, err := githubClient.Teams.ListTeamMembersBySlug(context.TODO(), teamParts[0], teamParts[1], teamOpts) | ||
if err != nil { | ||
return fmt.Errorf("error getting team %s: %w", internalTeam, err) | ||
} | ||
|
||
for _, member := range members { | ||
teamMembers[*member.Login] = true | ||
} | ||
|
||
if resp.NextPage == 0 { | ||
break | ||
} | ||
} | ||
|
||
for _, fullRepo := range cfg.LabelCheckRepos { | ||
log.Println("checking repos") | ||
parts := strings.Split(fullRepo.Name, "/") | ||
if len(parts) != 2 { | ||
return fmt.Errorf("invalid repository name - must contain owner and repository: %s", fullRepo.Name) | ||
} | ||
opts := &github.PullRequestListOptions{ | ||
State: "open", | ||
Sort: "created", | ||
Direction: "desc", | ||
ListOptions: github.ListOptions{ | ||
Page: 0, | ||
PerPage: 100, | ||
}, | ||
} | ||
for { | ||
lowestNumber := 0 | ||
opts.ListOptions.Page++ | ||
owner := parts[0] | ||
repo := parts[1] | ||
pullRequests, resp, err := githubClient.PullRequests.List(context.TODO(), owner, repo, opts) | ||
if err != nil { | ||
return fmt.Errorf("error listing pull requests: %w", err) | ||
} | ||
|
||
for _, pullRequest := range pullRequests { | ||
lowestNumber = *pullRequest.Number | ||
if *pullRequest.Number < fullRepo.MinimumNumber { | ||
// Break, not continue, since our order ensures PR numbers are getting smaller | ||
break | ||
} | ||
if *pullRequest.Draft { | ||
continue | ||
} | ||
user := *pullRequest.User.Login | ||
if cfg.LabelSkipMap[user] { | ||
continue | ||
} | ||
var label string | ||
if teamMembers[user] { | ||
label = cfg.LabelInternal | ||
} else { | ||
label = cfg.LabelExternal | ||
} | ||
|
||
if label != "" { | ||
log.Printf("Pull Request %d by %s will be labeled %s\n", *pullRequest.Number, user, label) | ||
hasLabel := false | ||
for _, existingLabel := range pullRequest.Labels { | ||
if *existingLabel.Name == label { | ||
log.Println(" Already labeled, skipping...") | ||
hasLabel = true | ||
break | ||
} | ||
} | ||
|
||
if !hasLabel { | ||
allLabels := []string{label} | ||
for _, labelP := range pullRequest.Labels { | ||
allLabels = append(allLabels, *labelP.Name) | ||
} | ||
_, _, err := githubClient.Issues.AddLabelsToIssue(context.TODO(), owner, repo, *pullRequest.Number, allLabels) | ||
if err != nil { | ||
return fmt.Errorf("error adding labels to pull request %d: %w", *pullRequest.Number, err) | ||
} | ||
} | ||
} | ||
} | ||
|
||
if resp.NextPage == 0 || lowestNumber <= fullRepo.MinimumNumber { | ||
break | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} |