433 lines
11 KiB
Go
433 lines
11 KiB
Go
package main
|
|
|
|
/*
|
|
* This file is part of Autogits.
|
|
*
|
|
* Copyright © 2024 SUSE LLC
|
|
*
|
|
* Autogits is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* Autogits is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* Foobar. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
|
|
"src.opensuse.org/autogits/common"
|
|
)
|
|
|
|
const (
|
|
ListenAddrDef = "[::1]:8002"
|
|
AppName = "rabbitmq-forwarder"
|
|
DefTopicDomain = "opensuse"
|
|
)
|
|
|
|
var DebugMode bool
|
|
var topicScope string
|
|
|
|
func connectToRabbitMQ() {
|
|
host := os.Getenv("RABBITMQ_HOST")
|
|
username := os.Getenv("RABBITMQ_USERNAME")
|
|
password := os.Getenv("RABBITMQ_PASSWORD")
|
|
|
|
if len(host) == 0 || len(username) == 0 || len(password) == 0 {
|
|
fmt.Println("Missing RABBITMQ_HOST, RABBITMQ_USERNAME, RABBITMQ_PASSWORD")
|
|
os.Exit(1)
|
|
}
|
|
|
|
go ConnectToExchangeForPublish(host, username, password)
|
|
}
|
|
|
|
var id int
|
|
|
|
func dumpUnhandledData(reqType string, data []byte) {
|
|
id++
|
|
if err := os.WriteFile(fmt.Sprintf("/tmp/unhandled-json-%s-%d.json", reqType, id), data, 0600); err != nil {
|
|
log.Printf("Cannot dump json. err: %v\n", err)
|
|
log.Println(string(data))
|
|
}
|
|
}
|
|
|
|
func parseRequestJSONOrg(reqType string, data []byte) (org *common.Organization, extraAction string, err error) {
|
|
extraAction = ""
|
|
|
|
switch reqType {
|
|
case common.RequestType_CreateBrachTag, common.RequestType_DeleteBranchTag:
|
|
create := common.CreateWebhookEvent{}
|
|
if err = json.Unmarshal(data, &create); err != nil {
|
|
return
|
|
}
|
|
org = create.Repository.Owner
|
|
|
|
case common.RequestType_Fork:
|
|
fork := common.ForkWebhookEvent{}
|
|
if err = json.Unmarshal(data, &fork); err != nil {
|
|
return
|
|
}
|
|
org = fork.Forkee.Owner
|
|
|
|
case common.RequestType_Push:
|
|
push := common.PushWebhookEvent{}
|
|
if err = json.Unmarshal(data, &push); err != nil {
|
|
return
|
|
}
|
|
org = push.Repository.Owner
|
|
|
|
case common.RequestType_Repository:
|
|
repoAction := common.RepositoryWebhookEvent{}
|
|
if err = json.Unmarshal(data, &repoAction); err != nil {
|
|
return
|
|
}
|
|
switch repoAction.Action {
|
|
case "created", "deleted":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown repository webhook action type: %s", repoAction.Action)
|
|
return
|
|
}
|
|
org = repoAction.Organization
|
|
extraAction = repoAction.Action
|
|
|
|
case common.RequestType_Release:
|
|
release := common.ReleaseWebhookEvent{}
|
|
if err = json.Unmarshal(data, &release); err != nil {
|
|
return
|
|
}
|
|
switch release.Action {
|
|
case "published", "updated", "deleted":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknwon Release webhook action type: %s", release.Action)
|
|
return
|
|
}
|
|
|
|
org = release.Repository.Owner
|
|
extraAction = release.Action
|
|
|
|
case common.RequestType_Issue:
|
|
issue := common.IssueWebhookEvent{}
|
|
if err = json.Unmarshal(data, &issue); err != nil {
|
|
return
|
|
}
|
|
switch issue.Action {
|
|
case "opened", "closed", "reopened", "edited":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown Issue webhook action type: %s", issue.Action)
|
|
return
|
|
}
|
|
org = issue.Repository.Owner
|
|
extraAction = issue.Action
|
|
|
|
case common.RequestType_IssueAssign:
|
|
issue := common.IssueWebhookEvent{}
|
|
if err = json.Unmarshal(data, &issue); err != nil {
|
|
return
|
|
}
|
|
switch issue.Action {
|
|
case "assigned", "unassigned":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown Issue Assign webhook action type: %s", issue.Action)
|
|
return
|
|
}
|
|
org = issue.Repository.Owner
|
|
extraAction = issue.Action
|
|
|
|
case common.RequestType_IssueComment, common.RequestType_PRComment:
|
|
issue := common.IssueCommentWebhookEvent{}
|
|
if err = json.Unmarshal(data, &issue); err != nil {
|
|
return
|
|
}
|
|
switch issue.Action {
|
|
case "edited", "created", "deleted":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown Issue/PR Comment webhook action type: %s", issue.Action)
|
|
return
|
|
}
|
|
org = issue.Repository.Owner
|
|
extraAction = issue.Action
|
|
|
|
case common.RequestType_IssueLabel:
|
|
issue := common.IssueWebhookEvent{}
|
|
if err = json.Unmarshal(data, &issue); err != nil {
|
|
return
|
|
}
|
|
switch issue.Action {
|
|
case "label_updated", "label_cleared":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown Issue Assign webhook action type: %s", issue.Action)
|
|
return
|
|
}
|
|
org = issue.Repository.Owner
|
|
extraAction = issue.Action
|
|
|
|
case common.RequestType_IssueMilestone:
|
|
issue := common.IssueWebhookEvent{}
|
|
if err = json.Unmarshal(data, &issue); err != nil {
|
|
return
|
|
}
|
|
switch issue.Action {
|
|
case "milestoned", "demilestoned":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown Issue Assign webhook action type: %s", issue.Action)
|
|
return
|
|
}
|
|
org = issue.Repository.Owner
|
|
extraAction = issue.Action
|
|
|
|
case common.RequestType_PR:
|
|
pr := common.PullRequestWebhookEvent{}
|
|
if err = json.Unmarshal(data, &pr); err != nil {
|
|
return
|
|
}
|
|
switch pr.Action {
|
|
case "opened", "closed", "reopened", "edited":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown PR webhook action type: %s", pr.Action)
|
|
return
|
|
}
|
|
org = pr.Repository.Owner
|
|
extraAction = pr.Action
|
|
|
|
case common.RequestType_PRLabel:
|
|
pr := common.PullRequestWebhookEvent{}
|
|
if err = json.Unmarshal(data, &pr); err != nil {
|
|
return
|
|
}
|
|
switch pr.Action {
|
|
case "label_updated", "label_cleared":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown PR Label webhook action type: %s", pr.Action)
|
|
return
|
|
}
|
|
org = pr.Repository.Owner
|
|
extraAction = pr.Action
|
|
|
|
case common.RequestType_PRMilestone:
|
|
pr := common.PullRequestWebhookEvent{}
|
|
if err = json.Unmarshal(data, &pr); err != nil {
|
|
return
|
|
}
|
|
switch pr.Action {
|
|
case "milestoned", "demilestoned":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown PR Milestone webhook action type: %s", pr.Action)
|
|
return
|
|
}
|
|
org = pr.Repository.Owner
|
|
extraAction = pr.Action
|
|
|
|
case common.RequestType_PRAssign:
|
|
issue := common.PullRequestWebhookEvent{}
|
|
if err = json.Unmarshal(data, &issue); err != nil {
|
|
return
|
|
}
|
|
switch issue.Action {
|
|
case "assigned", "unassigned":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown PR Assign webhook action type: %s", issue.Action)
|
|
return
|
|
}
|
|
org = issue.Repository.Owner
|
|
extraAction = issue.Action
|
|
|
|
case common.RequestType_PRReviewRequest:
|
|
issue := common.PullRequestWebhookEvent{}
|
|
if err = json.Unmarshal(data, &issue); err != nil {
|
|
return
|
|
}
|
|
switch issue.Action {
|
|
case "review_requested", "review_request_removed":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown PR Review Request webhook action type: %s", issue.Action)
|
|
return
|
|
}
|
|
org = issue.Repository.Owner
|
|
extraAction = issue.Action
|
|
|
|
case common.RequestType_PRReviewAccepted, common.RequestType_PRReviewRejected, common.RequestType_PRReviewComment:
|
|
pr := common.PullRequestWebhookEvent{}
|
|
if err = json.Unmarshal(data, &pr); err != nil {
|
|
return
|
|
}
|
|
switch pr.Action {
|
|
case "reviewed":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown PR Review webhook action type: %s", pr.Action)
|
|
return
|
|
}
|
|
org = pr.Repository.Owner
|
|
extraAction = ""
|
|
|
|
case common.RequestType_PRSync:
|
|
pr := common.PullRequestWebhookEvent{}
|
|
if err = json.Unmarshal(data, &pr); err != nil {
|
|
return
|
|
}
|
|
switch pr.Action {
|
|
case "synchronized":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown PR Sync webhook action type: %s", pr.Action)
|
|
return
|
|
}
|
|
org = pr.Repository.Owner
|
|
extraAction = ""
|
|
|
|
case common.RequestType_Wiki:
|
|
wiki := common.WikiWebhookEvent{}
|
|
if err = json.Unmarshal(data, &wiki); err != nil {
|
|
return
|
|
}
|
|
|
|
switch wiki.Action {
|
|
case "created", "edited", "renamed", "deleted":
|
|
break
|
|
default:
|
|
err = fmt.Errorf("Unknown Wiki webhook action type: %s", wiki.Action)
|
|
return
|
|
}
|
|
org = wiki.Repository.Owner
|
|
extraAction = wiki.Action
|
|
|
|
default:
|
|
// TODO: package webhook
|
|
err = fmt.Errorf("Unknown webhook request type: %s", reqType)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func main() {
|
|
var listenAddr string
|
|
var reqBearerToken string
|
|
var doPublish bool
|
|
|
|
flag.BoolVar(&DebugMode, "debug", false, "enables debugging messages")
|
|
flag.StringVar(&listenAddr, "listen", ListenAddrDef, "HTTP listen socket address for webhook events")
|
|
flag.StringVar(&topicScope, "topic-domain", DefTopicDomain, "Default domain for RabbitMQ topics")
|
|
flag.StringVar(&reqBearerToken, "token", "", "HTTP Bearer token to match")
|
|
flag.BoolVar(&doPublish, "publish", true, "Publish to RabbitMQ. Disable for testing.")
|
|
flag.Parse()
|
|
|
|
log.Println("Starting....")
|
|
log.Printf(" * Debugging: %t\n", DebugMode)
|
|
log.Printf(" * Listening: %s\n", listenAddr)
|
|
log.Printf(" * Bearer token: %t\n", len(reqBearerToken) > 0)
|
|
log.Printf(" * Publish to RabbitMQ: %t\n", doPublish)
|
|
|
|
if doPublish {
|
|
connectToRabbitMQ()
|
|
}
|
|
|
|
http.HandleFunc("POST /rabbitmq-forwarder", func(res http.ResponseWriter, req *http.Request) {
|
|
if len(req.Header.Get("Content-Type")) == 0 ||
|
|
req.Header["Content-Type"][0] != "application/json" ||
|
|
req.Method != "POST" {
|
|
|
|
res.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if len(reqBearerToken) > 0 {
|
|
authToken := req.Header.Get("Authorization")
|
|
if len(authToken) != len(reqBearerToken)+7 || authToken[0:7] != "Bearer " || authToken[7:] != reqBearerToken {
|
|
log.Println("Invalid Authorization request...", authToken)
|
|
res.WriteHeader(http.StatusNetworkAuthenticationRequired)
|
|
}
|
|
}
|
|
|
|
hdr := req.Header[common.GiteaRequestHeader]
|
|
if len(hdr) != 1 {
|
|
res.WriteHeader(http.StatusInternalServerError)
|
|
log.Printf("Multiple Gitea headers received. %#v\n", hdr)
|
|
if DebugMode {
|
|
log.Println(req.Header)
|
|
}
|
|
return
|
|
}
|
|
reqType := hdr[0]
|
|
data, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
errorStr := fmt.Sprintf("error reading hook info: %v", err)
|
|
res.Header().Add("Content-Type", "plain/text")
|
|
res.Write([]byte(errorStr))
|
|
res.WriteHeader(http.StatusBadRequest)
|
|
|
|
if DebugMode {
|
|
log.Printf(errorStr)
|
|
}
|
|
return
|
|
}
|
|
|
|
if !json.Valid(data) {
|
|
if DebugMode {
|
|
log.Println("send invalid json request")
|
|
}
|
|
res.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
org, extraAction, err := parseRequestJSONOrg(reqType, data)
|
|
if err != nil {
|
|
res.WriteHeader(http.StatusBadRequest)
|
|
log.Printf("error parsing webhook %s JSON. err: %v", reqType, err)
|
|
if DebugMode {
|
|
dumpUnhandledData(reqType, data)
|
|
}
|
|
return
|
|
}
|
|
|
|
if org == nil {
|
|
res.WriteHeader(http.StatusBadRequest)
|
|
log.Printf("no `org` for message... type: %s", reqType)
|
|
if DebugMode {
|
|
dumpUnhandledData(reqType, data)
|
|
}
|
|
return
|
|
}
|
|
|
|
if doPublish {
|
|
err = PublishMessage(org.Username, reqType, extraAction, data)
|
|
if err != nil {
|
|
errorStr := fmt.Sprintf("hook (%s) processing error: %v\n", reqType, err)
|
|
res.Header().Add("Content-Type", "plain/text")
|
|
res.Write([]byte(errorStr))
|
|
res.WriteHeader(http.StatusBadRequest)
|
|
|
|
if DebugMode {
|
|
log.Println(errorStr)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
res.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
log.Fatal(http.ListenAndServe(listenAddr, nil))
|
|
}
|