2011-12-20 19:16:47 -07:00
|
|
|
// Copyright 2011 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package build
|
|
|
|
|
|
|
|
import (
|
|
|
|
"appengine"
|
|
|
|
"appengine/datastore"
|
|
|
|
"appengine/delay"
|
|
|
|
"appengine/mail"
|
|
|
|
"bytes"
|
2012-02-05 15:26:32 -07:00
|
|
|
"encoding/gob"
|
2011-12-20 19:16:47 -07:00
|
|
|
"fmt"
|
2012-02-05 15:26:32 -07:00
|
|
|
"text/template"
|
2011-12-20 19:16:47 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
mailFrom = "builder@golang.org" // use this for sending any mail
|
|
|
|
failMailTo = "golang-dev@googlegroups.com"
|
2012-01-12 11:42:39 -07:00
|
|
|
domain = "build.golang.org"
|
2011-12-20 19:16:47 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// notifyOnFailure checks whether the supplied Commit or the subsequent
|
|
|
|
// Commit (if present) breaks the build for this builder.
|
|
|
|
// If either of those commits break the build an email notification is sent
|
|
|
|
// from a delayed task. (We use a task because this way the mail won't be
|
|
|
|
// sent if the enclosing datastore transaction fails.)
|
|
|
|
//
|
|
|
|
// This must be run in a datastore transaction, and the provided *Commit must
|
|
|
|
// have been retrieved from the datastore within that transaction.
|
2012-02-05 15:26:32 -07:00
|
|
|
func notifyOnFailure(c appengine.Context, com *Commit, builder string) error {
|
2011-12-20 19:16:47 -07:00
|
|
|
// TODO(adg): implement notifications for packages
|
|
|
|
if com.PackagePath != "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
p := &Package{Path: com.PackagePath}
|
|
|
|
var broken *Commit
|
|
|
|
ok, present := com.OK(builder, "")
|
|
|
|
if !present {
|
|
|
|
return fmt.Errorf("no result for %s/%s", com.Hash, builder)
|
|
|
|
}
|
|
|
|
q := datastore.NewQuery("Commit").Ancestor(p.Key(c))
|
|
|
|
if ok {
|
|
|
|
// This commit is OK. Notify if next Commit is broken.
|
|
|
|
next := new(Commit)
|
|
|
|
q.Filter("ParentHash=", com.Hash)
|
|
|
|
if err := firstMatch(c, q, next); err != nil {
|
|
|
|
if err == datastore.ErrNoSuchEntity {
|
|
|
|
// OK at tip, no notification necessary.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if ok, present := next.OK(builder, ""); present && !ok {
|
|
|
|
broken = next
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// This commit is broken. Notify if the previous Commit is OK.
|
|
|
|
prev := new(Commit)
|
|
|
|
q.Filter("Hash=", com.ParentHash)
|
|
|
|
if err := firstMatch(c, q, prev); err != nil {
|
|
|
|
if err == datastore.ErrNoSuchEntity {
|
|
|
|
// No previous result, let the backfill of
|
|
|
|
// this result trigger the notification.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if ok, present := prev.OK(builder, ""); present && ok {
|
|
|
|
broken = com
|
|
|
|
}
|
|
|
|
}
|
2012-02-05 15:26:32 -07:00
|
|
|
var err error
|
2011-12-20 19:16:47 -07:00
|
|
|
if broken != nil && !broken.FailNotificationSent {
|
|
|
|
c.Infof("%s is broken commit; notifying", broken.Hash)
|
|
|
|
sendFailMailLater.Call(c, broken, builder) // add task to queue
|
|
|
|
broken.FailNotificationSent = true
|
|
|
|
_, err = datastore.Put(c, broken.Key(c), broken)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// firstMatch executes the query q and loads the first entity into v.
|
2012-02-05 15:26:32 -07:00
|
|
|
func firstMatch(c appengine.Context, q *datastore.Query, v interface{}) error {
|
2011-12-20 19:16:47 -07:00
|
|
|
t := q.Limit(1).Run(c)
|
|
|
|
_, err := t.Next(v)
|
|
|
|
if err == datastore.Done {
|
|
|
|
err = datastore.ErrNoSuchEntity
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
sendFailMailLater = delay.Func("sendFailMail", sendFailMail)
|
|
|
|
sendFailMailTmpl = template.Must(
|
2012-02-05 15:26:32 -07:00
|
|
|
template.New("notify.txt").
|
|
|
|
Funcs(template.FuncMap(tmplFuncs)).
|
|
|
|
ParseFiles("build/notify.txt"),
|
2011-12-20 19:16:47 -07:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
gob.Register(&Commit{}) // for delay
|
|
|
|
}
|
|
|
|
|
|
|
|
// sendFailMail sends a mail notification that the build failed on the
|
|
|
|
// provided commit and builder.
|
|
|
|
func sendFailMail(c appengine.Context, com *Commit, builder string) {
|
|
|
|
// TODO(adg): handle packages
|
|
|
|
|
|
|
|
// get Result
|
|
|
|
r := com.Result(builder, "")
|
|
|
|
if r == nil {
|
|
|
|
c.Errorf("finding result for %q: %+v", builder, com)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// get Log
|
|
|
|
k := datastore.NewKey(c, "Log", r.LogHash, 0, nil)
|
|
|
|
l := new(Log)
|
|
|
|
if err := datastore.Get(c, k, l); err != nil {
|
2012-01-05 19:38:01 -07:00
|
|
|
c.Errorf("finding Log record %v: %v", r.LogHash, err)
|
2011-12-20 19:16:47 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare mail message
|
|
|
|
var body bytes.Buffer
|
|
|
|
err := sendFailMailTmpl.Execute(&body, map[string]interface{}{
|
|
|
|
"Builder": builder, "Commit": com, "Result": r, "Log": l,
|
2012-01-12 11:42:39 -07:00
|
|
|
"Hostname": domain,
|
2011-12-20 19:16:47 -07:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
c.Errorf("rendering mail template: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
subject := fmt.Sprintf("%s broken by %s", builder, shortDesc(com.Desc))
|
|
|
|
msg := &mail.Message{
|
|
|
|
Sender: mailFrom,
|
|
|
|
To: []string{failMailTo},
|
|
|
|
ReplyTo: failMailTo,
|
|
|
|
Subject: subject,
|
|
|
|
Body: body.String(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// send mail
|
|
|
|
if err := mail.Send(c, msg); err != nil {
|
|
|
|
c.Errorf("sending mail: %v", err)
|
|
|
|
}
|
|
|
|
}
|