You've already forked mpbot-github
mirror of
https://github.com/macports/mpbot-github.git
synced 2026-03-31 14:46:03 -07:00
Add PR bot
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
package pr
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"errors"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
@@ -16,8 +17,8 @@ type Maintainer struct {
|
||||
type PortMaintainer struct {
|
||||
Primary *Maintainer
|
||||
Others []*Maintainer
|
||||
OpenMaintainer bool
|
||||
NoMaintainer bool
|
||||
OpenMaintainer bool
|
||||
}
|
||||
|
||||
var tracDB *sql.DB
|
||||
@@ -66,11 +67,21 @@ func GetMaintainer(port string) (*PortMaintainer, error) {
|
||||
maintainer := new(PortMaintainer)
|
||||
maintainerCursor := ""
|
||||
isPrimary := false
|
||||
rowExist := false
|
||||
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&maintainerCursor, &isPrimary); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowExist = true
|
||||
switch maintainerCursor {
|
||||
case "nomaintainer":
|
||||
maintainer.NoMaintainer = true
|
||||
continue
|
||||
case "openmaintainer":
|
||||
maintainer.OpenMaintainer = true
|
||||
continue
|
||||
}
|
||||
if isPrimary {
|
||||
maintainer.Primary = parseMaintainer(maintainerCursor)
|
||||
} else {
|
||||
@@ -78,6 +89,10 @@ func GetMaintainer(port string) (*PortMaintainer, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if !rowExist {
|
||||
return nil, errors.New("port not found")
|
||||
}
|
||||
|
||||
return maintainer, nil
|
||||
}
|
||||
|
||||
@@ -94,5 +109,10 @@ func parseMaintainer(maintainerFullString string) *Maintainer {
|
||||
maintainer.Email = maintainerString + "@macports.org"
|
||||
}
|
||||
}
|
||||
if maintainer.GithubHandle == "" && maintainer.Email != "" {
|
||||
if handle, err := GetGitHubHandle(maintainer.Email); err == nil {
|
||||
maintainer.GithubHandle = handle
|
||||
}
|
||||
}
|
||||
return maintainer
|
||||
}
|
||||
@@ -2,15 +2,37 @@ package githubapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/go-github/github"
|
||||
"regexp"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func ListChangedPorts(number int) []string {
|
||||
client := github.NewClient(nil)
|
||||
type Client struct {
|
||||
*github.Client
|
||||
ctx context.Context
|
||||
owner, repo string
|
||||
}
|
||||
|
||||
func NewClient(botSecret, owner, repo string) *Client {
|
||||
ctx := context.Background()
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: botSecret},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
|
||||
return &Client{
|
||||
Client: github.NewClient(tc),
|
||||
ctx: ctx,
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) ListChangedPorts(number int) ([]string, error) {
|
||||
files, _, err := client.PullRequests.ListFiles(context.Background(), "macports-staging", "macports-ports", number, nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
ports := make([]string, 0, 1)
|
||||
portfileRegexp := regexp.MustCompile(`[^\._/][^/]*/([^/]+)/Portfile`)
|
||||
@@ -19,5 +41,45 @@ func ListChangedPorts(number int) []string {
|
||||
ports = append(ports, match[1])
|
||||
}
|
||||
}
|
||||
return ports
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func (client *Client) CreateComment(number int, body *string) error {
|
||||
_, _, err := client.Issues.CreateComment(
|
||||
client.ctx,
|
||||
client.owner,
|
||||
client.repo,
|
||||
number,
|
||||
&github.IssueComment{Body: body},
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) ReplaceLabels(number int, labels []string) error {
|
||||
_, _, err := client.Issues.ReplaceLabelsForIssue(
|
||||
client.ctx,
|
||||
client.owner,
|
||||
client.repo,
|
||||
number,
|
||||
labels,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) ListLabels(number int) ([]string, error) {
|
||||
labels, _, err := client.Issues.ListLabelsByIssue(
|
||||
client.ctx,
|
||||
client.owner,
|
||||
client.repo,
|
||||
number,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labelNames := make([]string, 0, 1)
|
||||
for _, label := range labels {
|
||||
labelNames = append(labelNames, *label.Name)
|
||||
}
|
||||
return labelNames, nil
|
||||
}
|
||||
|
||||
@@ -12,10 +12,14 @@ import (
|
||||
func main() {
|
||||
webhookAddr := flag.String("l", "localhost:8081", "listen address for webhook events")
|
||||
flag.Parse()
|
||||
hubSecret := []byte(os.Getenv("HUB_WEBHOOK_SECRET"))
|
||||
if len(hubSecret) == 0 {
|
||||
hookSecret := []byte(os.Getenv("HUB_WEBHOOK_SECRET"))
|
||||
if len(hookSecret) == 0 {
|
||||
log.Fatal("HUB_WEBHOOK_SECRET not found")
|
||||
}
|
||||
botSecret := os.Getenv("HUB_BOT_SECRET")
|
||||
if botSecret == "" {
|
||||
log.Fatal("HUB_BOT_SECRET not found")
|
||||
}
|
||||
|
||||
webhook.NewReceiver(*webhookAddr, hubSecret).Start()
|
||||
webhook.NewReceiver(*webhookAddr, hookSecret, botSecret).Start()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,92 @@
|
||||
package webhook
|
||||
|
||||
func handlePullRequest(body []byte) {
|
||||
// Use |= for openmaintainer/nomaintainer (init 1) flag, nomaintainer take precedence
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/macports/mpbot-github/pr/db"
|
||||
"log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (receiver *Receiver) handlePullRequest(body []byte) {
|
||||
// Use &&= for isOpenmaintainer/isNomaintainer (init true) flag, isNomaintainer take precedence
|
||||
// Loop over ports and aggregate related maintainers
|
||||
// Use @_handle for now
|
||||
|
||||
event := &github.PullRequestEvent{}
|
||||
err := json.Unmarshal(body, event)
|
||||
if err != nil {
|
||||
// TODO: log
|
||||
return
|
||||
}
|
||||
number := *event.Number
|
||||
isOpenmaintainer := true
|
||||
isNomaintainer := true
|
||||
isMaintainer := false
|
||||
ports, err := receiver.githubClient.ListChangedPorts(number)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
handles := make([]string, 0, 1)
|
||||
for _, port := range ports {
|
||||
maintainer, err := db.GetMaintainer(port)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
isNomaintainer = isNomaintainer && maintainer.NoMaintainer
|
||||
isOpenmaintainer = isOpenmaintainer && (maintainer.OpenMaintainer || maintainer.NoMaintainer)
|
||||
if maintainer.NoMaintainer {
|
||||
continue
|
||||
}
|
||||
if maintainer.Primary.GithubHandle != "" {
|
||||
handles = append(handles, maintainer.Primary.GithubHandle)
|
||||
if maintainer.Primary.GithubHandle == *event.Sender.Login {
|
||||
// TODO: should be set only when the sender is maintainer of all modified ports
|
||||
isMaintainer = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch *event.Action {
|
||||
case "opened":
|
||||
// Notify maintainers
|
||||
if len(handles) > 0 {
|
||||
body := "Notifying maintainers: @_" + strings.Join(handles, " @_")
|
||||
err = receiver.githubClient.CreateComment(number, &body)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
case "synchronize":
|
||||
// Modify labels
|
||||
labels, err := receiver.githubClient.ListLabels(number)
|
||||
newLabels := make([]string, len(labels))
|
||||
copy(newLabels, labels)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
maintainerLabels := make([]string, 0)
|
||||
if isMaintainer {
|
||||
maintainerLabels = append(maintainerLabels, "maintainer")
|
||||
}
|
||||
if isNomaintainer {
|
||||
maintainerLabels = append(maintainerLabels, "maintainer: none")
|
||||
} else if isOpenmaintainer {
|
||||
maintainerLabels = append(maintainerLabels, "maintainer: open")
|
||||
}
|
||||
for _, label := range labels {
|
||||
if !strings.HasPrefix(label, "maintainer") {
|
||||
newLabels = append(newLabels, label)
|
||||
}
|
||||
}
|
||||
newLabels = append(newLabels, maintainerLabels...)
|
||||
err = receiver.githubClient.ReplaceLabels(number, newLabels)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
log.Println("PR #" + strconv.Itoa(number) + " processed")
|
||||
}
|
||||
|
||||
@@ -7,17 +7,22 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/macports/mpbot-github/pr/githubapi"
|
||||
)
|
||||
|
||||
type Receiver struct {
|
||||
listenAddr string
|
||||
hubSecret []byte
|
||||
listenAddr string
|
||||
hookSecret []byte
|
||||
githubClient *githubapi.Client
|
||||
}
|
||||
|
||||
func NewReceiver(listenAddr string, hubSecret []byte) *Receiver {
|
||||
func NewReceiver(listenAddr string, hookSecret []byte, botSecret string) *Receiver {
|
||||
return &Receiver{
|
||||
listenAddr: listenAddr,
|
||||
hubSecret: hubSecret,
|
||||
hookSecret: hookSecret,
|
||||
// TODO: canonical owner
|
||||
githubClient: githubapi.NewClient(botSecret, "macports-staging", "macports-ports"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +58,7 @@ func (receiver *Receiver) Start() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
case "pull_request":
|
||||
go handlePullRequest(body)
|
||||
go receiver.handlePullRequest(body)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -64,7 +69,7 @@ func (receiver *Receiver) Start() {
|
||||
|
||||
// checkMAC reports whether messageMAC is a valid HMAC tag for message.
|
||||
func (receiver *Receiver) checkMAC(message, messageMAC []byte) bool {
|
||||
mac := hmac.New(sha1.New, receiver.hubSecret)
|
||||
mac := hmac.New(sha1.New, receiver.hookSecret)
|
||||
mac.Write(message)
|
||||
expectedMAC := mac.Sum(nil)
|
||||
return hmac.Equal(messageMAC, expectedMAC)
|
||||
|
||||
Reference in New Issue
Block a user