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