Browse Source

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
tags/0.4.0^0
Eric Fried 1 year ago
parent
commit
981dfa50fa
3 changed files with 83 additions and 42 deletions
  1. 12
    7
      doc/source/installation.rst
  2. 64
    16
      gerritbot/bot.py
  3. 7
    19
      gerritbot/tests/unit/test_bot.py

+ 12
- 7
doc/source/installation.rst View File

@@ -19,36 +19,41 @@ when starting the bot. It should look like::
19 19
   port=6667
20 20
   force_ssl=True or False (Defaults to False)
21 21
   server_password=SERVERPASS
22
-  channel_config=/path/to/yaml/config
23
-  
22
+  channel_config=/path/to/yaml/config (See below)
23
+
24 24
   [gerrit]
25 25
   user=gerrit2
26 26
   key=/path/to/id_rsa
27 27
   host=review.example.com
28 28
   port=29418
29 29
 
30
-The second configures the IRC channels and the events and projects that each
31
-channel is interested in. This config file is written in yaml and should look
32
-like::
30
+The second, referenced by ``[ircbot]channel_config`` in the above, configures
31
+the IRC channels and the events and projects that each channel is interested
32
+in. This config file is written in yaml and should look like::
33 33
 
34 34
   example-channel1:
35 35
       events:
36 36
         - patchset-created
37 37
         - change-merged
38
+        - ^x-(crvw|vrif)-(plus|minus)-2$
38 39
       projects:
39 40
         - example/project1
40 41
         - example/project2
41 42
       branches:
42 43
         - master
43 44
         - development
45
+
44 46
   example-channel2:
45 47
       events:
46 48
         - change-merged
47 49
       projects:
48
-        - example/project3
49
-        - example/project4
50
+        - ^example/project[34]$
51
+        - ^example/interesting-
50 52
       branches:
51 53
         - master
54
+        - ^stable/(newton|ocata|pike)$
55
+
56
+Denote regular expressions using the prefix ``^``.
52 57
 
53 58
 Running
54 59
 =======

+ 64
- 16
gerritbot/bot.py View File

@@ -49,8 +49,10 @@ openstack-dev:
49 49
     projects:
50 50
       - openstack/nova
51 51
       - openstack/swift
52
+      - ^openstack/fuel-.*
52 53
     branches:
53 54
       - master
