Merge "Merge branch 'master' into feature/zuulv3" into feature/zuulv3
diff --git a/tests/base.py b/tests/base.py
index 680c2a7..357dd7a 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -2811,6 +2811,41 @@
             files)
         return before
 
+    def newTenantConfig(self, source_name):
+        """ Use this to update the tenant config file in tests
+
+        This will update self.tenant_config_file to point to a temporary file
+        for the duration of this particular test. The content of that file will
+        be taken from FIXTURE_DIR/source_name
+
+        After the test the original value of self.tenant_config_file will be
+        restored.
+
+        :arg str source_name: The path of the file under
+            FIXTURE_DIR that will be used to populate the new tenant
+            config file.
+        """
+        source_path = os.path.join(FIXTURE_DIR, source_name)
+        orig_tenant_config_file = self.tenant_config_file
+        with tempfile.NamedTemporaryFile(
+            delete=False, mode='wb') as new_tenant_config:
+            self.tenant_config_file = new_tenant_config.name
+            with open(source_path, mode='rb') as source_tenant_config:
+                new_tenant_config.write(source_tenant_config.read())
+        self.config['scheduler']['tenant_config'] = self.tenant_config_file
+        self.setupAllProjectKeys()
+        self.log.debug(
+            'tenant_config_file = {}'.format(self.tenant_config_file))
+
+        def _restoreTenantConfig():
+            self.log.debug(
+                'restoring tenant_config_file = {}'.format(
+                    orig_tenant_config_file))
+            os.unlink(self.tenant_config_file)
+            self.tenant_config_file = orig_tenant_config_file
+            self.config['scheduler']['tenant_config'] = orig_tenant_config_file
+        self.addCleanup(_restoreTenantConfig)
+
     def addEvent(self, connection, event):
 
         """Inject a Fake (Gerrit) event.
diff --git a/tests/fixtures/config/job-output/git/common-config/playbooks/job-output.yaml b/tests/fixtures/config/job-output/git/common-config/playbooks/job-output.yaml
new file mode 100644
index 0000000..332db87
--- /dev/null
+++ b/tests/fixtures/config/job-output/git/common-config/playbooks/job-output.yaml
@@ -0,0 +1,3 @@
+- hosts: all
+  tasks:
+    - shell: echo "Standard output test {{ zuul.executor.src_root }}"
diff --git a/tests/fixtures/config/job-output/git/common-config/zuul.yaml b/tests/fixtures/config/job-output/git/common-config/zuul.yaml
new file mode 100644
index 0000000..a83f0bc
--- /dev/null
+++ b/tests/fixtures/config/job-output/git/common-config/zuul.yaml
@@ -0,0 +1,27 @@
+- pipeline:
+    name: check
+    manager: independent
+    post-review: true
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        Verified: 1
+    failure:
+      gerrit:
+        Verified: -1
+
+- job:
+    name: base
+    parent: null
+
+- job:
+    parent: base
+    name: job-output
+
+- project:
+    name: org/project
+    check:
+      jobs:
+        - job-output
diff --git a/tests/fixtures/config/job-output/git/org_project/README b/tests/fixtures/config/job-output/git/org_project/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/job-output/git/org_project/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/job-output/main.yaml b/tests/fixtures/config/job-output/main.yaml
new file mode 100644
index 0000000..208e274
--- /dev/null
+++ b/tests/fixtures/config/job-output/main.yaml
@@ -0,0 +1,8 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - common-config
+        untrusted-projects:
+          - org/project
diff --git a/tests/fixtures/layout-delayed-repo-init.yaml b/tests/fixtures/layout-delayed-repo-init.yaml
deleted file mode 100644
index 04dc010..0000000
--- a/tests/fixtures/layout-delayed-repo-init.yaml
+++ /dev/null
@@ -1,52 +0,0 @@
-pipelines:
-  - name: check
-    manager: IndependentPipelineManager
-    trigger:
-      gerrit:
-        - event: patchset-created
-    success:
-      gerrit:
-        Verified: 1
-    failure:
-      gerrit:
-        Verified: -1
-
-  - name: post
-    manager: IndependentPipelineManager
-    trigger:
-      gerrit:
-        - event: ref-updated
-          ref: ^(?!refs/).*$
-
-  - name: gate
-    manager: DependentPipelineManager
-    failure-message: Build failed.  For information on how to proceed, see http://wiki.example.org/Test_Failures
-    trigger:
-      gerrit:
-        - event: comment-added
-          approval:
-            - Approved: 1
-    success:
-      gerrit:
-        Verified: 2
-        submit: true
-    failure:
-      gerrit:
-        Verified: -2
-    start:
-      gerrit:
-        Verified: 0
-    precedence: high
-
-projects:
-  - name: org/new-project
-    check:
-      - project-merge:
-        - project-test1
-        - project-test2
-    gate:
-      - project-merge:
-        - project-test1
-        - project-test2
-    post:
-      - project-post
diff --git a/tests/fixtures/layouts/delayed-repo-init.yaml b/tests/fixtures/layouts/delayed-repo-init.yaml
new file mode 100644
index 0000000..e97d37a
--- /dev/null
+++ b/tests/fixtures/layouts/delayed-repo-init.yaml
@@ -0,0 +1,77 @@
+- pipeline:
+    name: check
+    manager: independent
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        Verified: 1
+    failure:
+      gerrit:
+        Verified: -1
+
+- pipeline:
+    name: post
+    manager: independent
+    trigger:
+      gerrit:
+        - event: ref-updated
+          ref: ^(?!refs/).*$
+
+- pipeline:
+    name: gate
+    manager: dependent
+    failure-message: Build failed.  For information on how to proceed, see http://wiki.example.org/Test_Failures
+    trigger:
+      gerrit:
+        - event: comment-added
+          approval:
+            - Approved: 1
+    success:
+      gerrit:
+        Verified: 2
+        submit: true
+    failure:
+      gerrit:
+        Verified: -2
+    start:
+      gerrit:
+        Verified: 0
+    precedence: high
+
+- job:
+    name: base
+    parent: null
+
+- job:
+    name: project-merge
+
+- job:
+    name: project-test1
+
+- job:
+    name: project-test2
+
+- job:
+    name: project-post
+
+- project:
+    name: org/new-project
+    check:
+      jobs:
+        - project-merge
+        - project-test1:
+            dependencies: project-merge
+        - project-test2:
+            dependencies: project-merge
+    gate:
+      jobs:
+        - project-merge:
+        - project-test1:
+            dependencies: project-merge
+        - project-test2:
+            dependencies: project-merge
+    post:
+      jobs:
+        - project-post
diff --git a/tests/fixtures/tenants/delayed-repo-init.yaml b/tests/fixtures/tenants/delayed-repo-init.yaml
new file mode 100644
index 0000000..433e6f7
--- /dev/null
+++ b/tests/fixtures/tenants/delayed-repo-init.yaml
@@ -0,0 +1,11 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - common-config
+        untrusted-projects:
+          - org/project
+          - org/project1
+          - org/project2
+          - org/new-project
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index ce30e7c..6dd8333 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -202,7 +202,7 @@
             'name': 'python27',
             'parent': 'base',
             'pre-run': 'py27-pre',
-            'post-run': 'py27-post',
+            'post-run': ['py27-post-a', 'py27-post-b'],
             'nodes': [{
                 'name': 'controller',
                 'label': 'new',
@@ -275,7 +275,8 @@
                          ['base-pre',
                           'py27-pre'])
         self.assertEqual([x.path for x in job.post_run],
-                         ['py27-post',
+                         ['py27-post-a',
+                          'py27-post-b',
                           'base-post'])
         self.assertEqual([x.path for x in job.run],
                          ['playbooks/python27',
@@ -305,7 +306,8 @@
                           'py27-diablo-pre'])
         self.assertEqual([x.path for x in job.post_run],
                          ['py27-diablo-post',
-                          'py27-post',
+                          'py27-post-a',
+                          'py27-post-b',
                           'base-post'])
         self.assertEqual([x.path for x in job.run],
                          ['py27-diablo']),
@@ -330,7 +332,8 @@
                           'py27-essex-pre'])
         self.assertEqual([x.path for x in job.post_run],
                          ['py27-essex-post',
-                          'py27-post',
+                          'py27-post-a',
+                          'py27-post-b',
                           'base-post'])
         self.assertEqual([x.path for x in job.run],
                          ['playbooks/python27',
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index 97d53e0..70f3fb7 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -2759,13 +2759,18 @@
         self.assertEqual(len(tenant.layout.pipelines['check'].queues), 0)
         self.assertIn('Build succeeded', A.messages[0])
 
-    @skip("Disabled for early v3 development")
     def test_delayed_repo_init(self):
-        self.updateConfigLayout(
-            'tests/fixtures/layout-delayed-repo-init.yaml')
-        self.sched.reconfigure(self.config)
-
         self.init_repo("org/new-project")
+        files = {'README': ''}
+        self.addCommitToRepo("org/new-project", 'Initial commit',
+                             files=files, tag='init')
+        self.newTenantConfig('tenants/delayed-repo-init.yaml')
+        self.commitConfigUpdate(
+            'common-config',
+            'layouts/delayed-repo-init.yaml')
+        self.sched.reconfigure(self.config)
+        self.waitUntilSettled()
+
         A = self.fake_gerrit.addFakeChange('org/new-project', 'master', 'A')
 
         A.addApproval('Code-Review', 2)
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 98cffcc..60a0986 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -14,6 +14,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import json
 import os
 import textwrap
 
@@ -1338,3 +1339,39 @@
         # paths.
         self.executor_server.verbose = True
         self._test_secret_file_fail()
+
+
+class TestJobOutput(AnsibleZuulTestCase):
+    tenant_config_file = 'config/job-output/main.yaml'
+
+    def _get_file(self, build, path):
+        p = os.path.join(build.jobdir.root, path)
+        with open(p) as f:
+            return f.read()
+
+    def test_job_output(self):
+        # Verify that command standard output appears in the job output
+
+        # This currently only verifies we receive output from
+        # localhost.  Notably, it does not verify we receive output
+        # via zuul_console streaming.
+        self.executor_server.keep_jobdir = True
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.assertHistory([
+            dict(name='job-output', result='SUCCESS', changes='1,1'),
+        ], ordered=False)
+
+        token = 'Standard output test %s' % (self.history[0].jobdir.src_root)
+        j = json.loads(self._get_file(self.history[0],
+                                      'work/logs/job-output.json'))
+        self.assertEqual(token,
+                         j[0]['plays'][0]['tasks'][0]
+                         ['hosts']['localhost']['stdout'])
+
+        print(self._get_file(self.history[0],
+                             'work/logs/job-output.txt'))
+        self.assertIn(token,
+                      self._get_file(self.history[0],
+                                     'work/logs/job-output.txt'))
diff --git a/zuul/ansible/callback/zuul_stream.py b/zuul/ansible/callback/zuul_stream.py
index 4744719..1ffeb9c 100644
--- a/zuul/ansible/callback/zuul_stream.py
+++ b/zuul/ansible/callback/zuul_stream.py
@@ -256,8 +256,12 @@
                 is_localhost = True
         else:
             task_hostvars = result._task._variable_manager._hostvars[task_host]
+            # Normally hosts in the inventory will have ansible_host
+            # or ansible_inventory host defined.  The implied
+            # inventory record for 'localhost' will have neither, so
+            # default to that if none are supplied.
             if task_hostvars.get('ansible_host', task_hostvars.get(
-                    'ansible_inventory_host')) in localhost_names:
+                    'ansible_inventory_host', 'localhost')) in localhost_names:
                 is_localhost = True
 
         if not is_localhost and is_task:
@@ -489,7 +493,7 @@
 
         # _restriction returns the parsed/compiled list of hosts after
         # applying subsets/limits
-        return self.play._variable_manager._inventory._restriction
+        return self._play._variable_manager._inventory._restriction
 
     def _dump_result_dict(self, result_dict):
         result_dict = result_dict.copy()
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 7da54cd..c925024 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -507,7 +507,10 @@
                                             pre_run_name, job.roles,
                                             secrets)
             job.pre_run = job.pre_run + (pre_run,)
-        for post_run_name in as_list(conf.get('post-run')):
+        # NOTE(pabelanger): Reverse the order of our post-run list. We prepend
+        # post-runs for inherits however, we want to execute post-runs in the
+        # order they are listed within the job.
+        for post_run_name in reversed(as_list(conf.get('post-run'))):
             post_run = model.PlaybookContext(job.source_context,
                                              post_run_name, job.roles,
                                              secrets)
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index 1a445f1..6a39096 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -256,6 +256,7 @@
         self.ansible_config = os.path.join(self.root, 'ansible.cfg')
         self.project_link = os.path.join(self.root, 'project')
         self.secrets_root = os.path.join(self.root, 'secrets')
+        os.makedirs(self.secrets_root)
         self.secrets = os.path.join(self.secrets_root, 'secrets.yaml')
         self.secrets_content = None
 
@@ -923,7 +924,6 @@
         args = json.loads(self.job.arguments)
         self.log.debug("Beginning job %s for ref %s" %
                        (self.job.name, args['zuul']['ref']))
-        self.log.debug("Args: %s" % (self.job.arguments,))
         self.log.debug("Job root: %s" % (self.jobdir.root,))
         tasks = []
         projects = set()