Skip to content

Instantly share code, notes, and snippets.

@jayco

jayco/main.go

Last active Dec 9, 2020
Embed
What would you like to do?
EventBridge
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"github.com/aws/aws-lambda-go/lambda"
"golang.org/x/oauth2"
)
// general config values - api token with full access
var (
apiToken = os.Getenv("GRAPHQL_API_TOKEN")
defaultAnnotationStyle = "ERROR"
graphAPI = "https://graphql.buildkite.com/v1"
finished = "finished"
)
// Buildkite job (unused values omitted)
type job struct {
ID string `json:"graphql_id"`
UUID string `json:"uuid"`
State string `json:"state"`
ExitStatus int `json:"exit_status"`
SoftFailed bool `json:"soft_failed"`
RetriedInNewJob *string `json:"retried_in_job_id"`
}
// Buildkite build (unused values omitted)
type build struct {
ID string `json:"graphql_id"`
Number *int `json:"number"`
URL string `json:"url"`
}
// Buildkite pipeline (unused values omitted)
type pipeline struct {
Slug string `json:"slug"`
}
// Buildkite organization (unused values omitted)
type organization struct {
Slug string `json:"slug"`
}
// Buildkite devent detail
type detail struct {
Job job `json:"job"`
Build build `json:"build"`
Pipeline pipeline `json:"pipeline"`
Organization pipeline `json:"organization"`
}
// Buildkite job finished event
type event struct {
Detail detail `json:"detail"`
}
// Nothing fancy, lets just post using http client
func jsonPost(c *http.Client, jsonData *map[string]string) {
jsonValue, _ := json.Marshal(jsonData)
payload := bytes.NewBuffer(jsonValue)
if _, err := c.Post(graphAPI, "application/json", payload); err != nil {
log.Printf("%v", err)
}
}
// cancel build grapthql query
func cancel(buildGQLID string) *map[string]string {
cancelTemplate := `mutation {
buildCancel(input: {id: "%s"}) {
build {
canceledAt
}
}
}`
return &map[string]string{"query": fmt.Sprintf(cancelTemplate, buildGQLID)}
}
// annotate build grapthql query
func annotate(BuildID string, body string, style string) *map[string]string {
annotateTemplate := `mutation {
buildAnnotate(input: {buildID: "%s", body: "%s", style: %s}) {
annotation {
uuid
body { text }
style
context
}
}
}`
return &map[string]string{"query": fmt.Sprintf(annotateTemplate, BuildID, body, style)}
}
// create a message with a link to the failed job
func buildMessage(orgSlug string, pipelineSlug string, buildNumber int, jobUUID string) string {
return fmt.Sprintf(`Canceled because of hard failure at https://buildkite.com/%s/%s/builds/%d#%s`, orgSlug, pipelineSlug, buildNumber, jobUUID)
}
// handleRequest with a http client - best effort fast failing
func handleRequest(ctx context.Context, e event) (string, error) {
src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: apiToken})
httpClient := oauth2.NewClient(context.Background(), src)
// call the API and fail fast if the build is hard failing
if (e.Detail.Job.State == finished) && (e.Detail.Job.ExitStatus > 0) && (e.Detail.Job.SoftFailed == false) && (e.Detail.Job.RetriedInNewJob == nil) {
log.Println("hardfailed job, attempting to cancel build")
jsonPost(httpClient, cancel(e.Detail.Build.ID))
log.Printf("Build #%s canceled, %s", strconv.Itoa(*e.Detail.Build.Number), e.Detail.Build.URL)
jsonPost(httpClient, annotate(e.Detail.Build.ID, buildMessage(e.Detail.Organization.Slug, e.Detail.Pipeline.Slug, *e.Detail.Build.Number, e.Detail.Job.UUID), defaultAnnotationStyle))
}
return "ok", nil
}
// small server to listen for webhooks
func main() {
lambda.Start(handleRequest)
}
@jayco

This comment has been minimized.

Copy link
Owner Author

@jayco jayco commented Dec 2, 2020

Create AWS EventBridge notification in Buildkite.

Fail Fast 1

@jayco

This comment has been minimized.

Copy link
Owner Author

@jayco jayco commented Dec 2, 2020

Associate the notification with the event bus in AWS.

Screen Shot 2020-12-02 at 11 22 32 am

--

Screen Shot 2020-12-02 at 11 29 18 am

--

Screen Shot 2020-12-02 at 11 29 42 am

--

Screen Shot 2020-12-02 at 11 30 39 am

@jayco

This comment has been minimized.

Copy link
Owner Author

@jayco jayco commented Dec 3, 2020

Select the EventBus and create a rule

Screen Shot 2020-12-03 at 2 18 28 pm

Give it a name

Screen Shot 2020-12-03 at 2 20 59 pm

Define an event pattern

Screen Shot 2020-12-03 at 2 21 49 pm

I use:

{
  "detail-type": [
    "Job Finished"
  ],
  "detail": {
    "job": {
      "state": [
        "finished"
      ],
      "soft_failed": [
        false
      ]
    }
  }
}

Match the associated event bus

Screen Shot 2020-12-03 at 2 23 03 pm

Select your lambda function to use as a target, I also enable event logging

Screen Shot 2020-12-03 at 3 01 10 pm

And then hit create

Screen Shot 2020-12-03 at 3 02 22 pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment