Add support for roles in zuul

This adds support for Ansible roles in Zuul-managed repos.  It
is currently limited to repos within the same source, which is
something we should fix.

We also plan to add support for roles from Ansible Galaxy in a
future change.

Change-Id: I7af4dc1333db0dcb9d4a8318a4a95b9564cd1dd8
diff --git a/tests/fixtures/config/ansible/git/bare-role/tasks/main.yaml b/tests/fixtures/config/ansible/git/bare-role/tasks/main.yaml
new file mode 100644
index 0000000..75943b1
--- /dev/null
+++ b/tests/fixtures/config/ansible/git/bare-role/tasks/main.yaml
@@ -0,0 +1,3 @@
+- file:
+    path: "{{zuul._test.test_root}}/{{zuul.uuid}}.bare-role.flag"
+    state: touch
diff --git a/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml b/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
index 408810e..6b79a78 100644
--- a/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
+++ b/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
@@ -6,3 +6,5 @@
     - copy:
         src: "{{zuul._test.test_root}}/{{zuul.uuid}}.flag"
         dest: "{{zuul._test.test_root}}/{{zuul.uuid}}.copied"
+  roles:
+    - bare-role
diff --git a/tests/fixtures/config/ansible/git/common-config/zuul.yaml b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
index 7964243..7373eff 100644
--- a/tests/fixtures/config/ansible/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
@@ -40,3 +40,5 @@
     name: python27
     pre-run: pre
     post-run: post
+    roles:
+      - zuul: bare-role
diff --git a/tests/fixtures/config/ansible/main.yaml b/tests/fixtures/config/ansible/main.yaml
index d9868fa..8df99f4 100644
--- a/tests/fixtures/config/ansible/main.yaml
+++ b/tests/fixtures/config/ansible/main.yaml
@@ -6,3 +6,4 @@
           - common-config
         project-repos:
           - org/project
+          - bare-role
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index b54eb5f..9dac383 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -34,10 +34,11 @@
 
     @property
     def job(self):
+        tenant = model.Tenant('tenant')
         layout = model.Layout()
         project = model.Project('project', None)
         context = model.SourceContext(project, 'master', True)
