From 981dfa50faf95f0248ca9490e05def3ab46a6309 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Wed, 30 Aug 2017 16:56:48 -0500 Subject: [PATCH] Support regexes in channel config yaml With this change, the channel config yaml file can now be configured to support regular expressions. Any value in any section may be prefixed with `^` to denote that it is to be treated as a regular expression [1]. Start and end ^anchors$ are implicit (so add `.*` if needed). For example, given the following paragraph in the channel config yaml: openstack-foo: events: - patchset-created - change-merged projects: - openstack/foo - ^openstack/foo-.* - openstack/oslo.foo branches: - master - ^stable/(newton|ocata|pike) ...messages will be posted to #openstack-foo for events coming in from project openstack/foo, openstack/foo-one, openstack/foo-bar, etc.; on branches master, stable/newton, stable/ocata, or stable/pike. Behavior is unchanged for values not prefixed with `^`. [1] This paradigm cribbed from gerrit's search functionality: https://review.openstack.org/Documentation/user-search.html#path Change-Id: I97cb8faa72600bd1bd9792bb6bb59a3b652ec389 --- doc/source/installation.rst | 19 +++++--- gerritbot/bot.py | 80 +++++++++++++++++++++++++------- gerritbot/tests/unit/test_bot.py | 26 +++-------- 3 files changed, 83 insertions(+), 42 deletions(-) diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 6a23604..1c4c5d7 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -19,36 +19,41 @@ when starting the bot. It should look like:: port=6667 force_ssl=True or False (Defaults to False) server_password=SERVERPASS - channel_config=/path/to/yaml/config - + channel_config=/path/to/yaml/config (See below) + [gerrit] user=gerrit2 key=/path/to/id_rsa host=review.example.com port=29418 -The second configures the IRC channels and the events and projects that each -channel is interested in. This config file is written in yaml and should look -like:: +The second, referenced by ``[ircbot]channel_config`` in the above, configures +the IRC channels and the events and projects that each channel is interested +in. This config file is written in yaml and should look like:: example-channel1: events: - patchset-created - change-merged + - ^x-(crvw|vrif)-(plus|minus)-2$ projects: - example/project1 - example/project2 branches: - master - development + example-channel2: events: - change-merged projects: - - example/project3 - - example/project4 + - ^example/project[34]$ + - ^example/interesting- branches: - master + - ^stable/(newton|ocata|pike)$ + +Denote regular expressions using the prefix ``^``. Running ======= diff --git a/gerritbot/bot.py b/gerritbot/bot.py index db3572c..fc38ac0 100755 --- a/gerritbot/bot.py +++ b/gerritbot/bot.py @@ -49,8 +49,10 @@ openstack-dev: projects: - openstack/nova - openstack/swift + - ^openstack/fuel-.* branches: - master + - ^stable/(newton|ocata|pike) """ import ConfigParser @@ -218,8 +220,8 @@ class Gerrit(threading.Thread): for approval in data.get('approvals', []): if (approval['type'] == 'VRIF' and approval['value'] == '-2' - and channel in self.channel_config.events.get( - 'x-vrif-minus-2', set())): + and channel in self._channels_for('events', + 'x-vrif-minus-2')): msg = 'Verification of a change to %s failed: %s %s' % ( data['change']['project'], data['change']['subject'], @@ -228,8 +230,8 @@ class Gerrit(threading.Thread): self.ircbot.send(channel, msg) if (approval['type'] == 'VRIF' and approval['value'] == '2' - and channel in self.channel_config.events.get( - 'x-vrif-plus-2', set())): + and channel in self._channels_for('events', + 'x-vrif-plus-2')): msg = 'Verification of a change to %s succeeded: %s %s' % ( data['change']['project'], data['change']['subject'], @@ -238,8 +240,8 @@ class Gerrit(threading.Thread): self.ircbot.send(channel, msg) if (approval['type'] == 'CRVW' and approval['value'] == '-2' - and channel in self.channel_config.events.get( - 'x-crvw-minus-2', set())): + and channel in self._channels_for('events', + 'x-crvw-minus-2')): msg = 'A change to %s has been rejected: %s %s' % ( data['change']['project'], data['change']['subject'], @@ -248,8 +250,8 @@ class Gerrit(threading.Thread): self.ircbot.send(channel, msg) if (approval['type'] == 'CRVW' and approval['value'] == '2' - and channel in self.channel_config.events.get( - 'x-crvw-plus-2', set())): + and channel in self._channels_for('events', + 'x-crvw-plus-2')): msg = 'A change to %s has been approved: %s %s' % ( data['change']['project'], data['change']['subject'], @@ -266,17 +268,63 @@ class Gerrit(threading.Thread): self.log.info('Compiled Message %s: %s' % (channel, msg)) self.ircbot.send(channel, msg) + def _channels_for(self, section, datakey): + """Get a set of channel names for a given data value. + + Finds all the channels that care about the specified datakey for a + given channel_config section. If the channel config key starts with + '^', datakey is matched by regex; otherwise it is matched by string + equality. For example, given input data: + + openstack-dev: + projects: + - openstack/foo-bar + + openstack-infra: + projects: + - ^openstack/foo-.*$ + + openstack-sdks: + projects: + - openstack/foo + + ...the call: + + _channels_for('projects', 'openstack/foo-bar') + + ...will return the set: + + {'#openstack-dev', '#openstack-infra'} + + :param str section: The channel_config section to inspect ('projects', + 'events', or 'branches') + :param str datakey: The key into the section, from the source data. + E.g. for section 'projects', the key would be the + project name (data['change']['project']). + """ + ret = set() + for key, chanset in getattr(self.channel_config, section, {}).items(): + for channel in chanset or set(): + if key.startswith('^'): + if re.search(key, datakey): + ret.add(channel) + else: + if key == datakey: + ret.add(channel) + return ret + def _read(self, data): try: - if data['type'] == 'ref-updated': - channel_set = self.channel_config.events.get('ref-updated') + # We only consider event (not project/branch) filters for these. + event_only_types = ('ref-updated',) + if data['type'] in event_only_types: + channel_set = self._channels_for('events', data['type']) else: - channel_set = (self.channel_config.projects.get( - data['change']['project'], set()) & - self.channel_config.events.get( - data['type'], set()) & - self.channel_config.branches.get( - data['change']['branch'], set())) + channel_set = ( + self._channels_for('projects', data['change']['project']) & + self._channels_for('events', data['type']) & + self._channels_for('branches', data['change']['branch']) + ) except KeyError: # The data we care about was not present, no channels want # this event. diff --git a/gerritbot/tests/unit/test_bot.py b/gerritbot/tests/unit/test_bot.py index b516f8b..1c14498 100644 --- a/gerritbot/tests/unit/test_bot.py +++ b/gerritbot/tests/unit/test_bot.py @@ -23,10 +23,7 @@ openstack-dev: events: - patchset-created - change-merged - - x-vrif-minus-2 - - x-vrif-plus-2 - - x-crvw-minus-2 - - x-crvw-plus-2 + - ^x-(crvw|vrif)-(plus|minus)-2$ projects: - openstack/nova - openstack/swift @@ -39,14 +36,9 @@ openstack-infra: - change-merged - comment-added - ref-updated - - x-vrif-minus-2 - - x-vrif-plus-2 - - x-crvw-minus-2 - - x-crvw-plus-2 + - ^x-(crvw|vrif)-(plus|minus)-2$ projects: - - openstack/gerritbot - - openstack/nova - - openstack/swift + - ^openstack/ branches: - master - stable/queens @@ -80,21 +72,17 @@ class ChannelConfigTestCase(testtools.TestCase): 'comment-added': {'#openstack-infra'}, 'patchset-created': expected_channels, 'ref-updated': {'#openstack-infra'}, - 'x-crvw-minus-2': expected_channels, - 'x-crvw-plus-2': expected_channels, - 'x-vrif-minus-2': expected_channels, - 'x-vrif-plus-2': expected_channels, + '^x-(crvw|vrif)-(plus|minus)-2$': expected_channels, }, channel_config.events) def test_projects(self): channel_config = bot.ChannelConfig(yaml.load(CHANNEL_CONFIG_YAML)) - expected_channels = {'#openstack-dev', '#openstack-infra'} self.assertEqual( { - 'openstack/gerritbot': {'#openstack-infra'}, - 'openstack/nova': expected_channels, - 'openstack/swift': expected_channels, + '^openstack/': {'#openstack-infra'}, + 'openstack/nova': {'#openstack-dev'}, + 'openstack/swift': {'#openstack-dev'}, }, channel_config.projects)