Remove tenant argument from UnparsedTenantConfig

This allows UnparsedTenantConfig to be a simple data structure
which doesn't perform any project name resolution during loading.
This simplifies arguments to several functions.

Change-Id: I658453e7b4320b369388796edd246f90fb551d84
diff --git a/zuul/configloader.py b/zuul/configloader.py
index d2256ba..2580cb2 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -1265,8 +1265,8 @@
             self._loadTenantInRepoLayouts(tenant.config_projects,
                                           tenant.untrusted_projects,
                                           cached, tenant)
-        unparsed_config.extend(tenant.config_projects_config, tenant)
-        unparsed_config.extend(tenant.untrusted_projects_config, tenant)
+        unparsed_config.extend(tenant.config_projects_config)
+        unparsed_config.extend(tenant.untrusted_projects_config)
         tenant.layout = self._parseLayout(base, tenant, unparsed_config)
         return tenant
 
@@ -1514,10 +1514,10 @@
                     (job.project,))
                 if job.config_project:
                     config_projects_config.extend(
-                        job.project.unparsed_config, tenant)
+                        job.project.unparsed_config)
                 else:
                     untrusted_projects_config.extend(
-                        job.project.unparsed_config, tenant)
+                        job.project.unparsed_config)
                 continue
             self.log.debug("Waiting for cat job %s" % (job,))
             job.wait()
@@ -1548,18 +1548,18 @@
                     branch = source_context.branch
                     if source_context.trusted:
                         incdata = self.loadConfigProjectLayout(
-                            job.files[fn], source_context, tenant)
-                        config_projects_config.extend(incdata, tenant)
+                            job.files[fn], source_context)
+                        config_projects_config.extend(incdata)
                     else:
                         incdata = self.loadUntrustedProjectLayout(
-                            job.files[fn], source_context, tenant)
-                        untrusted_projects_config.extend(incdata, tenant)
+                            job.files[fn], source_context)
+                        untrusted_projects_config.extend(incdata)
                     new_project_unparsed_config[project].extend(
-                        incdata, tenant)
+                        incdata)
                     if branch in new_project_unparsed_branch_config.get(
                             project, {}):
                         new_project_unparsed_branch_config[project][branch].\
-                            extend(incdata, tenant)
+                            extend(incdata)
         # Now that we've sucessfully loaded all of the configuration,
         # cache the unparsed data on the project objects.
         for project, data in new_project_unparsed_config.items():
@@ -1569,17 +1569,17 @@
             project.unparsed_branch_config = branch_config
         return config_projects_config, untrusted_projects_config
 
-    def loadConfigProjectLayout(self, data, source_context, tenant):
+    def loadConfigProjectLayout(self, data, source_context):
         # This is the top-level configuration for a tenant.
         config = model.UnparsedTenantConfig()
         with early_configuration_exceptions(source_context):
-            config.extend(safe_load_yaml(data, source_context), tenant)
+            config.extend(safe_load_yaml(data, source_context))
         return config
 
-    def loadUntrustedProjectLayout(self, data, source_context, tenant):
+    def loadUntrustedProjectLayout(self, data, source_context):
         config = model.UnparsedTenantConfig()
         with early_configuration_exceptions(source_context):
-            config.extend(safe_load_yaml(data, source_context), tenant)
+            config.extend(safe_load_yaml(data, source_context))
         if config.pipelines:
             with configuration_exceptions('pipeline', config.pipelines[0]):
                 raise PipelineNotPermittedError()
@@ -1674,8 +1674,9 @@
                 layout.addProjectTemplate(project_template_parser.fromYaml(
                     config_template))
 
+        flattened_projects = self._flattenProjects(data.projects, tenant)
         project_parser = ProjectParser(tenant, layout, project_template_parser)
-        for config_projects in data.projects.values():
+        for config_projects in flattened_projects.values():
             # Unlike other config classes, we expect multiple project
             # stanzas with the same name, so that a config repo can
             # define a project-pipeline and the project itself can
