| // sheriff_emails is an application that emails the next sheriff every week. |
| package main |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "html/template" |
| "strings" |
| |
| "go.skia.org/infra/go/common" |
| "go.skia.org/infra/go/email" |
| "go.skia.org/infra/go/httputils" |
| "go.skia.org/infra/go/sklog" |
| "go.skia.org/infra/go/util" |
| ) |
| |
| const ( |
| ROTATIONS_GMAIL_CACHED_TOKEN = "rotations_gmail_cached_token" |
| |
| NEXT_SHERIFF_JSON_URL = "http://tree-status.skia.org/next-sheriff" |
| |
| EXTRA_RECIPIENT = "rmistry@google.com" |
| |
| EMAIL_TEMPLATE = ` |
| Hi {{.SheriffName}}, |
| <br/><br/> |
| |
| You will be the {{.SheriffType}} for the coming week ({{.ScheduleStart}} - {{.ScheduleEnd}}). |
| <br/><br/> |
| |
| Documentation for {{.SheriffType}}s is in {{.SheriffDoc}}. |
| <br/><br/> |
| |
| The schedule for {{.SheriffType}}s is in {{.SheriffSchedules}}. |
| <br/><br/> |
| |
| If you need to swap shifts with someone (because you are out sick or on vacation), please get approval from the person you want to swap with. Then send an email to skiabot@google.com to have someone make the change in the database (or directly ping rmistry@). |
| <br/><br/> |
| |
| Please let skiabot@google.com know if you have any other questions. |
| <br/><br/> |
| |
| Thanks! |
| ` |
| ) |
| |
| type ShiftType struct { |
| shiftName string |
| schedulesLink string |
| documentationLink string |
| nextSheriffEndpoint string |
| } |
| |
| const ( |
| // This placeholder is used to signify that nobody will be sheriffing that |
| // week. |
| NO_SHERIFF = "nobody" |
| ) |
| |
| var ( |
| local = flag.Bool("local", false, "Running locally if true. As opposed to in production.") |
| skiaSheriffShift = &ShiftType{shiftName: "Skia Sheriff", schedulesLink: "http://tree-status.skia.org/sheriff", documentationLink: "https://skia.org/dev/sheriffing", nextSheriffEndpoint: "http://tree-status.skia.org/next-sheriff"} |
| gpuWranglerShift = &ShiftType{shiftName: "GPU Wrangler", schedulesLink: "http://tree-status.skia.org/wrangler", documentationLink: "https://skia.org/dev/sheriffing/gpu", nextSheriffEndpoint: "http://tree-status.skia.org/next-wrangler"} |
| robocopShift = &ShiftType{shiftName: "Android Robocop", schedulesLink: "http://tree-status.skia.org/robocop", documentationLink: "https://skia.org/dev/sheriffing/android", nextSheriffEndpoint: "http://tree-status.skia.org/next-robocop"} |
| trooperShift = &ShiftType{shiftName: "Infra Trooper", schedulesLink: "http://tree-status.skia.org/trooper", documentationLink: "https://skia.org/dev/sheriffing/trooper", nextSheriffEndpoint: "http://tree-status.skia.org/next-trooper"} |
| allShiftTypes = []*ShiftType{skiaSheriffShift, gpuWranglerShift, robocopShift, trooperShift} |
| |
| emailClientSecretFile = flag.String("email_client_secret_file", "", "OAuth client secret JSON file for sending email.") |
| emailTokenCacheFile = flag.String("email_token_cache_file", "", "OAuth token cache file for sending email.") |
| ) |
| |
| // sendEmail sends an email with the specified header and body to the recipients. |
| func sendEmail(emailAuth *email.GMail, recipients []string, senderDisplayName, subject, body, markup string) error { |
| _, err := emailAuth.SendWithMarkup(senderDisplayName, recipients, subject, body, markup, "") |
| if err != nil { |
| return fmt.Errorf("Could not send email: %s", err) |
| } |
| return nil |
| } |
| |
| func main() { |
| common.Init() |
| |
| emailAuth, err := email.NewFromFiles(*emailTokenCacheFile, *emailClientSecretFile) |
| if err != nil { |
| sklog.Fatalf("Failed to create email auth: %v", err) |
| } |
| |
| defer sklog.Flush() |
| |
| client := httputils.NewTimeoutClient() |
| for _, shiftType := range allShiftTypes { |
| |
| res, err := client.Get(shiftType.nextSheriffEndpoint) |
| if err != nil { |
| sklog.Fatalf("Could not HTTP Get: %s", err) |
| } |
| defer util.Close(res.Body) |
| |
| var jsonType map[string]interface{} |
| if err := json.NewDecoder(res.Body).Decode(&jsonType); err != nil { |
| sklog.Fatalf("Could not unmarshal JSON: %s", err) |
| } |
| sheriffEmail, _ := jsonType["username"].(string) |
| if sheriffEmail == NO_SHERIFF { |
| sklog.Infof("Skipping emailing %s because %s was specified", shiftType.shiftName, NO_SHERIFF) |
| continue |
| } |
| sheriffUsername := strings.Split(string(sheriffEmail), "@")[0] |
| |
| emailTemplateParsed := template.Must(template.New("sheriff_email").Parse(EMAIL_TEMPLATE)) |
| emailBytes := new(bytes.Buffer) |
| if err := emailTemplateParsed.Execute(emailBytes, struct { |
| SheriffName string |
| SheriffType string |
| SheriffSchedules string |
| SheriffDoc string |
| ScheduleStart string |
| ScheduleEnd string |
| }{ |
| SheriffName: sheriffUsername, |
| SheriffType: shiftType.shiftName, |
| SheriffSchedules: shiftType.schedulesLink, |
| SheriffDoc: shiftType.documentationLink, |
| ScheduleStart: jsonType["schedule_start"].(string), |
| ScheduleEnd: jsonType["schedule_end"].(string), |
| }); err != nil { |
| sklog.Errorf("Failed to execute template: %s", err) |
| return |
| } |
| |
| emailSubject := fmt.Sprintf("%s is the next %s", sheriffUsername, shiftType.shiftName) |
| viewActionMarkup, err := email.GetViewActionMarkup(shiftType.schedulesLink, "View Schedule", "Direct link to the schedule") |
| if err != nil { |
| sklog.Errorf("Failed to get view action markup: %s", err) |
| return |
| } |
| senderDisplayName := fmt.Sprintf("%s Rotation", shiftType.shiftName) |
| if err := sendEmail(emailAuth, []string{sheriffEmail, EXTRA_RECIPIENT}, senderDisplayName, emailSubject, emailBytes.String(), viewActionMarkup); err != nil { |
| sklog.Fatalf("Error sending email to sheriff: %s", err) |
| } |
| } |
| } |