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
This commit is contained in:
Eric Fried 2017-08-30 16:56:48 -05:00 committed by John L. Villalovos
parent fc4041f6a3
commit 981dfa50fa
3 changed files with 83 additions and 42 deletions

View File

@ -19,36 +19,41 @@ when starting the bot. It should look like::
port=6667 port=6667
force_ssl=True or False (Defaults to False) force_ssl=True or False (Defaults to False)
server_password=SERVERPASS server_password=SERVERPASS
channel_config=/path/to/yaml/config channel_config=/path/to/yaml/config (See below)
[gerrit] [gerrit]
user=gerrit2 user=gerrit2
key=/path/to/id_rsa key=/path/to/id_rsa
host=review.example.com host=review.example.com
port=29418 port=29418
The second configures the IRC channels and the events and projects that each The second, referenced by ``[ircbot]channel_config`` in the above, configures
channel is interested in. This config file is written in yaml and should look the IRC channels and the events and projects that each channel is interested
like:: in. This config file is written in yaml and should look like::
example-channel1: example-channel1:
events: events:
- patchset-created - patchset-created
- change-merged - change-merged
- ^x-(crvw|vrif)-(plus|minus)-2$
projects: projects:
- example/project1 - example/project1
- example/project2 - example/project2
branches: branches:
- master - master
- development - development
example-channel2: example-channel2:
events: events:
- change-merged - change-merged
projects: projects:
- example/project3 - ^example/project[34]$
- example/project4 - ^example/interesting-
branches: branches:
- master - master
- ^stable/(newton|ocata|pike)$
Denote regular expressions using the prefix ``^``.
Running Running
======= =======

View File

