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() // 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) } }