180 lines
6.6 KiB
Python
180 lines
6.6 KiB
Python
# Copyright 2015 Puppet Labs
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import logging
|
|
import voluptuous as v
|
|
import time
|
|
|
|
from zuul.reporter import BaseReporter
|
|
from zuul.exceptions import MergeFailure
|
|
from zuul.driver.util import scalar_or_list
|
|
|
|
|
|
class GithubReporter(BaseReporter):
|
|
"""Sends off reports to Github."""
|
|
|
|
name = 'github'
|
|
log = logging.getLogger("zuul.GithubReporter")
|
|
|
|
def __init__(self, driver, connection, config=None):
|
|
super(GithubReporter, self).__init__(driver, connection, config)
|
|
self._commit_status = self.config.get('status', None)
|
|
self._create_comment = self.config.get('comment', True)
|
|
self._merge = self.config.get('merge', False)
|
|
self._labels = self.config.get('label', [])
|
|
if not isinstance(self._labels, list):
|
|
self._labels = [self._labels]
|
|
self._unlabels = self.config.get('unlabel', [])
|
|
if not isinstance(self._unlabels, list):
|
|
self._unlabels = [self._unlabels]
|
|
|
|
def report(self, item):
|
|
"""Report on an event."""
|
|
# order is important for github branch protection.
|
|
# A status should be set before a merge attempt
|
|
if self._commit_status is not None:
|
|
if (hasattr(item.change, 'patchset') and
|
|
item.change.patchset is not None):
|
|
self.setCommitStatus(item)
|
|
elif (hasattr(item.change, 'newrev') and
|
|
item.change.newrev is not None):
|
|
self.setCommitStatus(item)
|
|
# Comments, labels, and merges can only be performed on pull requests.
|
|
# If the change is not a pull request (e.g. a push) skip them.
|
|
if hasattr(item.change, 'number'):
|
|
if self._create_comment:
|
|
self.addPullComment(item)
|
|
if self._labels or self._unlabels:
|
|
self.setLabels(item)
|
|
if (self._merge):
|
|
self.mergePull(item)
|
|
if not item.change.is_merged:
|
|
msg = self._formatItemReportMergeFailure(item)
|
|
self.addPullComment(item, msg)
|
|
|
|
def addPullComment(self, item, comment=None):
|
|
message = comment or self._formatItemReport(item)
|
|
project = item.change.project.name
|
|
pr_number = item.change.number
|
|
self.log.debug(
|
|
'Reporting change %s, params %s, message: %s' %
|
|
(item.change, self.config, message))
|
|
self.connection.commentPull(project, pr_number, message)
|
|
|
|
def setCommitStatus(self, item):
|
|
project = item.change.project.name
|
|
if hasattr(item.change, 'patchset'):
|
|
sha = item.change.patchset
|
|
elif hasattr(item.change, 'newrev'):
|
|
sha = item.change.newrev
|
|
context = '%s/%s' % (item.pipeline.layout.tenant.name,
|
|
item.pipeline.name)
|
|
state = self._commit_status
|
|
|
|
url_pattern = self.config.get('status-url')
|
|
if not url_pattern:
|
|
sched_config = self.connection.sched.config
|
|
if sched_config.has_option('webapp', 'status_url'):
|
|
url_pattern = sched_config.get('webapp', 'status_url')
|
|
url = item.formatUrlPattern(url_pattern) if url_pattern else ''
|
|
|
|
description = ''
|
|
if item.pipeline.description:
|
|
description = item.pipeline.description
|
|
|
|
self.log.debug(
|
|
'Reporting change %s, params %s, status:\n'
|
|
'context: %s, state: %s, description: %s, url: %s' %
|
|
(item.change, self.config, context, state,
|
|
description, url))
|
|
|
|
self.connection.setCommitStatus(
|
|
project, sha, state, url, description, context)
|
|
|
|
def mergePull(self, item):
|
|
project = item.change.project.name
|
|
pr_number = item.change.number
|
|
sha = item.change.patchset
|
|
self.log.debug('Reporting change %s, params %s, merging via API' %
|
|
(item.change, self.config))
|
|
message = self._formatMergeMessage(item.change)
|
|
|
|
for i in [1, 2]:
|
|
try:
|
|
self.connection.mergePull(project, pr_number, message, sha)
|
|
item.change.is_merged = True
|
|
return
|
|
except MergeFailure:
|
|
self.log.exception(
|
|
'Merge attempt of change %s %s/2 failed.' %
|
|
(item.change, i), exc_info=True)
|
|
if i == 1:
|
|
time.sleep(2)
|
|
self.log.warning(
|
|
'Merge of change %s failed after 2 attempts, giving up' %
|
|
item.change)
|
|
|
|
def setLabels(self, item):
|
|
project = item.change.project.name
|
|
pr_number = item.change.number
|
|
if self._labels:
|
|
self.log.debug('Reporting change %s, params %s, labels:\n%s' %
|
|
(item.change, self.config, self._labels))
|
|
for label in self._labels:
|
|
self.connection.labelPull(project, pr_number, label)
|
|
if self._unlabels:
|
|
self.log.debug('Reporting change %s, params %s, unlabels:\n%s' %
|
|
(item.change, self.config, self._unlabels))
|
|
for label in self._unlabels:
|
|
self.connection.unlabelPull(project, pr_number, label)
|
|
|
|
def _formatMergeMessage(self, change):
|
|
message = ''
|
|
|
|
if change.title:
|
|
message += change.title
|
|
|
|
account = getattr(change.source_event, 'account', None)
|
|
if not account:
|
|
return message
|
|
|
|
username = account['username']
|
|
name = account['name']
|
|
email = account['email']
|
|
message += '\n\nReviewed-by: '
|
|
|
|
if name:
|
|
message += name
|
|
if email:
|
|
if name:
|
|
message += ' '
|
|
message += '<' + email + '>'
|
|
if name or email:
|
|
message += '\n '
|
|
message += self.connection.getUserUri(username)
|
|
|
|
return message
|
|
|
|
|
|
def getSchema():
|
|
github_reporter = v.Schema({
|
|
'status': v.Any('pending', 'success', 'failure'),
|
|
'status-url': str,
|
|
'comment': bool,
|
|
'merge': bool,
|
|
'label': scalar_or_list(str),
|
|
'unlabel': scalar_or_list(str)
|
|
})
|
|
return github_reporter
|