1 # Copyright (C) 2013 Google Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
13 # * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 from google
.appengine
.api
import mail
36 from google
.appengine
.ext
import ndb
37 from google
.appengine
.ext
.webapp
import template
38 from google
.appengine
.ext
.db
import BadRequestError
40 # A simple log server for rebaseline-o-matic.
42 # Accepts updates to the same log entry and shows a simple status page.
43 # Has a special state for the case where there are no NeedsRebaseline
44 # lines in TestExpectations to avoid cluttering the log with useless
45 # entries every 30 seconds.
47 # Other than that, new updatelog calls append to the most recent log
48 # entry until they have the newentry parameter, in which case, it
49 # starts a new log entry.
52 NEW_ENTRY_PARAM
= "newentry"
53 NUM_LOGS_PARAM
= "numlogs"
54 BEFORE_PARAM
= "before"
55 COMMAND_PARAM
= "command"
57 SENDER_ADDRESS
= "bot@blinkrebaseline.appspotmail.com"
58 REBASELINES_LIST_ADDRESS
= "blink-rebaselines@chromium.org"
60 class LogEntry(ndb
.Model
):
61 content
= ndb
.TextProperty()
62 date
= ndb
.DateTimeProperty(auto_now_add
=True)
65 class SendMailCommand(object):
66 def execute(self
, command_data
):
67 if "to" not in command_data
or "subject" not in command_data
or "body" not in command_data
:
68 return "Malformed command JSON"
69 to
= command_data
["to"]
70 subject
= command_data
["subject"]
71 body
= command_data
["body"]
72 htmlbody
= command_data
["htmlbody"] if "htmlbody" in command_data
else body
73 mail
.send_mail(sender
=SENDER_ADDRESS
,
75 reply_to
="@".join(["apavlov", "chromium.org"]),
76 cc
=REBASELINES_LIST_ADDRESS
,
77 subject
="Auto-rebaseline bot: " + subject
,
80 return "Sent mail to %s (%s)" % (to
, subject
)
84 "sendmail": SendMailCommand()
89 return LogEntry
.query().order(-LogEntry
.date
)
92 def execute_command(command_data
):
94 return "ERROR: No command data"
96 if "name" not in command_data
:
97 return "Command is missing a name: %s" % json
.dumps(command_data
)
99 name
= command_data
["name"]
100 command
= COMMANDS
.get(name
)
102 return "Command %s is unknown"
104 return command
.execute(command_data
)
105 except Exception as e
:
106 return "Exception caught when executing command '%s'" % name
109 class UpdateLog(webapp2
.RequestHandler
):
111 new_log_data
= self
.request
.POST
.get(LOG_PARAM
)
113 command_json
= self
.request
.POST
.get(COMMAND_PARAM
)
114 command_data
= json
.loads(command_json
) if command_json
else None
117 if command_data
is not None:
118 command_output
= "\n" + execute_command(command_data
)
119 new_log_data
+= command_output
121 # This entry is set to on whenever a new auto-rebaseline run is going to
122 # start logging entries. If this is not on, then the log will get appended
123 # to the most recent log entry.
124 new_entry
= self
.request
.POST
.get(NEW_ENTRY_PARAM
) == "on"
126 out
= "Wrote new log entry."
128 log_entries
= logs_query().fetch(1)
130 log_entry
= log_entries
[0]
131 log_entry
.date
= datetime
.datetime
.now()
132 log_entry
.content
= log_entry
.content
+ "\n" + new_log_data
133 out
= "Added to existing log entry."
135 if new_entry
or not log_entries
:
136 log_entry
= LogEntry(content
=new_log_data
)
140 except BadRequestError
:
141 out
= "Created new log entry because the previous one exceeded the max length."
142 LogEntry(content
=new_log_data
, date
=datetime
.datetime
.now()).put()
144 self
.response
.headers
.add_header("Content-Type", "text/plain")
145 self
.response
.out
.write(out
+ command_output
)
148 class UploadForm(webapp2
.RequestHandler
):
150 self
.response
.out
.write(template
.render("uploadform.html", {
151 "update_log_url": "/updatelog",
152 "set_no_needs_rebaseline_url": "/noneedsrebaselines",
153 "log_param": LOG_PARAM
,
154 "new_entry_param": NEW_ENTRY_PARAM
,
155 "command_param": COMMAND_PARAM
159 class ShowLatest(webapp2
.RequestHandler
):
163 before
= self
.request
.get(BEFORE_PARAM
)
165 date
= datetime
.datetime
.strptime(before
, "%Y-%m-%dT%H:%M:%SZ")
166 query
= query
.filter(LogEntry
.date
< date
)
168 num_logs
= self
.request
.get(NUM_LOGS_PARAM
)
169 logs
= query
.fetch(int(num_logs
) if num_logs
else 3)
171 self
.response
.out
.write(template
.render("logs.html", {
173 "num_logs_param": NUM_LOGS_PARAM
,
174 "before_param": BEFORE_PARAM
,
179 ('/uploadform', UploadForm
),
180 ('/updatelog', UpdateLog
),
184 app
= webapp2
.WSGIApplication(routes
, debug
=True)