filter events by user email

On some setup we might restrict a pipeline to trusted users. The new
email_filter will matches the email coming from the event account
attribute.

In Gerrit, each event hold the account in a different field
name so I have simply added a map to find out the correct field.

email_filter works just like comment_filter, fields are ORed and are
considered to be regex.

Change-Id: I775f67b48d5f162106c024f94fe498a049b3fe94
Reviewed-on: https://review.openstack.org/17609
Reviewed-by: James E. Blair <corvus@inaugust.com>
Approved: Monty Taylor <mordred@inaugust.com>
Reviewed-by: Monty Taylor <mordred@inaugust.com>
Tested-by: Jenkins
diff --git a/doc/source/zuul.rst b/doc/source/zuul.rst
index 92b7a6f..416174d 100644
--- a/doc/source/zuul.rst
+++ b/doc/source/zuul.rst
@@ -246,6 +246,10 @@
   ``code-review: 2`` matches a ``+2`` vote on the code review category.
   Multiple approvals may be listed.
 
+  *email_filter*
+  This is used for any event.  It takes a regex applied on the performer
+  email. Example: ``email_filter: .*@example.org$``.
+
   *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/etc/layout.yaml-sample b/etc/layout.yaml-sample
index 2012e74..eec8553 100644
--- a/etc/layout.yaml-sample
+++ b/etc/layout.yaml-sample
@@ -8,6 +8,16 @@
     failure:
       verified: -1
 
+  - name: tests
+    manager: IndependentPipelineManager
+    trigger:
+     - event: patchset-created
+       email_filter: ^.*@example.org$
+    success:
+      verified: 1
+    failure:
+      verified: -1
+
   - name: post
     manager: IndependentPipelineManager
     trigger:
@@ -35,6 +45,8 @@
 projects:
   - name: example/project
     check:
+      - project-merge
+    tests:
       - project-merge:
         - project-test
     gate:
diff --git a/zuul/model.py b/zuul/model.py
index 1398131..9ec4a68 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -520,6 +520,8 @@
         # common
         self.type = None
         self.project_name = None
+        # Representation of the user account that performed the event.
+        self.account = None
         # patchset-created, comment-added, etc.
         self.change_number = None
         self.change_url = None
@@ -565,7 +567,7 @@
 
 class EventFilter(object):
     def __init__(self, types=[], branches=[], refs=[], approvals={},
-                 comment_filters=[]):
+                 comment_filters=[], email_filters=[]):
         self._types = types
         self._branches = branches
         self._refs = refs
@@ -573,6 +575,7 @@
         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.approvals = approvals
 
     def __repr__(self):
@@ -629,6 +632,19 @@
         if self.comment_filters and not matches_comment_filter:
             return False
 
+        # We better have an account provided by Gerrit to do
+        # email filtering.
+        if event.account is not None:
+            # email_filters are ORed
+            matches_email_filter = False
+            for email_filter in self.email_filters:
+                account_email = event.account.get('email')
+                if (account_email is not None and
+                        email_filter.search(account_email)):
+                    matches_email_filter = True
+            if self.email_filters and not matches_email_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 60e85f3..31a504e 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -99,7 +99,9 @@
                                 refs=toList(trigger.get('ref')),
                                 approvals=approvals,
                                 comment_filters=
-                                toList(trigger.get('comment_filter')))
+                                toList(trigger.get('comment_filter')),
+                                email_filters=
+                                toList(trigger.get('email_filter')))
                 manager.event_filters.append(f)
 
         for config_job in data['jobs']:
diff --git a/zuul/trigger/gerrit.py b/zuul/trigger/gerrit.py
index 3a8f104..892eb36 100644
--- a/zuul/trigger/gerrit.py
+++ b/zuul/trigger/gerrit.py
@@ -59,6 +59,24 @@
             event.ref = refupdate.get('refName')
             event.oldrev = refupdate.get('oldRev')
             event.newrev = refupdate.get('newRev')
+        # Map the event types to a field name holding a Gerrit
+        # account attribute. See Gerrit stream-event documentation
+        # in cmd-stream-events.html
+        accountfield_from_type = {
+            'patchset-created': 'uploader',
+            'change-abandoned': 'abandoner',
+            'change-restored': 'restorer',
+            'change-merged': 'submitter',
+            'comment-added': 'author',
+            'ref-updated': 'submitter',
+        }
+        try:
+            event.account = data.get(accountfield_from_type[event.type])
+        except KeyError:
+            self.log.error("Received unrecongized event type '%s' from Gerrit.\
+                    Can not get account information." % event.type)
+            event.account = None
+
         self.sched.addEvent(event)
         self.gerrit.eventDone()