@@ -1695,6 +1696,26 @@
             layout.addProjectConfig(project_parser.fromYaml(
                 filtered_projects))
 
+    def _flattenProjects(self, projects, tenant):
+        # Group together all of the project stanzas for each project.
+        result_projects = {}
+        for config_project in projects:
+            with configuration_exceptions('project', config_project):
+                name = config_project.get('name')
+                if not name:
+                    # There is no name defined so implicitly add the name
+                    # of the project where it is defined.
+                    name = (config_project['_source_context'].
+                            project.canonical_name)
+                else:
+                    trusted, project = tenant.getProject(name)
+                    if project is None:
+                        raise ProjectNotFoundError(name)
+                    name = project.canonical_name
+                config_project['name'] = name
+                result_projects.setdefault(name, []).append(config_project)
+        return result_projects
+
     def _parseLayout(self, base, tenant, data):
         # Don't call this method from dynamic reconfiguration because
         # it interacts with drivers and connections.
@@ -1784,7 +1805,7 @@
                 else:
                     incdata = project.unparsed_branch_config.get(branch)
                 if incdata:
-                    config.extend(incdata, tenant)
+                    config.extend(incdata)
                 continue
             # Otherwise, do not use the cached config (even if the
             # files are empty as that likely means they were deleted).
@@ -1814,13 +1835,13 @@
                     if trusted:
                         incdata = (self.tenant_parser.
                                    loadConfigProjectLayout(
-                                       data, source_context, tenant))
+                                       data, source_context))
                     else:
                         incdata = (self.tenant_parser.
                                    loadUntrustedProjectLayout(
-                                       data, source_context, tenant))
+                                       data, source_context))
 
-                    config.extend(incdata, tenant)
+                    config.extend(incdata)
 
     def createDynamicLayout(self, tenant, files,
                             include_config_projects=False,
diff --git a/zuul/model.py b/zuul/model.py
index 2ccaade..d0c66e8 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -2478,7 +2478,7 @@
         self.pipelines = []
         self.jobs = []
         self.project_templates = []
-        self.projects = {}
+        self.projects = []
         self.nodesets = []
         self.secrets = []
         self.semaphores = []
@@ -2495,23 +2495,13 @@
         r.semaphores = copy.deepcopy(self.semaphores)
         return r
 
-    def extend(self, conf, tenant):
+    def extend(self, conf):
         if isinstance(conf, UnparsedTenantConfig):
             self.pragmas.extend(conf.pragmas)
             self.pipelines.extend(conf.pipelines)
             self.jobs.extend(conf.jobs)
             self.project_templates.extend(conf.project_templates)
-            for k, v in conf.projects.items():
-                name = k
-                # Add the projects to the according canonical name instead of
-                # the given project name. If it is not found, it's ok to add
-                # this to the given name. We also don't need to throw the
-                # ProjectNotFoundException here as semantic validation occurs
-                # later where it will fail then.
-                trusted, project = tenant.getProject(k)
-                if project is not None:
-                    name = project.canonical_name
-                self.projects.setdefault(name, []).extend(v)
+            self.projects.extend(conf.projects)
             self.nodesets.extend(conf.nodesets)
             self.secrets.extend(conf.secrets)
             self.semaphores.extend(conf.semaphores)
@@ -2527,13 +2517,7 @@
                 raise ConfigItemMultipleKeysError()
             key, value = list(item.items())[0]
             if key == 'project':
-                name = value.get('name')
-                if not name:
-                    # There is no name defined so implicitly add the name
-                    # of the project where it is defined.
-                    name = value['_source_context'].project.canonical_name
-                    value['name'] = name
-                self.projects.setdefault(name, []).append(value)
+                self.projects.append(value)
             elif key == 'job':
                 self.jobs.append(value)
             elif key == 'project-template':