Merge "Ansible launcher: use .txt as logfile extension"
diff --git a/doc/source/zuul.rst b/doc/source/zuul.rst
index 07b777a..be9570c 100644
--- a/doc/source/zuul.rst
+++ b/doc/source/zuul.rst
@@ -399,11 +399,12 @@
   approval matching all specified requirements.
 
     *username*
-    If present, an approval from this username is required.
+    If present, an approval from this username is required.  It is
+    treated as a regular expression.
 
     *email*
     If present, an approval with this email address is required.  It
-    is treated as a regular expression as above.
+    is treated as a regular expression.
 
     *email-filter* (deprecated)
     A deprecated alternate spelling of *email*.  Only one of *email* or
diff --git a/tests/fixtures/layout-requirement-username.yaml b/tests/fixtures/layout-requirement-username.yaml
index 7a549f0..f9e6477 100644
--- a/tests/fixtures/layout-requirement-username.yaml
+++ b/tests/fixtures/layout-requirement-username.yaml
@@ -3,7 +3,7 @@
     manager: IndependentPipelineManager
     require:
       approval:
-        - username: jenkins
+        - username: ^(jenkins|zuul)$
     trigger:
       gerrit:
         - event: comment-added
diff --git a/zuul/launcher/ansiblelaunchserver.py b/zuul/launcher/ansiblelaunchserver.py
index b792a2c..d6f9174 100644
--- a/zuul/launcher/ansiblelaunchserver.py
+++ b/zuul/launcher/ansiblelaunchserver.py
@@ -502,6 +502,8 @@
         self.termination_queue = termination_queue
         self.keep_jobdir = keep_jobdir
         self.running_job_lock = threading.Lock()
+        self.pending_registration = False
+        self.registration_lock = threading.Lock()
         self._get_job_lock = threading.Lock()
         self._got_job = False
         self._job_complete_event = threading.Event()
@@ -625,6 +627,8 @@
                 self._got_job = False
 
     def _runGearman(self):
+        if self.pending_registration:
+            self.register()
         with self._get_job_lock:
             try:
                 job = self.worker.getJob()
@@ -658,13 +662,23 @@
         return ret
 
     def register(self):
-        if self._running_job:
+        if not self.registration_lock.acquire(False):
+            self.log.debug("Registration already in progress")
             return
-        new_functions = set()
-        for job in self.jobs.values():
-            new_functions |= self.generateFunctionNames(job)
-        self.worker.sendMassDo(new_functions)
-        self.registered_functions = new_functions
+        try:
+            if self._running_job:
+                self.pending_registration = True
+                self.log.debug("Ignoring registration due to running job")
+                return
+            self.log.debug("Updating registration")
+            self.pending_registration = False
+            new_functions = set()
+            for job in self.jobs.values():
+                new_functions |= self.generateFunctionNames(job)
+            self.worker.sendMassDo(new_functions)
+            self.registered_functions = new_functions
+        finally:
+            self.registration_lock.release()
 
     def abortRunningJob(self):
         self._aborted_job = True
@@ -1108,7 +1122,7 @@
 
             play = dict(hosts='node', name='Job body',
                         tasks=tasks)
-            playbook.write(yaml.dump([play], default_flow_style=False))
+            playbook.write(yaml.safe_dump([play], default_flow_style=False))
 
         early_publishers, late_publishers = self._transformPublishers(jjb_job)
 
@@ -1135,7 +1149,7 @@
 
             play = dict(hosts='node', name='Publishers',
                         tasks=tasks)
-            playbook.write(yaml.dump([play], default_flow_style=False))
+            playbook.write(yaml.safe_dump([play], default_flow_style=False))
 
         with open(jobdir.config, 'w') as config:
             config.write('[defaults]\n')
@@ -1164,13 +1178,22 @@
         self.abortRunningProc(proc)
 
     def runAnsiblePlaybook(self, jobdir, timeout):
+        # Set LOGNAME env variable so Ansible log_path log reports
+        # the correct user.
+        env_copy = os.environ.copy()
+        env_copy['LOGNAME'] = 'zuul'
+
+        cmd = ['ansible-playbook', jobdir.playbook,
+               '-e', 'timeout=%s' % timeout, '-v']
+        self.log.debug("Ansible command: %s" % (cmd,))
+
         self.ansible_job_proc = subprocess.Popen(
-            ['ansible-playbook', jobdir.playbook,
-             '-e', 'timeout=%s' % timeout, '-v'],
+            cmd,
             cwd=jobdir.ansible_root,
             stdout=subprocess.PIPE,
             stderr=subprocess.STDOUT,
             preexec_fn=os.setsid,
+            env=env_copy,
         )
         ret = None
         watchdog = Watchdog(timeout + ANSIBLE_WATCHDOG_GRACE,
@@ -1194,13 +1217,22 @@
         return ret == 0
 
     def runAnsiblePostPlaybook(self, jobdir, success):
+        # Set LOGNAME env variable so Ansible log_path log reports
+        # the correct user.
+        env_copy = os.environ.copy()
+        env_copy['LOGNAME'] = 'zuul'
+
+        cmd = ['ansible-playbook', jobdir.post_playbook,
+               '-e', 'success=%s' % success, '-v']
+        self.log.debug("Ansible post command: %s" % (cmd,))
+
         self.ansible_post_proc = subprocess.Popen(
-            ['ansible-playbook', jobdir.post_playbook,
-             '-e', 'success=%s' % success, '-v'],
+            cmd,
             cwd=jobdir.ansible_root,
             stdout=subprocess.PIPE,
             stderr=subprocess.STDOUT,
             preexec_fn=os.setsid,
+            env=env_copy,
         )
         ret = None
         watchdog = Watchdog(ANSIBLE_DEFAULT_POST_TIMEOUT,
diff --git a/zuul/model.py b/zuul/model.py
index ca8f098..46b0b98 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -1079,7 +1079,7 @@
         for a in approvals:
             for k, v in a.items():
                 if k == 'username':
-                    pass
+                    a['username'] = re.compile(v)
                 elif k in ['email', 'email-filter']:
                     a['email'] = re.compile(v)
                 elif k == 'newer-than':
@@ -1098,7 +1098,7 @@
         by = approval.get('by', {})
         for k, v in rapproval.items():
             if k == 'username':
-                if (by.get('username', '') != v):
+                if (not v.search(by.get('username', ''))):
                         return False
             elif k == 'email':
                 if (not v.search(by.get('email', ''))):
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index f08612d..a30b735 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -1105,10 +1105,11 @@
             return
         if build.end_time and build.start_time and build.result:
             duration = build.end_time - build.start_time
-        try:
-            self.time_database.update(build.job.name, duration, build.result)
-        except Exception:
-            self.log.exception("Exception recording build time:")
+            try:
+                self.time_database.update(
+                    build.job.name, duration, build.result)
+            except Exception:
+                self.log.exception("Exception recording build time:")
         pipeline.manager.onBuildCompleted(event.build)
 
     def _doMergeCompletedEvent(self, event):