zuul/zuul/driver/gitlab/gitlabmodel.py
James E. Blair 336bb21386 Don't reconfigure after every gitlab merge
The gitlab driver sets change.files to None on branch push events.
Like every other system, it also emits branch push events after
merging merge requests.  When the scheduler encounters a branch
updated event with files.None, it assumes the worst and performs
a tenant reconfiguration.  This means that Zuul performs a tenant
reconfiguration after every single MR is merged.

Gitlab supplies a list of changed files on push events.  This change
modifies the gitlab driver to do that.  This mirrors what is done
in the github driver.

Change-Id: Ia6651048eea7f183ee0a65b6e8de83fdf892c9a7
2022-02-24 09:57:07 -08:00

279 lines
9.2 KiB
Python

# Copyright 2019 Red Hat, Inc.
# Copyright 2022 Acme Gating, LLC
#
# 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 re
from zuul.model import Change, TriggerEvent, EventFilter, RefFilter
EMPTY_GIT_REF = '0' * 40 # git sha of all zeros, used during creates/deletes
class MergeRequest(Change):
def __init__(self, project):
super(MergeRequest, self).__init__(project)
self.updated_at = None
self.approved = None
self.labels = None
self.merge_status = None
self.mr = None
self.title = None
def __repr__(self):
r = ['<Change 0x%x' % id(self)]
if self.project:
r.append('project: %s' % self.project)
if self.number:
r.append('number: %s' % self.number)
if self.patchset:
r.append('patchset: %s' % self.patchset)
if self.updated_at:
r.append('updated: %s' % self.updated_at)
if self.is_merged:
r.append('state: merged')
if self.open:
r.append('state: open')
if self.approved:
r.append('approval: approved')
if self.labels:
r.append('labels: %s' % ', '.join(self.labels))
return ' '.join(r) + '>'
def serialize(self):
d = super().serialize()
d.update({
"updated_at": self.updated_at,
"approved": self.approved,
"labels": self.labels,
"merge_status": self.merge_status,
"mr": self.mr,
"title": self.title,
})
return d
def deserialize(self, data):
super().deserialize(data)
self.updated_at = data.get("updated_at")
self.approved = data.get("approved")
self.labels = data.get("labels")
self.merge_status = data.get("merge_status")
self.mr = data.get("mr")
self.title = data.get("title")
def isUpdateOf(self, other):
if (self.project == other.project and
hasattr(other, 'number') and self.number == other.number and
hasattr(other, 'updated_at') and
self.updated_at > other.updated_at):
return True
return False
class GitlabTriggerEvent(TriggerEvent):
def __init__(self):
super(GitlabTriggerEvent, self).__init__()
self.trigger_name = 'gitlab'
self.title = None
self.action = None
self.labels = []
self.unlabels = []
self.change_number = None
self.merge_request_description_changed = None
self.tag = None
self.commits = []
self.total_commits_count = 0
def toDict(self):
d = super().toDict()
d["trigger_name"] = self.trigger_name
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
d["tag"] = self.tag
d["commits"] = self.commits
d["total_commits_count"] = self.total_commits_count
return d
def updateFromDict(self, d):
super().updateFromDict(d)
self.trigger_name = d["trigger_name"]
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"]
self.tag = d["tag"]
self.commits = d.get("commits", [])
self.total_commits_count = d.get("total_commits_count",
len(self.commits))
def _repr(self):
r = [super(GitlabTriggerEvent, self)._repr()]
if self.action:
r.append("action:%s" % self.action)
r.append("project:%s" % self.project_name)
if self.change_number:
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):
if self.type == 'gl_merge_request':
return self.action in ['opened', 'changed']
return False
def isMessageChanged(self):
return bool(self.merge_request_description_changed)
class GitlabEventFilter(EventFilter):
def __init__(
self, connection_name, trigger, types=None, actions=None,
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]
self.actions = actions or []
self._comments = comments or []
self.comments = [re.compile(x) for x in self._comments]
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):
ret = '<GitlabEventFilter'
ret += ' connection: %s' % self.connection_name
if self._types:
ret += ' types: %s' % ', '.join(self._types)
if self.actions:
ret += ' actions: %s' % ', '.join(self.actions)
if self._comments:
ret += ' comments: %s' % ', '.join(self._comments)
if self._refs:
ret += ' refs: %s' % ', '.join(self._refs)
if self.ignore_deletes:
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
def matches(self, event, change):
if not super().matches(event, change):
return False
matches_type = False
for etype in self.types:
if etype.match(event.type):
matches_type = True
if self.types and not matches_type:
return False
matches_ref = False
if event.ref is not None:
for ref in self.refs:
if ref.match(event.ref):
matches_ref = True
if self.refs and not matches_ref:
return False
if self.ignore_deletes and event.newrev == EMPTY_GIT_REF:
# If the updated ref has an empty git sha (all 0s),
# then the ref is being deleted
return False
matches_action = False
for action in self.actions:
if (event.action == action):
matches_action = True
if self.actions and not matches_action:
return False
matches_comment_re = False
for comment_re in self.comments:
if (event.comment is not None and
comment_re.search(event.comment)):
matches_comment_re = True
if self.comments and not matches_comment_re:
return False
if self.labels:
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
# The RefFilter should be understood as RequireFilter (it maps to
# pipeline requires definition)
class GitlabRefFilter(RefFilter):
def __init__(self, connection_name, open=None, merged=None, approved=None,
labels=None):
RefFilter.__init__(self, connection_name)
self.open = open
self.merged = merged
self.approved = approved
self.labels = labels or []
def __repr__(self):
ret = '<GitlabRefFilter connection_name: %s ' % self.connection_name
if self.open is not None:
ret += ' open: %s' % self.open
if self.merged is not None:
ret += ' merged: %s' % self.merged
if self.approved is not None:
ret += ' approved: %s' % self.approved
if self.labels:
ret += ' labels: %s' % ', '.join(self.labels)
ret += '>'
return ret
def matches(self, change):
if self.open is not None:
if change.open != self.open:
return False
if self.merged is not None:
if change.is_merged != self.merged:
return False
if self.approved is not None:
if change.approved != self.approved:
return False
if self.labels:
if not set(self.labels).issubset(set(change.labels)):
return False
return True