55
+      - ^stable/(newton|ocata|pike)
54 56
 """
55 57
 
56 58
 import ConfigParser
@@ -218,8 +220,8 @@ class Gerrit(threading.Thread):
218 220
 
219 221
         for approval in data.get('approvals', []):
220 222
             if (approval['type'] == 'VRIF' and approval['value'] == '-2'
221
-                and channel in self.channel_config.events.get(
222
-                    'x-vrif-minus-2', set())):
223
+                and channel in self._channels_for('events',
224
+                                                  'x-vrif-minus-2')):
223 225
                 msg = 'Verification of a change to %s failed: %s  %s' % (
224 226
                     data['change']['project'],
225 227
                     data['change']['subject'],
@@ -228,8 +230,8 @@ class Gerrit(threading.Thread):
228 230
                 self.ircbot.send(channel, msg)
229 231
 
230 232
             if (approval['type'] == 'VRIF' and approval['value'] == '2'
231
-                and channel in self.channel_config.events.get(
232
-                    'x-vrif-plus-2', set())):
233
+                and channel in self._channels_for('events',
234
+                                                  'x-vrif-plus-2')):
233 235
                 msg = 'Verification of a change to %s succeeded: %s  %s' % (
234 236
                     data['change']['project'],
235 237
                     data['change']['subject'],
@@ -238,8 +240,8 @@ class Gerrit(threading.Thread):
238 240
                 self.ircbot.send(channel, msg)
239 241
 
240 242
             if (approval['type'] == 'CRVW' and approval['value'] == '-2'
241
-                and channel in self.channel_config.events.get(
242
-                    'x-crvw-minus-2', set())):
243
+                and channel in self._channels_for('events',
244
+                                                  'x-crvw-minus-2')):
243 245
                 msg = 'A change to %s has been rejected: %s  %s' % (
244 246
                     data['change']['project'],
245 247
                     data['change']['subject'],
@@ -248,8 +250,8 @@ class Gerrit(threading.Thread):
248 250
                 self.ircbot.send(channel, msg)
249 251
 
250 252
             if (approval['type'] == 'CRVW' and approval['value'] == '2'
251
-                and channel in self.channel_config.events.get(
252
-                    'x-crvw-plus-2', set())):
253
+                and channel in self._channels_for('events',
254
+                                                  'x-crvw-plus-2')):
253 255
                 msg = 'A change to %s has been approved: %s  %s' % (
254 256
                     data['change']['project'],
255 257
                     data['change']['subject'],
@@ -266,17 +268,63 @@ class Gerrit(threading.Thread):
266 268
         self.log.info('Compiled Message %s: %s' % (channel, msg))
267 269
         self.ircbot.send(channel, msg)
268 270
 
271
+    def _channels_for(self, section, datakey):
272
+        """Get a set of channel names for a given data value.
273
+
274
+        Finds all the channels that care about the specified datakey for a
275
+        given channel_config section.  If the channel config key starts with
276
+        '^', datakey is matched by regex; otherwise it is matched by string
277
+        equality.  For example, given input data:
278
+
279
+            openstack-dev:
280
+              projects:
281
+                - openstack/foo-bar
282
+
283
+            openstack-infra:
284
+              projects:
285
+                - ^openstack/foo-.*$
286
+
287
+            openstack-sdks:
288
+              projects:
289
+                - openstack/foo
290
+
291
+        ...the call:
292
+
293
+            _channels_for('projects', 'openstack/foo-bar')
294
+
295
+        ...will return the set:
296
+
297
+            {'#openstack-dev', '#openstack-infra'}
298
+
299
+        :param str section: The channel_config section to inspect ('projects',
300
+                           'events', or 'branches')
301
+        :param str datakey: The key into the section, from the source data.
302
+                            E.g. for section 'projects', the key would be the
303
+                            project name (data['change']['project']).
304
+        """
305
+        ret = set()
306
+        for key, chanset in getattr(self.channel_config, section, {}).items():
307
+            for channel in chanset or set():
308
+                if key.startswith('^'):
309
+                    if re.search(key, datakey):
310
+                        ret.add(channel)
311
+                else:
312
+                    if key == datakey:
313
+                        ret.add(channel)
314
+        return ret
315
+
269 316
     def _read(self, data):
270 317
         try:
271
-            if data['type'] == 'ref-updated':
272
-                channel_set = self.channel_config.events.get('ref-updated')
318
+            # We only consider event (not project/branch) filters for these.
319
+            event_only_types = ('ref-updated',)
320
+            if data['type'] in event_only_types:
321
+                channel_set = self._channels_for('events', data['type'])
273 322
             else:
274
-                channel_set = (self.channel_config.projects.get(
275
-                    data['change']['project'], set()) &
276
-                    self.channel_config.events.get(
277
-                        data['type'], set()) &
278
-                    self.channel_config.branches.get(
279
-                        data['change']['branch'], set()))
323
+                channel_set = (
324
+                    self._channels_for('projects', data['change']['project']) &
325
+                    self._channels_for('events', data['type']) &
326
+                    self._channels_for('branches', data['change']['branch'])
327
+                )
280 328
         except KeyError:
281 329
             # The data we care about was not present, no channels want
282 330
             # this event.

+ 7
- 19
gerritbot/tests/unit/test_bot.py View File

@@ -23,10 +23,7 @@ openstack-dev:
23 23
     events:
24 24
       - patchset-created
25 25
       - change-merged
26
-      - x-vrif-minus-2
27
-      - x-vrif-plus-2
28
-      - x-crvw-minus-2
29
-      - x-crvw-plus-2
26
+      - ^x-(crvw|vrif)-(plus|minus)-2$
30 27
     projects:
31 28
       - openstack/nova
32 29
       - openstack/swift
@@ -39,14 +36,9 @@ openstack-infra:
39 36
       - change-merged
40 37
       - comment-added
41 38
       - ref-updated
42
-      - x-vrif-minus-2
43
-      - x-vrif-plus-2
44
-      - x-crvw-minus-2
45
-      - x-crvw-plus-2
39
+      - ^x-(crvw|vrif)-(plus|minus)-2$
46 40
     projects:
47
-      - openstack/gerritbot
48
-      - openstack/nova
49
-      - openstack/swift
41
+      - ^openstack/
50 42
     branches:
51 43
       - master
52 44
       - stable/queens
@@ -80,21 +72,17 @@ class ChannelConfigTestCase(testtools.TestCase):
80 72
                 'comment-added': {'#openstack-infra'},
81 73
                 'patchset-created': expected_channels,
82 74
                 'ref-updated': {'#openstack-infra'},
83
-                'x-crvw-minus-2': expected_channels,
84
-                'x-crvw-plus-2': expected_channels,
85
-                'x-vrif-minus-2': expected_channels,
86
-                'x-vrif-plus-2': expected_channels,
75
+                '^x-(crvw|vrif)-(plus|minus)-2$': expected_channels,
87 76
             },
88 77
             channel_config.events)
89 78
 
90 79
     def test_projects(self):
91 80
         channel_config = bot.ChannelConfig(yaml.load(CHANNEL_CONFIG_YAML))
92
-        expected_channels = {'#openstack-dev', '#openstack-infra'}
93 81
         self.assertEqual(
94 82
             {
95
-                'openstack/gerritbot': {'#openstack-infra'},
96
-                'openstack/nova': expected_channels,
97
-                'openstack/swift': expected_channels,
83
+                '^openstack/': {'#openstack-infra'},
84
+                'openstack/nova': {'#openstack-dev'},
85
+                'openstack/swift': {'#openstack-dev'},
98 86
             },
99 87
             channel_config.projects)
100 88
 

Loading…
Cancel
Save