Index: scripts/master/try_job_gerrit.py |
diff --git a/scripts/master/try_job_gerrit.py b/scripts/master/try_job_gerrit.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..680e47ccc4e5da58c9a901e931a171c8300493da |
--- /dev/null |
+++ b/scripts/master/try_job_gerrit.py |
@@ -0,0 +1,131 @@ |
+# Copyright (c) 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import json |
+import re |
+ |
+from twisted.internet import defer |
+from twisted.python import log |
+ |
+from buildbot.schedulers.base import BaseScheduler |
+ |
+from master.gerrit_poller import GerritPoller |
+ |
+ |
+class JobDefinition(object): |
+ """Describes a try job posted on Gerrit.""" |
+ def __init__(self, builder_names=None): |
+ # Force str type and remove empty builder names. |
+ self.builder_names = [str(b) for b in (builder_names or []) if b] |
+ |
+ def __repr__(self): |
+ return repr(self.__dict__) |
+ |
+ @staticmethod |
+ def parse(text): |
+ """Parses a try job definition.""" |
+ text = text and text.strip() |
+ if not text: |
+ # Return an empty definition. |
+ return JobDefinition() |
+ |
+ # Parse as json. |
+ try: |
+ job = json.loads(text) |
+ except: |
+ raise ValueError('Couldn\'t parse job definition: %s' % text) |
+ |
+ # Convert to canonical form. |
+ if isinstance(job, list): |
+ # Treat a list as builder name list. |
+ job = {'builderNames': job} |
+ elif not isinstance(job, dict): |
+ raise ValueError('Job definition must be a JSON object or array.') |
+ |
+ return JobDefinition(job.get('builderNames')) |
+ |
+ |
+class _TryJobGerritPoller(GerritPoller): |
+ """Polls issues, creates changes and calls scheduler.submitJob. |
+ |
+ This class is a part of TryJobGerritScheduler implementation and not designed |
+ to be used otherwise. |
+ """ |
+ |
+ change_category = 'tryjob' |
+ |
+ MESSAGE_REGEX_TRYJOB = re.compile('Patch set \d+:\s+\!tryjob(.*)', re.I) |
+ |
+ def __init__(self, scheduler, gerrit_host, gerrit_projects=None, |
+ pollInterval=None): |
+ assert scheduler |
+ GerritPoller.__init__(self, gerrit_host, gerrit_projects, pollInterval) |
+ self.scheduler = scheduler |
+ |
+ def _is_interesting_message(self, message): |
+ return self.MESSAGE_REGEX_TRYJOB.match(message['message']) |
+ |
+ def getChangeQuery(self): |
+ query = GerritPoller.getChangeQuery(self) |
+ # Request only issues with TryJob=+1 label. |
+ query += '+label:TryJob=%2B1' |
+ return query |
+ |
+ def parseJob(self, message): |
+ """Parses a JobDefinition from a Gerrit message.""" |
+ tryjob_match = self.MESSAGE_REGEX_TRYJOB.match(message['message']) |
+ assert tryjob_match |
+ return JobDefinition.parse(tryjob_match.group(1)) |
+ |
+ @defer.inlineCallbacks |
+ def addChange(self, change, message): |
+ """Parses a job, adds a change and calls self.scheduler.submitJob.""" |
+ try: |
+ job = self.parseJob(message) |
+ buildbotChange = yield self.addBuildbotChange(change, message) |
+ yield self.scheduler.submitJob(buildbotChange, job) |
+ defer.returnValue(buildbotChange) |
+ except Exception as e: |
+ log.err('TryJobGerritPoller failed: %s' % e) |
+ raise |
+ |
+ |
+class TryJobGerritScheduler(BaseScheduler): |
+ """Polls try jobs on Gerrit and creates buildsets.""" |
+ def __init__(self, name, default_builder_names, gerrit_host, |
+ gerrit_projects=None, pollInterval=None): |
+ """Creates a new TryJobGerritScheduler. |
+ |
+ Args: |
+ name: name of the scheduler. |
+ default_builder_names: a list of builder names used in case a job didn't |
+ specify any. |
+ gerrit_host: URL to the Gerrit instance |
+ gerrit_projects: Gerrit projects to filter issues. |
+ pollInterval: frequency of polling. |
+ """ |
+ BaseScheduler.__init__(self, name, |
+ builderNames=default_builder_names, |
+ properties={}) |
+ self.poller = _TryJobGerritPoller(self, gerrit_host, gerrit_projects, |
+ pollInterval) |
+ |
+ def setServiceParent(self, parent): |
+ BaseScheduler.setServiceParent(self, parent) |
+ self.poller.master = self.master |
+ self.poller.setServiceParent(self) |
+ |
+ def gotChange(self, *args, **kwargs): |
+ """Do nothing because changes are processed by submitJob.""" |
+ |
+ @defer.inlineCallbacks |
+ def submitJob(self, change, job): |
+ bsid = yield self.addBuildsetForChanges( |
+ reason='tryjob', |
+ changeids=[change.number], |
+ builderNames=job.builder_names, |
+ properties=change.properties) |
+ log.msg('Successfully submitted a Gerrit try job for %s: %s.' % |
+ (change.who, job)) |
+ defer.returnValue(bsid) |