Add support for adding and removing labels in gitlab
Also add support for triggering on label removal (addition is already supported). Change-Id: I2c65a53a5df66704f1621c208a2282d63d0f8074
This commit is contained in:
parent
9903b0b017
commit
56865e57d2
@ -190,9 +190,15 @@ the following options.
|
||||
|
||||
.. attr:: labels
|
||||
|
||||
This is only used for ``gl_merge_request`` and ``labeled`` actions. It
|
||||
accepts a string or a list of strings that are searched into the list
|
||||
of labels set to the merge request.
|
||||
This is only used for ``gl_merge_request`` and ``labeled``
|
||||
actions. It accepts a string or a list of strings that are that
|
||||
must have been added for the event to match.
|
||||
|
||||
.. attr:: unlabels
|
||||
|
||||
This is only used for ``gl_merge_request`` and ``labeled``
|
||||
actions. It accepts a string or a list of strings that are that
|
||||
must have been removed for the event to match.
|
||||
|
||||
.. attr:: ref
|
||||
|
||||
@ -235,6 +241,16 @@ is taken from the pipeline.
|
||||
*Maintainer* project's member. In case of *developer*, the *Allowed to merge*
|
||||
setting in *protected branches* must be set to *Developers + Maintainers*.
|
||||
|
||||
.. attr:: label
|
||||
|
||||
A string or list of strings, each representing a label name
|
||||
which should be added to the merge request.
|
||||
|
||||
.. attr:: unlabel
|
||||
|
||||
A string or list of strings, each representing a label name
|
||||
which should be removed from the merge request.
|
||||
|
||||
|
||||
Requirements Configuration
|
||||
--------------------------
|
||||
@ -275,7 +291,7 @@ in the *opened* state (not merged yet).
|
||||
|
||||
.. attr:: labels
|
||||
|
||||
if present, the list of labels a Merge Request must have.
|
||||
A list of labels a Merge Request must have in order to be enqueued.
|
||||
|
||||
|
||||
Reference pipelines configuration
|
||||
|
6
releasenotes/notes/gitlab-labels-1a7140429fb3f276.yaml
Normal file
6
releasenotes/notes/gitlab-labels-1a7140429fb3f276.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added support for adding and removing merge request labels in the
|
||||
GitLab driver, as well as triggering pipelines on label removal
|
||||
(label addition was already supported).
|
@ -2127,7 +2127,7 @@ class FakeGitlabMergeRequest(object):
|
||||
def _updateTimeStamp(self):
|
||||
self.updated_at = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
def getMergeRequestEvent(self, action, include_labels=False):
|
||||
def getMergeRequestEvent(self, action, previous_labels=None):
|
||||
name = 'gl_merge_request'
|
||||
data = {
|
||||
'object_kind': 'merge_request',
|
||||
@ -2149,9 +2149,9 @@ class FakeGitlabMergeRequest(object):
|
||||
data['labels'] = [{'title': label} for label in self.labels]
|
||||
data['changes'] = {}
|
||||
|
||||
if include_labels:
|
||||
if previous_labels is not None:
|
||||
data['changes']['labels'] = {
|
||||
'previous': [],
|
||||
'previous': [{'title': label} for label in previous_labels],
|
||||
'current': data['labels']
|
||||
}
|
||||
return (name, data)
|
||||
@ -2175,9 +2175,14 @@ class FakeGitlabMergeRequest(object):
|
||||
self.approved = False
|
||||
return self.getMergeRequestEvent(action='unapproved')
|
||||
|
||||
def getMergeRequestLabeledEvent(self, labels):
|
||||
self.labels = labels
|
||||
return self.getMergeRequestEvent(action='update', include_labels=True)
|
||||
def getMergeRequestLabeledEvent(self, add_labels=[], remove_labels=[]):
|
||||
previous_labels = self.labels
|
||||
labels = set(previous_labels)
|
||||
labels = labels - set(remove_labels)
|
||||
labels = labels | set(add_labels)
|
||||
self.labels = list(labels)
|
||||
return self.getMergeRequestEvent(action='update',
|
||||
previous_labels=previous_labels)
|
||||
|
||||
def getMergeRequestCommentedEvent(self, note):
|
||||
self.addNote(note)
|
||||
|
@ -64,6 +64,8 @@ class GitlabWebServer(object):
|
||||
|
||||
mr_merge_re = re.compile(r'.+/projects/(?P<project>.+)/'
|
||||
r'merge_requests/(?P<mr>\d+)/merge$')
|
||||
mr_update_re = re.compile(r'.+/projects/(?P<project>.+)/'
|
||||
r'merge_requests/(?P<mr>\d+)$')
|
||||
|
||||
def _get_mr(self, project, number):
|
||||
project = urllib.parse.unquote(project)
|
||||
@ -129,6 +131,9 @@ class GitlabWebServer(object):
|
||||
m = self.mr_merge_re.match(path)
|
||||
if m:
|
||||
return self.put_mr_merge(data, **m.groupdict())
|
||||
m = self.mr_update_re.match(path)
|
||||
if m:
|
||||
return self.put_mr_update(data, **m.groupdict())
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
|
||||
@ -231,6 +236,16 @@ class GitlabWebServer(object):
|
||||
mr.mergeMergeRequest()
|
||||
self.send_data({'state': 'merged'})
|
||||
|
||||
def put_mr_update(self, data, project, mr):
|
||||
mr = self._get_mr(project, mr)
|
||||
labels = set(mr.labels)
|
||||
add_labels = data.get('add_labels', [''])[0].split(',')
|
||||
remove_labels = data.get('remove_labels', [''])[0].split(',')
|
||||
labels = labels - set(remove_labels)
|
||||
labels = labels | set(add_labels)
|
||||
mr.labels = list(labels)
|
||||
self.send_data({})
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
self.log.debug(fmt, *args)
|
||||
|
||||
|
5
tests/fixtures/layouts/basic-gitlab.yaml
vendored
5
tests/fixtures/layouts/basic-gitlab.yaml
vendored
@ -42,6 +42,11 @@
|
||||
- labeled
|
||||
labels:
|
||||
- gateit
|
||||
- event: gl_merge_request
|
||||
action:
|
||||
- labeled
|
||||
unlabels:
|
||||
- verified
|
||||
|
||||
- pipeline:
|
||||
name: promote
|
||||
|
31
tests/fixtures/layouts/gitlab-label-add-remove.yaml
vendored
Normal file
31
tests/fixtures/layouts/gitlab-label-add-remove.yaml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
- pipeline:
|
||||
name: check
|
||||
manager: independent
|
||||
trigger:
|
||||
gitlab:
|
||||
- event: gl_merge_request
|
||||
action:
|
||||
- opened
|
||||
success:
|
||||
gitlab:
|
||||
comment: true
|
||||
label:
|
||||
- addme1
|
||||
- addme2
|
||||
unlabel:
|
||||
- removeme1
|
||||
- removeme2
|
||||
|
||||
- job:
|
||||
name: base
|
||||
parent: null
|
||||
run: playbooks/base.yaml
|
||||
|
||||
- job:
|
||||
name: project1-test
|
||||
|
||||
- project:
|
||||
name: org/project1
|
||||
check:
|
||||
jobs:
|
||||
- project1-test
|
@ -196,15 +196,21 @@ class TestGitlabDriver(ZuulTestCase):
|
||||
A = self.fake_gitlab.openFakeMergeRequest('org/project', 'master', 'A')
|
||||
|
||||
self.fake_gitlab.emitEvent(A.getMergeRequestLabeledEvent(
|
||||
labels=('label1', 'label2')))
|
||||
add_labels=('label1', 'label2')))
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(0, len(self.history))
|
||||
|
||||
self.fake_gitlab.emitEvent(A.getMergeRequestLabeledEvent(
|
||||
labels=('gateit', )))
|
||||
add_labels=('gateit', )))
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(1, len(self.history))
|
||||
|
||||
A.labels = ['verified']
|
||||
self.fake_gitlab.emitEvent(A.getMergeRequestLabeledEvent(
|
||||
remove_labels=('verified', )))
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(2, len(self.history))
|
||||
|
||||
@simple_layout('layouts/basic-gitlab.yaml', driver='gitlab')
|
||||
def test_merge_request_merged(self):
|
||||
|
||||
@ -639,6 +645,18 @@ class TestGitlabDriver(ZuulTestCase):
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(1, len(self.history))
|
||||
|
||||
@simple_layout('layouts/gitlab-label-add-remove.yaml', driver='gitlab')
|
||||
def test_label_add_remove(self):
|
||||
|
||||
A = self.fake_gitlab.openFakeMergeRequest(
|
||||
'org/project1', 'master', 'A')
|
||||
A.labels = ['removeme1', 'removeme2']
|
||||
|
||||
self.fake_gitlab.emitEvent(A.getMergeRequestOpenedEvent())
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(1, len(self.history))
|
||||
self.assertEqual(set(A.labels), {'addme1', 'addme2'})
|
||||
|
||||
@simple_layout('layouts/merging-gitlab.yaml', driver='gitlab')
|
||||
def test_merge_action_in_independent(self):
|
||||
|
||||
|
@ -156,6 +156,8 @@ class GitlabEventConnector(threading.Thread):
|
||||
label in body["changes"]["labels"]["current"]]
|
||||
new_labels = set(current_labels) - set(previous_labels)
|
||||
event.labels = list(new_labels)
|
||||
removed_labels = set(previous_labels) - set(current_labels)
|
||||
event.unlabels = list(removed_labels)
|
||||
elif attrs['action'] in ('approved', 'unapproved'):
|
||||
event.action = attrs['action']
|
||||
else:
|
||||
@ -443,6 +445,17 @@ class GitlabAPIClient():
|
||||
raise MergeFailure('Merge request merge failed: %s' % e)
|
||||
return resp[0]
|
||||
|
||||
# https://docs.gitlab.com/ee/api/merge_requests.html#update-mr
|
||||
def update_mr(self, project_name, number,
|
||||
zuul_event_id=None,
|
||||
**params):
|
||||
path = "/projects/%s/merge_requests/%s" % (
|
||||
quote_plus(project_name), number)
|
||||
resp = self.put(self.baseurl + path, params=params,
|
||||
zuul_event_id=zuul_event_id)
|
||||
self._manage_error(*resp, zuul_event_id=zuul_event_id)
|
||||
return resp[0]
|
||||
|
||||
|
||||
class GitlabConnection(ZKChangeCacheMixin, ZKBranchCacheMixin, BaseConnection):
|
||||
driver_name = 'gitlab'
|
||||
@ -710,6 +723,16 @@ class GitlabConnection(ZKChangeCacheMixin, ZKBranchCacheMixin, BaseConnection):
|
||||
project_name, number, method, zuul_event_id=event)
|
||||
log.info("Merged MR %s#%s", project_name, number)
|
||||
|
||||
def updateMRLabels(self, project_name, mr_number, labels, unlabels,
|
||||
zuul_event_id=None):
|
||||
log = get_annotated_logger(self.log, zuul_event_id)
|
||||
self.gl_client.update_mr(
|
||||
project_name, mr_number, zuul_event_id=zuul_event_id,
|
||||
add_labels=','.join(labels),
|
||||
remove_labels=','.join(unlabels))
|
||||
log.debug("Added labels %s to, and removed labels %s from %s#%s",
|
||||
labels, unlabels, project_name, mr_number)
|
||||
|
||||
|
||||
class GitlabWebController(BaseWebController):
|
||||
|
||||
|
@ -86,6 +86,7 @@ class GitlabTriggerEvent(TriggerEvent):
|
||||
self.title = None
|
||||
self.action = None
|
||||
self.labels = []
|
||||
self.unlabels = []
|
||||
self.change_number = None
|
||||
self.merge_request_description_changed = None
|
||||
self.tag = None
|
||||
@ -96,6 +97,7 @@ class GitlabTriggerEvent(TriggerEvent):
|
||||
d["title"] = self.title
|
||||
d["action"] = self.action
|
||||
d["labels"] = self.labels
|
||||
d["unlabels"] = self.unlabels
|
||||
d["change_number"] = self.change_number
|
||||
d["merge_request_description_changed"] = \
|
||||
self.merge_request_description_changed
|
||||
@ -108,6 +110,7 @@ class GitlabTriggerEvent(TriggerEvent):
|
||||
self.title = d["title"]
|
||||
self.action = d["action"]
|
||||
self.labels = d["labels"]
|
||||
self.unlabels = d.get("unlabels", [])
|
||||
self.change_number = d["change_number"]
|
||||
self.merge_request_description_changed = \
|
||||
d["merge_request_description_changed"]
|
||||
@ -122,6 +125,8 @@ class GitlabTriggerEvent(TriggerEvent):
|
||||
r.append("mr:%s" % self.change_number)
|
||||
if self.labels:
|
||||
r.append("labels:%s" % ', '.join(self.labels))
|
||||
if self.unlabels:
|
||||
r.append("unlabels:%s" % ', '.join(self.unlabels))
|
||||
return ' '.join(r)
|
||||
|
||||
def isPatchsetCreated(self):
|
||||
@ -136,7 +141,8 @@ class GitlabTriggerEvent(TriggerEvent):
|
||||
class GitlabEventFilter(EventFilter):
|
||||
def __init__(
|
||||
self, connection_name, trigger, types=None, actions=None,
|
||||
comments=None, refs=None, labels=None, ignore_deletes=True):
|
||||
comments=None, refs=None, labels=None, unlabels=None,
|
||||
ignore_deletes=True):
|
||||
super().__init__(connection_name, trigger)
|
||||
self._types = types or []
|
||||
self.types = [re.compile(x) for x in self._types]
|
||||
@ -146,6 +152,7 @@ class GitlabEventFilter(EventFilter):
|
||||
self._refs = refs or []
|
||||
self.refs = [re.compile(x) for x in self._refs]
|
||||
self.labels = labels or []
|
||||
self.unlabels = unlabels or []
|
||||
self.ignore_deletes = ignore_deletes
|
||||
|
||||
def __repr__(self):
|
||||
@ -164,6 +171,8 @@ class GitlabEventFilter(EventFilter):
|
||||
ret += ' ignore_deletes: %s' % self.ignore_deletes
|
||||
if self.labels:
|
||||
ret += ' labels: %s' % ', '.join(self.labels)
|
||||
if self.unlabels:
|
||||
ret += ' unlabels: %s' % ', '.join(self.unlabels)
|
||||
ret += '>'
|
||||
|
||||
return ret
|
||||
@ -210,6 +219,10 @@ class GitlabEventFilter(EventFilter):
|
||||
if not set(event.labels).intersection(set(self.labels)):
|
||||
return False
|
||||
|
||||
if self.unlabels:
|
||||
if not set(event.unlabels).intersection(set(self.unlabels)):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -21,6 +21,7 @@ from zuul.model import MERGER_MERGE_RESOLVE, MERGER_MERGE, MERGER_MAP, \
|
||||
MERGER_SQUASH_MERGE
|
||||
from zuul.lib.logutil import get_annotated_logger
|
||||
from zuul.driver.gitlab.gitlabsource import GitlabSource
|
||||
from zuul.driver.util import scalar_or_list
|
||||
from zuul.exceptions import MergeFailure
|
||||
|
||||
|
||||
@ -42,6 +43,12 @@ class GitlabReporter(BaseReporter):
|
||||
self._create_comment = self.config.get('comment', True)
|
||||
self._approval = self.config.get('approval', None)
|
||||
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."""
|
||||
@ -57,6 +64,8 @@ class GitlabReporter(BaseReporter):
|
||||
self.addMRComment(item)
|
||||
if self._approval is not None:
|
||||
self.setApproval(item)
|
||||
if self._labels or self._unlabels:
|
||||
self.setLabels(item)
|
||||
if self._merge:
|
||||
self.mergeMR(item)
|
||||
if not item.change.is_merged:
|
||||
@ -83,6 +92,16 @@ class GitlabReporter(BaseReporter):
|
||||
self.connection.approveMR(project, mr_number, patchset,
|
||||
self._approval, event=item.event)
|
||||
|
||||
def setLabels(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, labels: %s, unlabels: %s',
|
||||
item.change, self.config, self._labels, self._unlabels)
|
||||
self.connection.updateMRLabels(project, mr_number,
|
||||
self._labels, self._unlabels,
|
||||
zuul_event_id=item.event)
|
||||
|
||||
def mergeMR(self, item):
|
||||
project = item.change.project.name
|
||||
mr_number = item.change.number
|
||||
@ -120,5 +139,7 @@ def getSchema():
|
||||
'comment': bool,
|
||||
'approval': bool,
|
||||
'merge': bool,
|
||||
'label': scalar_or_list(str),
|
||||
'unlabel': scalar_or_list(str),
|
||||
})
|
||||
return gitlab_reporter
|
||||
|
@ -34,6 +34,7 @@ class GitlabTrigger(BaseTrigger):
|
||||
comments=to_list(trigger.get('comment')),
|
||||
refs=to_list(trigger.get('ref')),
|
||||
labels=to_list(trigger.get('labels')),
|
||||
unlabels=to_list(trigger.get('unlabels')),
|
||||
)
|
||||
efilters.append(f)
|
||||
return efilters
|
||||
@ -53,6 +54,7 @@ def getSchema():
|
||||
'action': scalar_or_list(str),
|
||||
'comment': scalar_or_list(str),
|
||||
'ref': scalar_or_list(str),
|
||||
'labels': scalar_or_list(str)
|
||||
'labels': scalar_or_list(str),
|
||||
'unlabels': scalar_or_list(str),
|
||||
}
|
||||
return gitlab_trigger
|
||||
|
Loading…
x
Reference in New Issue
Block a user