diff options
author | Franck Cuny <franck@fcuny.net> | 2022-05-30 13:32:43 -0700 |
---|---|---|
committer | Franck Cuny <franck@fcuny.net> | 2022-06-04 15:25:46 -0700 |
commit | 83a38a6da9ef99bc6596f6cfb53395a89f0165c7 (patch) | |
tree | c808d9dd826757c440f1632fd758503f563c0403 /tools/gerrit-hook/gerrit.go | |
parent | meta: remove pre-commit checks (diff) | |
download | world-83a38a6da9ef99bc6596f6cfb53395a89f0165c7.tar.gz |
feat(gerrit-hook): a small tool to act as a dispatcher for gerrit
When a patchset is created, gerrit will call this tool with a number of arguments. This hook triggers a build with buildKite for the given patchset, and add a comment to gerrit with a link to the build. We do not wait for the build to be successful to update gerrit. This will be done by another hook which the buildKite agents will call once they are done with the build. Change-Id: Iaa221765f3c52875ec37c5d282ba0557291eb5a4 Reviewed-on: https://cl.fcuny.net/c/world/+/171 Reviewed-by: Franck Cuny <franck@fcuny.net>
Diffstat (limited to '')
-rw-r--r-- | tools/gerrit-hook/gerrit.go | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/tools/gerrit-hook/gerrit.go b/tools/gerrit-hook/gerrit.go new file mode 100644 index 0000000..6a23527 --- /dev/null +++ b/tools/gerrit-hook/gerrit.go @@ -0,0 +1,144 @@ +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) + } +} |