diff options
Diffstat (limited to 'misc/dashboard/app/build/notify.go')
-rw-r--r-- | misc/dashboard/app/build/notify.go | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/misc/dashboard/app/build/notify.go b/misc/dashboard/app/build/notify.go new file mode 100644 index 000000000..826132be2 --- /dev/null +++ b/misc/dashboard/app/build/notify.go @@ -0,0 +1,150 @@ +// 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" + "fmt" + "gob" + "os" + "template" +) + +const ( + mailFrom = "builder@golang.org" // use this for sending any mail + failMailTo = "golang-dev@googlegroups.com" + domain = "build.golang.org" +) + +// 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. +func notifyOnFailure(c appengine.Context, com *Commit, builder string) os.Error { + // 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 + } + } + var err os.Error + 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. +func firstMatch(c appengine.Context, q *datastore.Query, v interface{}) os.Error { + 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( + template.New("notify").Funcs(tmplFuncs).ParseFile("build/notify.txt"), + ) +) + +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 { + c.Errorf("finding Log record %v: %v", r.LogHash, err) + return + } + + // prepare mail message + var body bytes.Buffer + err := sendFailMailTmpl.Execute(&body, map[string]interface{}{ + "Builder": builder, "Commit": com, "Result": r, "Log": l, + "Hostname": domain, + }) + 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) + } +} |