-        job = configloader.JobParser.fromYaml(layout, {
+        job = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'job',
             'irrelevant-files': [
@@ -134,6 +135,7 @@
 
     def test_job_inheritance_configloader(self):
         # TODO(jeblair): move this to a configloader test
+        tenant = model.Tenant('tenant')
         layout = model.Layout()
 
         pipeline = model.Pipeline('gate', layout)
@@ -142,7 +144,7 @@
         project = model.Project('project', None)
         context = model.SourceContext(project, 'master', True)
 
-        base = configloader.JobParser.fromYaml(layout, {
+        base = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'base',
             'timeout': 30,
@@ -154,7 +156,7 @@
             }],
         })
         layout.addJob(base)
-        python27 = configloader.JobParser.fromYaml(layout, {
+        python27 = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'python27',
             'parent': 'base',
@@ -167,7 +169,7 @@
             'timeout': 40,
         })
         layout.addJob(python27)
-        python27diablo = configloader.JobParser.fromYaml(layout, {
+        python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'python27',
             'branches': [
@@ -184,7 +186,7 @@
         })
         layout.addJob(python27diablo)
 
-        python27essex = configloader.JobParser.fromYaml(layout, {
+        python27essex = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'python27',
             'branches': [
@@ -195,7 +197,7 @@
         })
         layout.addJob(python27essex)
 
-        project_config = configloader.ProjectParser.fromYaml(layout, {
+        project_config = configloader.ProjectParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'project',
             'gate': {
@@ -291,43 +293,46 @@
                           'playbooks/base'])
 
     def test_job_auth_inheritance(self):
+        tenant = model.Tenant('tenant')
         layout = model.Layout()
         project = model.Project('project', None)
         context = model.SourceContext(project, 'master', True)
 
-        base = configloader.JobParser.fromYaml(layout, {
+        base = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'base',
             'timeout': 30,
         })
         layout.addJob(base)
-        pypi_upload_without_inherit = configloader.JobParser.fromYaml(layout, {
-            '_source_context': context,
-            'name': 'pypi-upload-without-inherit',
-            'parent': 'base',
-            'timeout': 40,
-            'auth': {
-                'secrets': [
-                    'pypi-credentials',
-                ]
-            }
-        })
+        pypi_upload_without_inherit = configloader.JobParser.fromYaml(
+            tenant, layout, {
+                '_source_context': context,
+                'name': 'pypi-upload-without-inherit',
+                'parent': 'base',
+                'timeout': 40,
+                'auth': {
+                    'secrets': [
+                        'pypi-credentials',
+                    ]
+                }
+            })
         layout.addJob(pypi_upload_without_inherit)
-        pypi_upload_with_inherit = configloader.JobParser.fromYaml(layout, {
-            '_source_context': context,
-            'name': 'pypi-upload-with-inherit',
-            'parent': 'base',
-            'timeout': 40,
-            'auth': {
-                'inherit': True,
-                'secrets': [
-                    'pypi-credentials',
-                ]
-            }
-        })
+        pypi_upload_with_inherit = configloader.JobParser.fromYaml(
+            tenant, layout, {
+                '_source_context': context,
+                'name': 'pypi-upload-with-inherit',
+                'parent': 'base',
+                'timeout': 40,
+                'auth': {
+                    'inherit': True,
+                    'secrets': [
+                        'pypi-credentials',
+                    ]
+                }
+            })
         layout.addJob(pypi_upload_with_inherit)
         pypi_upload_with_inherit_false = configloader.JobParser.fromYaml(
-            layout, {
+            tenant, layout, {
                 '_source_context': context,
                 'name': 'pypi-upload-with-inherit-false',
                 'parent': 'base',
@@ -340,20 +345,22 @@
                 }
             })
         layout.addJob(pypi_upload_with_inherit_false)
-        in_repo_job_without_inherit = configloader.JobParser.fromYaml(layout, {
-            '_source_context': context,
-            'name': 'in-repo-job-without-inherit',
-            'parent': 'pypi-upload-without-inherit',
-        })
+        in_repo_job_without_inherit = configloader.JobParser.fromYaml(
+            tenant, layout, {
+                '_source_context': context,
+                'name': 'in-repo-job-without-inherit',
+                'parent': 'pypi-upload-without-inherit',
+            })
         layout.addJob(in_repo_job_without_inherit)
-        in_repo_job_with_inherit = configloader.JobParser.fromYaml(layout, {
-            '_source_context': context,
-            'name': 'in-repo-job-with-inherit',
-            'parent': 'pypi-upload-with-inherit',
-        })
+        in_repo_job_with_inherit = configloader.JobParser.fromYaml(
+            tenant, layout, {
+                '_source_context': context,
+                'name': 'in-repo-job-with-inherit',
+                'parent': 'pypi-upload-with-inherit',
+            })
         layout.addJob(in_repo_job_with_inherit)
         in_repo_job_with_inherit_false = configloader.JobParser.fromYaml(
-            layout, {
+            tenant, layout, {
                 '_source_context': context,
                 'name': 'in-repo-job-with-inherit-false',
                 'parent': 'pypi-upload-with-inherit-false',
@@ -367,6 +374,7 @@
         self.assertNotIn('auth', in_repo_job_with_inherit_false.auth)
 
     def test_job_inheritance_job_tree(self):
+        tenant = model.Tenant('tenant')
         layout = model.Layout()
 
         pipeline = model.Pipeline('gate', layout)
@@ -375,20 +383,20 @@
         project = model.Project('project', None)
         context = model.SourceContext(project, 'master', True)
 
-        base = configloader.JobParser.fromYaml(layout, {
+        base = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'base',
             'timeout': 30,
         })
         layout.addJob(base)
-        python27 = configloader.JobParser.fromYaml(layout, {
+        python27 = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'python27',
             'parent': 'base',
             'timeout': 40,
         })
         layout.addJob(python27)
-        python27diablo = configloader.JobParser.fromYaml(layout, {
+        python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'python27',
             'branches': [
@@ -398,7 +406,7 @@
         })
         layout.addJob(python27diablo)
 
-        project_config = configloader.ProjectParser.fromYaml(layout, {
+        project_config = configloader.ProjectParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'project',
             'gate': {
@@ -439,6 +447,7 @@
         self.assertEqual(job.timeout, 70)
 
     def test_inheritance_keeps_matchers(self):
+        tenant = model.Tenant('tenant')
         layout = model.Layout()
 
         pipeline = model.Pipeline('gate', layout)
@@ -447,13 +456,13 @@
         project = model.Project('project', None)
         context = model.SourceContext(project, 'master', True)
 
-        base = configloader.JobParser.fromYaml(layout, {
+        base = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'base',
             'timeout': 30,
         })
         layout.addJob(base)
-        python27 = configloader.JobParser.fromYaml(layout, {
+        python27 = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'python27',
             'parent': 'base',
@@ -462,7 +471,7 @@
         })
         layout.addJob(python27)
 
-        project_config = configloader.ProjectParser.fromYaml(layout, {
+        project_config = configloader.ProjectParser.fromYaml(tenant, layout, {
             '_source_context': context,
             'name': 'project',
             'gate': {
@@ -486,11 +495,12 @@
         self.assertEqual([], item.getJobs())
 
     def test_job_source_project(self):
+        tenant = model.Tenant('tenant')
         layout = model.Layout()
         base_project = model.Project('base_project', None)
         base_context = model.SourceContext(base_project, 'master', True)
 
-        base = configloader.JobParser.fromYaml(layout, {
+        base = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': base_context,
             'name': 'base',
         })
@@ -498,7 +508,7 @@
 
         other_project = model.Project('other_project', None)
         other_context = model.SourceContext(other_project, 'master', True)
-        base2 = configloader.JobParser.fromYaml(layout, {
+        base2 = configloader.JobParser.fromYaml(tenant, layout, {
             '_source_context': other_context,
             'name': 'base',
         })
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index dd812a8..27e2275 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -155,3 +155,6 @@
         post_flag_path = os.path.join(self.test_root, build.uuid +
                                       '.post.flag')
         self.assertTrue(os.path.exists(post_flag_path))
+        bare_role_flag_path = os.path.join(self.test_root,
+                                           build.uuid + '.bare-role.flag')
+        self.assertTrue(os.path.exists(bare_role_flag_path))