package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"log/syslog"
"net/http"
"os"
"regexp"
"strconv"
"time"
)
// Regular expression to extract change ID out of a URL
var changeIdRegexp = regexp.MustCompile(`^.*/(\d+)$`)
func gerritHookMain(cfg *config, log *syslog.Writer, trigger *buildTrigger) {
if trigger == nil {
os.Exit(0)
}
err := triggerBuild(cfg, log, trigger)
if err != nil {
log.Err(fmt.Sprintf("failed to trigger Buildkite build: %s", err))
os.Exit(1)
}
}
type reviewInput struct {
Message string `json:"message"`
Labels map[string]int `json:"labels,omitempty"`
OmitDuplicateComments bool `json:"omit_duplicate_comments"`
IgnoreDefaultAttentionSetRules bool `json:"ignore_default_attention_set_rules"`
Tag string `json:"tag"`
Notify string `json:"notify,omitempty"`
}
type buildTrigger struct {
project string
change string
kind string
changeUrl string
changeOwner string
changeOwnerUserName string
branch string
topic string
uploader string
uploaderUserName string
commit string
patchset string
changeId string
ref string
}
// https://gerrit.googlesource.com/plugins/hooks/+/HEAD/src/main/resources/Documentation/hooks.md#patchset_created
func triggerForPatchsetCreated() (*buildTrigger, error) {
var trigger buildTrigger
flag.StringVar(&trigger.project, "project", "", "Gerrit project")
flag.StringVar(&trigger.change, "change", "", "Gerrit change")
flag.StringVar(&trigger.kind, "kind", "", "Gerrit kind")
flag.StringVar(&trigger.changeUrl, "change-url", "", "Gerrit URL for the change")
flag.StringVar(&trigger.changeOwner, "change-owner", "", "Gerrit owner")
flag.StringVar(&trigger.changeOwnerUserName, "change-owner-username", "", "Gerrit username")
flag.StringVar(&trigger.branch, "branch", "", "name of the branch")
flag.StringVar(&trigger.topic, "topic", "", "name of the topic")
flag.StringVar(&trigger.uploader, "uploader", "", "name ofthe uploader")
flag.StringVar(&trigger.uploaderUserName, "uploader-username", "", "")
flag.StringVar(&trigger.commit, "commit", "", "")
flag.StringVar(&trigger.patchset, "patchset", "", "")
flag.Parse()
// for now we only care about the project named `world' and the
// branch named 'main'
if trigger.project != "world" || trigger.branch != "main" {
return nil, nil
}
// We only care about patchset that are actually modifying the
// code. See
// https://gerrit-review.googlesource.com/Documentation/config-labels.html
if trigger.kind == "NO_CODE_CHANGE" || trigger.kind == "NO_CHANGE" {
return nil, nil
}
// extract the changeId from the URL
matches := changeIdRegexp.FindStringSubmatch(trigger.changeUrl)
trigger.changeId = matches[1]
// build the ref
changeId, _ := strconv.Atoi(trigger.changeId)
trigger.ref = fmt.Sprintf(
"refs/changes/%02d/%s/%s",
changeId%100, trigger.changeId, trigger.patchset,
)
return &trigger, nil
}
// after triggering a build with buildKite, we update gerrit to add a
// comment that links to the build.
func updateGerrit(cfg *config, review reviewInput, changeId string, patchSet string) {
body, err := json.Marshal(review)
if err != nil {
log.Fatal(fmt.Sprintf("failed to marshal gerrit update: %v", err))
os.Exit(1)
}
reader := ioutil.NopCloser(bytes.NewReader(body))
url := fmt.Sprintf("%s/a/changes/%s/revisions/%s/review", cfg.GerritUrl, changeId, patchSet)
req, err := http.NewRequest("POST", url, reader)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create an HTTP request: %v", err)
os.Exit(1)
}
req.SetBasicAuth(cfg.GerritUser, cfg.GerritPassword)
req.Header.Add("Content-Type", "application/json")
// Let's budget this to 10 seconds maximum, this should be more
// than enough to add a comment to gerrit.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
fmt.Fprintf(os.Stderr, "failed to send gerrit request: %v", err)
os.Exit(1)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := ioutil.ReadAll(resp.Body)
fmt.Fprintf(os.Stderr, "failed to update gerrit: %s: %s ", respBody, resp.Status)
} else {
fmt.Printf("added link to CI build to %s", patchSet)
}
}