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
|
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log/syslog"
"net/http"
"os"
"strings"
"time"
)
// https://buildkite.com/docs/apis/rest-api/builds#create-a-Build
type Build struct {
Commit string `json:"commit"`
Branch string `json:"branch"`
Env map[string]string `json:"env"`
}
type buildResponse struct {
WebUrl string `json:"web_url"`
}
func triggerBuild(cfg *config, log *syslog.Writer, trigger *buildTrigger) error {
env := make(map[string]string)
branch := trigger.ref
if trigger.changeId != "" && trigger.patchset != "" {
env["GERRIT_CHANGE_ID"] = trigger.changeId
env["GERRIT_CHANGE_URL"] = trigger.changeUrl
env["GERRIT_PATCHSET"] = trigger.patchset
branch = fmt.Sprintf("cl/%v", strings.Split(trigger.ref, "/")[3])
}
b := Build{
Commit: trigger.commit,
Branch: branch,
Env: env,
}
body, _ := json.Marshal(b)
reader := ioutil.NopCloser(bytes.NewReader(body))
bkUrl := fmt.Sprintf("https://api.buildkite.com/v2/organizations/%s/pipelines/%s/builds", cfg.BuildKiteOrganization, trigger.project)
req, err := http.NewRequest("POST", bkUrl, reader)
if err != nil {
return fmt.Errorf("failed to create an HTTP request: %v", err)
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", cfg.BuildKiteToken))
req.Header.Add("Content-Type", "application/json")
// Let's budget this to 10 seconds maximum, this should be more
// than enough, as we're only triggering the build, we're not
// waiting on the status of the build
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return fmt.Errorf("failed to send buildKite request: %v", err)
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to parse buildKite response: %v", err)
}
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("received a non-success response from buildKite: %s (%v)", respBody, resp.Status)
}
var buildResp buildResponse
err = json.Unmarshal(respBody, &buildResp)
if err != nil {
return fmt.Errorf("failed to unmarshal build response: %v", err)
}
// Report the status back to the Gerrit CL so that users can click
// through to the running build.
msg := fmt.Sprintf("started build for patchset #%s on: %s", trigger.patchset, buildResp.WebUrl)
review := reviewInput{
Message: msg,
OmitDuplicateComments: true,
Tag: "autogenerated:buildkite~trigger",
IgnoreDefaultAttentionSetRules: true,
Notify: "NONE",
}
updateGerrit(cfg, review, trigger.changeId, trigger.patchset)
return nil
}
func postCommand(cfg *config) {
changeId := os.Getenv("GERRIT_CHANGE_ID")
patchSet := os.Getenv("GERRIT_PATCHSET")
if changeId == "" || patchSet == "" {
fmt.Println("nothing to do")
return
}
// our build stage has the label :hammer:
if os.Getenv("BUILDKITE_LABEL") != ":hammer:" {
return
}
var vote int
var verb string
var notify string
if os.Getenv("BUILDKITE_COMMAND_EXIT_STATUS") == "0" {
vote = 1
verb = "passed"
notify = "NONE"
} else {
vote = -1
verb = "failed"
notify = "OWNER"
}
msg := fmt.Sprintf("Build of patchset %s %s: %s", patchSet, verb, os.Getenv("BUILDKITE_BUILD_URL"))
review := reviewInput{
Message: msg,
OmitDuplicateComments: true,
IgnoreDefaultAttentionSetRules: vote == 1,
Tag: "autogenerated:buildkite~result",
Notify: notify,
Labels: map[string]int{
"Verified": vote,
},
}
updateGerrit(cfg, review, changeId, patchSet)
}
|