aboutsummaryrefslogblamecommitdiff
path: root/tools/gerrit-hook/gerrit.go
blob: 4854427981ee650a41b3e2426cb6ea9bf6f8b007 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                       
                 






























































                                                                                                                  



                                                                            


                                                                      






























































                                                                                                    
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"log/syslog"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"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()

	// if the name of the repository has  dot in it's name,  we
	// replace it with the string `-dot-'.
	trigger.project = strings.Replace(trigger.project, ".", "-dot-", -1)

	// if the name of the targetted branch is not `main', we don't
	// care about running the tests.
	if 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)
	}
}