about summary refs log tree commit diff
path: root/tools/gerrit-hook/gerrit.go
blob: 6a23527d63218dae238ce2e03733eb4e23160a0d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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)
	}
}