Allow per-repo selection of configuration classes to load

So that multiple Zuul installations can share portions of their
configuration, allow the administrator to indicate which
configuration objects should be loaded from which repositories.

This also facilitates third-party CI, and is important for
any interaction with repos for which a given Zuul installation is
not fully responsible.

In particular, this allows an administrator to use the jobs, but
not the project-pipeline definitions from a given repo.  Or even
to use the content of a repo without reading any of the zuul
configuration therein.

Change-Id: I8a07e298c8cf4dd7cbf6f5b7fc38990f7d740af4
diff --git a/tests/fixtures/config/tenant-parser/git/common-config/zuul.yaml b/tests/fixtures/config/tenant-parser/git/common-config/zuul.yaml
new file mode 100644
index 0000000..9e52187
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/git/common-config/zuul.yaml
@@ -0,0 +1,27 @@
+- pipeline:
+    name: check
+    manager: independent
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        verified: 1
+    failure:
+      gerrit:
+        verified: -1
+
+- job:
+    name: common-config-job
+
+- project:
+    name: org/project1
+    check:
+      jobs:
+        - common-config-job
+
+- project:
+    name: org/project2
+    check:
+      jobs:
+        - common-config-job
diff --git a/tests/fixtures/config/tenant-parser/git/org_project1/.zuul.yaml b/tests/fixtures/config/tenant-parser/git/org_project1/.zuul.yaml
new file mode 100644
index 0000000..cd5dba7
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/git/org_project1/.zuul.yaml
@@ -0,0 +1,8 @@
+- job:
+    name: project1-job
+
+- project:
+    name: org/project1
+    check:
+      jobs:
+        - project1-job
diff --git a/tests/fixtures/config/tenant-parser/git/org_project2/.zuul.yaml b/tests/fixtures/config/tenant-parser/git/org_project2/.zuul.yaml
new file mode 100644
index 0000000..4292c89
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/git/org_project2/.zuul.yaml
@@ -0,0 +1,8 @@
+- job:
+    name: project2-job
+
+- project:
+    name: org/project2
+    check:
+      jobs:
+        - project2-job
diff --git a/tests/fixtures/config/tenant-parser/groups.yaml b/tests/fixtures/config/tenant-parser/groups.yaml
new file mode 100644
index 0000000..f2a0d99
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/groups.yaml
@@ -0,0 +1,11 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - common-config
+        untrusted-projects:
+          - exclude: project
+            projects:
+              - org/project1
+              - org/project2
diff --git a/tests/fixtures/config/tenant-parser/groups2.yaml b/tests/fixtures/config/tenant-parser/groups2.yaml
new file mode 100644
index 0000000..dc8d339
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/groups2.yaml
@@ -0,0 +1,12 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - common-config
+        untrusted-projects:
+          - exclude: project
+            projects:
+              - org/project1
+              - org/project2:
+                  exclude: job
diff --git a/tests/fixtures/config/tenant-parser/groups3.yaml b/tests/fixtures/config/tenant-parser/groups3.yaml
new file mode 100644
index 0000000..196f03a
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/groups3.yaml
@@ -0,0 +1,14 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - common-config
+        untrusted-projects:
+          - include: job
+            projects:
+              - org/project1
+              - org/project2:
+                  include:
+                    - project
+                    - job
diff --git a/tests/fixtures/config/tenant-parser/override.yaml b/tests/fixtures/config/tenant-parser/override.yaml
new file mode 100644
index 0000000..87674f1
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/override.yaml
@@ -0,0 +1,11 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - common-config
+        untrusted-projects:
+          - org/project1:
+              exclude: project
+          - org/project2:
+              include: job
diff --git a/tests/fixtures/config/tenant-parser/simple.yaml b/tests/fixtures/config/tenant-parser/simple.yaml
new file mode 100644
index 0000000..950b117
--- /dev/null
+++ b/tests/fixtures/config/tenant-parser/simple.yaml
@@ -0,0 +1,9 @@
+- tenant:
+    name: tenant-one
+    source:
+      gerrit:
+        config-projects:
+          - common-config
+        untrusted-projects:
+          - org/project1
+          - org/project2
diff --git a/tests/unit/test_configloader.py b/tests/unit/test_configloader.py
new file mode 100644
index 0000000..faa2f61
--- /dev/null
+++ b/tests/unit/test_configloader.py
@@ -0,0 +1,188 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tests.base import ZuulTestCase
+
+
+class TenantParserTestCase(ZuulTestCase):
+    create_project_keys = True
+
+    CONFIG_SET = set(['pipeline', 'job', 'semaphore', 'project',
+                      'project-template', 'nodeset', 'secret'])
+    UNTRUSTED_SET = CONFIG_SET - set(['pipeline'])
+
+    def setupAllProjectKeys(self):
+        for project in ['common-config', 'org/project1', 'org/project2']:
+            self.setupProjectKeys('gerrit', project)
+
+
+class TestTenantSimple(TenantParserTestCase):
+    tenant_config_file = 'config/tenant-parser/simple.yaml'
+
+    def test_tenant_simple(self):
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        self.assertEqual(['common-config'],
+                         [x.name for x in tenant.config_projects])
+        self.assertEqual(['org/project1', 'org/project2'],
+                         [x.name for x in tenant.untrusted_projects])
+        self.assertEqual(self.CONFIG_SET,
+                         tenant.config_projects[0].load_classes)
+        self.assertEqual(self.UNTRUSTED_SET,
+                         tenant.untrusted_projects[0].load_classes)
+        self.assertEqual(self.UNTRUSTED_SET,
+                         tenant.untrusted_projects[1].load_classes)
+        self.assertTrue('common-config-job' in tenant.layout.jobs)
+        self.assertTrue('project1-job' in tenant.layout.jobs)
+        self.assertTrue('project2-job' in tenant.layout.jobs)
+        project1_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project1')
+        self.assertTrue('common-config-job' in
+                        project1_config.pipelines['check'].job_list.jobs)
+        self.assertTrue('project1-job' in
+                        project1_config.pipelines['check'].job_list.jobs)
+        project2_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project2')
+        self.assertTrue('common-config-job' in
+                        project2_config.pipelines['check'].job_list.jobs)
+        self.assertTrue('project2-job' in
+                        project2_config.pipelines['check'].job_list.jobs)
+
+
+class TestTenantOverride(TenantParserTestCase):
+    tenant_config_file = 'config/tenant-parser/override.yaml'
+
+    def test_tenant_override(self):
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        self.assertEqual(['common-config'],
+                         [x.name for x in tenant.config_projects])
+        self.assertEqual(['org/project1', 'org/project2'],
+                         [x.name for x in tenant.untrusted_projects])
+        self.assertEqual(self.CONFIG_SET,
+                         tenant.config_projects[0].load_classes)
+        self.assertEqual(self.UNTRUSTED_SET - set(['project']),
+                         tenant.untrusted_projects[0].load_classes)
+        self.assertEqual(set(['job']),
+                         tenant.untrusted_projects[1].load_classes)
+        self.assertTrue('common-config-job' in tenant.layout.jobs)
+        self.assertTrue('project1-job' in tenant.layout.jobs)
+        self.assertTrue('project2-job' in tenant.layout.jobs)
+        project1_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project1')
+        self.assertTrue('common-config-job' in
+                        project1_config.pipelines['check'].job_list.jobs)
+        self.assertFalse('project1-job' in
+                         project1_config.pipelines['check'].job_list.jobs)
+        project2_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project2')
+        self.assertTrue('common-config-job' in
+                        project2_config.pipelines['check'].job_list.jobs)
+        self.assertFalse('project2-job' in
+                         project2_config.pipelines['check'].job_list.jobs)
+
+
+class TestTenantGroups(TenantParserTestCase):
+    tenant_config_file = 'config/tenant-parser/groups.yaml'
+
+    def test_tenant_groups(self):
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        self.assertEqual(['common-config'],
+                         [x.name for x in tenant.config_projects])
+        self.assertEqual(['org/project1', 'org/project2'],
+                         [x.name for x in tenant.untrusted_projects])
+        self.assertEqual(self.CONFIG_SET,
+                         tenant.config_projects[0].load_classes)
+        self.assertEqual(self.UNTRUSTED_SET - set(['project']),
+                         tenant.untrusted_projects[0].load_classes)
+        self.assertEqual(self.UNTRUSTED_SET - set(['project']),
+                         tenant.untrusted_projects[1].load_classes)
+        self.assertTrue('common-config-job' in tenant.layout.jobs)
+        self.assertTrue('project1-job' in tenant.layout.jobs)
+        self.assertTrue('project2-job' in tenant.layout.jobs)
+        project1_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project1')
+        self.assertTrue('common-config-job' in
+                        project1_config.pipelines['check'].job_list.jobs)
+        self.assertFalse('project1-job' in
+                         project1_config.pipelines['check'].job_list.jobs)
+        project2_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project2')
+        self.assertTrue('common-config-job' in
+                        project2_config.pipelines['check'].job_list.jobs)
+        self.assertFalse('project2-job' in
+                         project2_config.pipelines['check'].job_list.jobs)
+
+
+class TestTenantGroups2(TenantParserTestCase):
+    tenant_config_file = 'config/tenant-parser/groups2.yaml'
+
+    def test_tenant_groups2(self):
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        self.assertEqual(['common-config'],
+                         [x.name for x in tenant.config_projects])
+        self.assertEqual(['org/project1', 'org/project2'],
+                         [x.name for x in tenant.untrusted_projects])
+        self.assertEqual(self.CONFIG_SET,
+                         tenant.config_projects[0].load_classes)
+        self.assertEqual(self.UNTRUSTED_SET - set(['project']),
+                         tenant.untrusted_projects[0].load_classes)
+        self.assertEqual(self.UNTRUSTED_SET - set(['project', 'job']),
+                         tenant.untrusted_projects[1].load_classes)
+        self.assertTrue('common-config-job' in tenant.layout.jobs)
+        self.assertTrue('project1-job' in tenant.layout.jobs)
+        self.assertFalse('project2-job' in tenant.layout.jobs)
+        project1_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project1')
+        self.assertTrue('common-config-job' in
+                        project1_config.pipelines['check'].job_list.jobs)
+        self.assertFalse('project1-job' in
+                         project1_config.pipelines['check'].job_list.jobs)
+        project2_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project2')
+        self.assertTrue('common-config-job' in
+                        project2_config.pipelines['check'].job_list.jobs)
+        self.assertFalse('project2-job' in
+                         project2_config.pipelines['check'].job_list.jobs)
+
+
+class TestTenantGroups3(TenantParserTestCase):
+    tenant_config_file = 'config/tenant-parser/groups3.yaml'
+
+    def test_tenant_groups3(self):
+        tenant = self.sched.abide.tenants.get('tenant-one')
+        self.assertEqual(['common-config'],
+                         [x.name for x in tenant.config_projects])
+        self.assertEqual(['org/project1', 'org/project2'],
+                         [x.name for x in tenant.untrusted_projects])
+        self.assertEqual(self.CONFIG_SET,
+                         tenant.config_projects[0].load_classes)
+        self.assertEqual(set(['job']),
+                         tenant.untrusted_projects[0].load_classes)
+        self.assertEqual(set(['project', 'job']),
+                         tenant.untrusted_projects[1].load_classes)
+        self.assertTrue('common-config-job' in tenant.layout.jobs)
+        self.assertTrue('project1-job' in tenant.layout.jobs)
+        self.assertTrue('project2-job' in tenant.layout.jobs)
+        project1_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project1')
+        self.assertTrue('common-config-job' in
+                        project1_config.pipelines['check'].job_list.jobs)
+        self.assertFalse('project1-job' in
+                         project1_config.pipelines['check'].job_list.jobs)
+        project2_config = tenant.layout.project_configs.get(
+            'review.example.com/org/project2')
+        self.assertTrue('common-config-job' in
+                        project2_config.pipelines['check'].job_list.jobs)
+        self.assertTrue('project2-job' in
+                        project2_config.pipelines['check'].job_list.jobs)