gitlab: implement the approval reporter

Change-Id: Iac34f7883d1eda5179b707b3620d17b085bf135c
This commit is contained in:
Fabien Boucher 2020-07-16 12:10:04 +00:00
parent 7fae9a5926
commit b1494db03e
7 changed files with 78 additions and 9 deletions

View File

@ -19,14 +19,17 @@
success:
gitlab.com:
comment: true
approval: true
sqlreporter:
failure:
gitlab.com:
comment: true
approval: false
sqlreporter:
start:
gitlab.com:
comment: true
approval: false
sqlreporter:
- pipeline:

View File

@ -159,6 +159,12 @@ is taken from the pipeline.
Boolean value that determines if the reporter should add a
comment to the pipeline status to the GitLab Merge Request.
.. attr:: approval
Bolean value that determines whether to report *approve* or *unapprove*
into the merge request approval system. To set an approval the Zuul user
must be a *Developer* or *Maintainer* project's member. If not set approval
won't be reported.
Requirements Configuration
--------------------------

View File

@ -1682,6 +1682,18 @@ class FakeGitlabAPIClient(gitlabconnection.GitlabAPIClient):
mr = self._get_mr(match)
mr.addNote(params['body'])
match = re.match(
r'.+/projects/(.+)/merge_requests/(\d+)/approve$', url)
if match:
mr = self._get_mr(match)
mr.approved = True
match = re.match(
r'.+/projects/(.+)/merge_requests/(\d+)/unapprove$', url)
if match:
mr = self._get_mr(match)
mr.approved = False
return {}, 200, "", "POST"
@ -1715,6 +1727,7 @@ class FakeGitlabMergeRequest(object):
self.url = "https://%s/%s/merge_requests/%s" % (
self.gitlab.server, self.project, self.number)
self.is_merged = False
self.approved = False
self.mr_ref = self._createMRRef()
self._addCommitInMR(files=files)
@ -1789,7 +1802,7 @@ class FakeGitlabMergeRequest(object):
def _updateTimeStamp(self):
self.updated_at = datetime.datetime.now()
def getMergeRequestOpenedEvent(self):
def getMergeRequestOpenedEvent(self, action='open'):
name = 'gl_merge_request'
data = {
'object_kind': 'merge_request',
@ -1804,14 +1817,15 @@ class FakeGitlabMergeRequest(object):
'%Y-%m-%d %H:%M:%S UTC'),
'iid': self.number,
'target_branch': self.branch,
'last_commit': {'id': self.sha}
'last_commit': {'id': self.sha},
'action': action
},
}
return (name, data)
def getMergeRequestUpdatedEvent(self):
self.addCommit()
return self.getMergeRequestOpenedEvent()
return self.getMergeRequestOpenedEvent(action='update')
def getMergeRequestCommentedEvent(self, note):
self.addNote(note)

View File

@ -16,9 +16,11 @@
success:
gitlab:
comment: True
approval: True
failure:
gitlab:
comment: True
approval: False
- pipeline:
name: post

View File

@ -114,6 +114,7 @@ class TestGitlabDriver(ZuulTestCase):
self.assertThat(
A.notes[1]['body'],
MatchesRegex(r'.*project-test2.*SUCCESS.*', re.DOTALL))
self.assertTrue(A.approved)
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
def test_merge_request_updated(self):

View File

@ -128,10 +128,13 @@ class GitlabEventConnector(threading.Thread):
event.patch_number = attrs['last_commit']['id']
event.change_url = self.connection.getPullUrl(event.project_name,
event.change_number)
if event.created_at == event.updated_at:
if attrs['action'] == 'open':
event.action = 'opened'
else:
elif attrs['action'] == 'update':
event.action = 'changed'
else:
# Do not handle other merge_request action for now.
return None
event.type = 'gl_merge_request'
return event
@ -294,6 +297,26 @@ class GitlabAPIClient():
self._manage_error(*resp, zuul_event_id=zuul_event_id)
return resp[0]
# https://docs.gitlab.com/ee/api/merge_request_approvals.html#approve-merge-request
def approve_mr(self, project_name, number, approve=True,
zuul_event_id=None):
approve = 'approve' if approve else 'unapprove'
path = "/projects/%s/merge_requests/%s/%s" % (
quote_plus(project_name), number, approve)
params = {}
resp = self.post(
self.baseurl + path, params=params,
zuul_event_id=zuul_event_id)
try:
self._manage_error(*resp, zuul_event_id=zuul_event_id)
except GitlabAPIClientException:
# approve and unapprove endpoint could return code 401 whether the
# actual state of the Merge Request approval. Two call on approve
# endpoint the second call return 401.
if resp[1] != 401:
raise
return resp[0]
class GitlabConnection(BaseConnection):
driver_name = 'gitlab'
@ -490,6 +513,13 @@ class GitlabConnection(BaseConnection):
project_name, number, message, zuul_event_id=event)
log.info("Commented on MR %s#%s", project_name, number)
def approveMR(self, project_name, number, approve, event=None):
log = get_annotated_logger(self.log, event)
self.gl_client.approve_mr(
project_name, number, approve, zuul_event_id=event)
log.info(
"Set approval: %s on MR %s#%s", approve, project_name, number)
class GitlabWebController(BaseWebController):

View File

@ -29,6 +29,7 @@ class GitlabReporter(BaseReporter):
def __init__(self, driver, connection, pipeline, config=None):
super(GitlabReporter, self).__init__(driver, connection, config)
self._create_comment = self.config.get('comment', True)
self._approval = self.config.get('approval', None)
def report(self, item):
"""Report on an event."""
@ -42,15 +43,26 @@ class GitlabReporter(BaseReporter):
if hasattr(item.change, 'number'):
if self._create_comment:
self.addMRComment(item)
if self._approval is not None:
self.setApproval(item)
def addMRComment(self, item, comment=None):
def addMRComment(self, item):
log = get_annotated_logger(self.log, item.event)
message = comment or self._formatItemReport(item)
message = self._formatItemReport(item)
project = item.change.project.name
pr_number = item.change.number
mr_number = item.change.number
log.debug('Reporting change %s, params %s, message: %s',
item.change, self.config, message)
self.connection.commentMR(project, pr_number, message,
self.connection.commentMR(project, mr_number, message,
event=item.event)
def setApproval(self, item):
log = get_annotated_logger(self.log, item.event)
project = item.change.project.name
mr_number = item.change.number
log.debug('Reporting change %s, params %s, approval: %s',
item.change, self.config, self._approval)
self.connection.approveMR(project, mr_number, self._approval,
event=item.event)
def mergePull(self, item):
@ -63,5 +75,6 @@ class GitlabReporter(BaseReporter):
def getSchema():
gitlab_reporter = v.Schema({
'comment': bool,
'approval': bool,
})
return gitlab_reporter