Allow pipelines triggers to filter by username

Currently we can filter pipeline triggers by email address but not
username. This is an issue for users that have no email addresses
such as Jenkins.

This patch adds a new filter "username_filter" to the gerrit trigger
section.

Change-Id: I66680ab7e9e5ff49466269175c8fb54aef30e016
diff --git a/doc/source/zuul.rst b/doc/source/zuul.rst
index 0ec9f88..a03cc1c 100644
--- a/doc/source/zuul.rst
+++ b/doc/source/zuul.rst
@@ -323,6 +323,13 @@
     greedy matchers and to escapes dots!
     Example: ``email_filter: ^.*?@example\.org$``.
 
+    *username_filter*
+    This is used for any event.  It takes a regex applied on the performer
+    username, i.e. Gerrit account name.  If you want to specify several
+    username filters, you must use a YAML list.  Make sure to use non greedy
+    matchers and to escapes dots!
+    Example: ``username_filter: ^jenkins$``.
+
     *comment_filter*
     This is only used for ``comment-added`` events.  It accepts a list of
     regexes that are searched for in the comment string. If any of these
diff --git a/zuul/layoutvalidator.py b/zuul/layoutvalidator.py
index bc82501..d4ff143 100644
--- a/zuul/layoutvalidator.py
+++ b/zuul/layoutvalidator.py
@@ -44,6 +44,7 @@
                                    'ref-updated')),
                       'comment_filter': toList(str),
                       'email_filter': toList(str),
+                      'username_filter': toList(str),
                       'branch': toList(str),
                       'ref': toList(str),
                       'approval': toList(variable_dict),
diff --git a/zuul/model.py b/zuul/model.py
index b71552d..e62feed 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -837,17 +837,20 @@
 
 class EventFilter(object):
     def __init__(self, types=[], branches=[], refs=[], approvals={},
-                 comment_filters=[], email_filters=[], timespecs=[]):
+                 comment_filters=[], email_filters=[], username_filters=[],
+                 timespecs=[]):
         self._types = types
         self._branches = branches
         self._refs = refs
         self._comment_filters = comment_filters
         self._email_filters = email_filters
+        self._username_filters = username_filters
         self.types = [re.compile(x) for x in types]
         self.branches = [re.compile(x) for x in branches]
         self.refs = [re.compile(x) for x in refs]
         self.comment_filters = [re.compile(x) for x in comment_filters]
         self.email_filters = [re.compile(x) for x in email_filters]
+        self.username_filters = [re.compile(x) for x in username_filters]
         self.approvals = approvals
         self.timespecs = timespecs
 
@@ -867,6 +870,8 @@
             ret += ' comment_filters: %s' % ', '.join(self._comment_filters)
         if self._email_filters:
             ret += ' email_filters: %s' % ', '.join(self._email_filters)
+        if self._username_filters:
+            ret += ' username_filters: %s' % ', '.join(self._username_filters)
         if self.timespecs:
             ret += ' timespecs: %s' % ', '.join(self.timespecs)
         ret += '>'
@@ -924,6 +929,16 @@
             if self.email_filters and not matches_email_filter:
                 return False
 
+            # username_filters are ORed
+            account_username = event.account.get('username')
+            matches_username_filter = False
+            for username_filter in self.username_filters:
+                if (account_username is not None and
+                    username_filter.search(account_username)):
+                    matches_username_filter = True
+            if self.username_filters and not matches_username_filter:
+                return False
+
         # approvals are ANDed
         for category, value in self.approvals.items():
             matches_approval = False
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 96bd624..ea25948 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -217,7 +217,9 @@
                                     comment_filters=
                                     toList(trigger.get('comment_filter')),
                                     email_filters=
-                                    toList(trigger.get('email_filter')))
+                                    toList(trigger.get('email_filter')),
+                                    username_filters=
+                                    toList(trigger.get('username_filter')))
                     manager.event_filters.append(f)
             elif 'timer' in conf_pipeline['trigger']:
                 pipeline.trigger = self.triggers['timer']