@ -49,8 +49,10 @@ openstack-dev:
projects: projects:
- openstack/nova - openstack/nova
- openstack/swift - openstack/swift
- ^openstack/fuel-.*
branches: branches:
- master - master
- ^stable/(newton|ocata|pike)
""" """
import ConfigParser import ConfigParser
@ -218,8 +220,8 @@ class Gerrit(threading.Thread):
for approval in data.get('approvals', []): for approval in data.get('approvals', []):
if (approval['type'] == 'VRIF' and approval['value'] == '-2' if (approval['type'] == 'VRIF' and approval['value'] == '-2'
and channel in self.channel_config.events.get( and channel in self._channels_for('events',
'x-vrif-minus-2', set())): 'x-vrif-minus-2')):
msg = 'Verification of a change to %s failed: %s %s' % ( msg = 'Verification of a change to %s failed: %s %s' % (
data['change']['project'], data['change']['project'],
data['change']['subject'], data['change']['subject'],
@ -228,8 +230,8 @@ class Gerrit(threading.Thread):
self.ircbot.send(channel, msg) self.ircbot.send(channel, msg)
if (approval['type'] == 'VRIF' and approval['value'] == '2' if (approval['type'] == 'VRIF' and approval['value'] == '2'
and channel in self.channel_config.events.get( and channel in self._channels_for('events',
'x-vrif-plus-2', set())): 'x-vrif-plus-2')):
msg = 'Verification of a change to %s succeeded: %s %s' % ( msg = 'Verification of a change to %s succeeded: %s %s' % (
data['change']['project'], data['change']['project'],
data['change']['subject'], data['change']['subject'],
@ -238,8 +240,8 @@ class Gerrit(threading.Thread):
self.ircbot.send(channel, msg) self.ircbot.send(channel, msg)
if (approval['type'] == 'CRVW' and approval['value'] == '-2' if (approval['type'] == 'CRVW' and approval['value'] == '-2'
and channel in self.channel_config.events.get( and channel in self._channels_for('events',
'x-crvw-minus-2', set())): 'x-crvw-minus-2')):
msg = 'A change to %s has been rejected: %s %s' % ( msg = 'A change to %s has been rejected: %s %s' % (
data['change']['project'], data['change']['project'],
data['change']['subject'], data['change']['subject'],
@ -248,8 +250,8 @@ class Gerrit(threading.Thread):
self.ircbot.send(channel, msg) self.ircbot.send(channel, msg)
if (approval['type'] == 'CRVW' and approval['value'] == '2' if (approval['type'] == 'CRVW' and approval['value'] == '2'
and channel in self.channel_config.events.get( and channel in self._channels_for('events',
'x-crvw-plus-2', set())): 'x-crvw-plus-2')):
msg = 'A change to %s has been approved: %s %s' % ( msg = 'A change to %s has been approved: %s %s' % (
data['change']['project'], data['change']['project'],
data['change']['subject'], data['change']['subject'],
@ -266,17 +268,63 @@ class Gerrit(threading.Thread):
self.log.info('Compiled Message %s: %s' % (channel, msg)) self.log.info('Compiled Message %s: %s' % (channel, msg))
self.ircbot.send(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): def _read(self, data):
try: try:
if data['type'] == 'ref-updated': # We only consider event (not project/branch) filters for these.
channel_set = self.channel_config.events.get('ref-updated') event_only_types = ('ref-updated',)
if data['type'] in event_only_types:
channel_set = self._channels_for('events', data['type'])
else: else:
channel_set = (self.channel_config.projects.get( channel_set = (
data['change']['project'], set()) & self._channels_for('projects', data['change']['project']) &
self.channel_config.events.get( self._channels_for('events', data['type']) &
data['type'], set()) & self._channels_for('branches', data['change']['branch'])
self.channel_config.branches.get( )
data['change']['branch'], set()))
except KeyError: except KeyError:
# The data we care about was not present, no channels want # The data we care about was not present, no channels want
# this event. # this event.

View File

@ -23,10 +23,7 @@ openstack-dev:
events: events:
- patchset-created - patchset-created
- change-merged - change-merged
- x-vrif-minus-2 - ^x-(crvw|vrif)-(plus|minus)-2$
- x-vrif-plus-2
- x-crvw-minus-2
- x-crvw-plus-2
projects: projects:
- openstack/nova - openstack/nova
- openstack/swift - openstack/swift
@ -39,14 +36,9 @@ openstack-infra:
- change-merged - change-merged
- comment-added - comment-added
- ref-updated - ref-updated
- x-vrif-minus-2 - ^x-(crvw|vrif)-(plus|minus)-2$
- x-vrif-plus-2
- x-crvw-minus-2
- x-crvw-plus-2
projects: projects:
- openstack/gerritbot - ^openstack/
- openstack/nova
- openstack/swift
branches: branches:
- master - master
- stable/queens - stable/queens
@ -80,21 +72,17 @@ class ChannelConfigTestCase(testtools.TestCase):
'comment-added': {'#openstack-infra'}, 'comment-added': {'#openstack-infra'},
'patchset-created': expected_channels, 'patchset-created': expected_channels,
'ref-updated': {'#openstack-infra'}, 'ref-updated': {'#openstack-infra'},
'x-crvw-minus-2': expected_channels, '^x-(crvw|vrif)-(plus|minus)-2$': expected_channels,
'x-crvw-plus-2': expected_channels,
'x-vrif-minus-2': expected_channels,
'x-vrif-plus-2': expected_channels,
}, },
channel_config.events) channel_config.events)
def test_projects(self): def test_projects(self):
channel_config = bot.ChannelConfig(yaml.load(CHANNEL_CONFIG_YAML)) channel_config = bot.ChannelConfig(yaml.load(CHANNEL_CONFIG_YAML))
expected_channels = {'#openstack-dev', '#openstack-infra'}
self.assertEqual( self.assertEqual(
{ {
'openstack/gerritbot': {'#openstack-infra'}, '^openstack/': {'#openstack-infra'},
'openstack/nova': expected_channels, 'openstack/nova': {'#openstack-dev'},
'openstack/swift': expected_channels, 'openstack/swift': {'#openstack-dev'},
}, },
channel_config.projects) channel_config.projects)