1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// 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"
)
// 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": appengine.DefaultVersionHostname(c),
})
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)
}
}