blob: 4ed11975ab3b2030ce152887cb812f472a8fca03 [file] [log] [blame]
// 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://skia-tree-status.appspot.com/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://skia-tree-status.appspot.com/sheriff", documentationLink: "https://skia.org/dev/sheriffing", nextSheriffEndpoint: "http://skia-tree-status.appspot.com/next-sheriff"}
gpuWranglerShift = &ShiftType{shiftName: "GPU Wrangler", schedulesLink: "http://skia-tree-status.appspot.com/gpu-sheriff", documentationLink: "https://skia.org/dev/sheriffing/gpu", nextSheriffEndpoint: "http://skia-tree-status.appspot.com/next-gpu-sheriff"}
robocopShift = &ShiftType{shiftName: "Android Robocop", schedulesLink: "http://skia-tree-status.appspot.com/robocop", documentationLink: "https://skia.org/dev/sheriffing/android", nextSheriffEndpoint: "http://skia-tree-status.appspot.com/next-robocop"}
trooperShift = &ShiftType{shiftName: "Infra Trooper", schedulesLink: "http://skia-tree-status.appspot.com/trooper", documentationLink: "https://skia.org/dev/sheriffing/trooper", nextSheriffEndpoint: "http://skia-tree-status.appspot.com/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 {
if err := emailAuth.SendWithMarkup(senderDisplayName, recipients, subject, body, markup); 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)
}
}
}